https://bugs.winehq.org/show_bug.cgi?id=39421
--- Comment #8 from Jonas Maebe jonas.bugzilla@gmail.com --- (In reply to Jonas Maebe from comment #7)
I guess it would boil down to implementing double buffering ourselves: always keep a copy of the previous image in VRAM, and then composite that one (on the card) with textures created from the the new partial blits (which come from system memory and which only cover part of the screen).
As may be clear from the above, until now I know/knew very little about either DirectDraw and OpenGL (other than high level concepts). I've been reading up a bit on both, and disregarding the adagio that"a little knowledge is worse than none", here's what I understood from it:
1) a ddraw surface can be updated in two ways: either you lock the surface manually and directly manipulate the pixel data, or you use the ddraw blit functions. In the former case, ddraw has no clue what exactly changed, in the latter case it knows exactly what changed.
2) you could have an OpenGL FBO, three texture images and a single render buffer image. Initially, you make texture image 1 all black. You assign texture image 2 via a texture object to one of the color attachments of the FBO, and the render buffer to to the depth attachment. Then you use the following logic after every blit:
previous_frame_tex = texture_image_1; blit_data_tex = texture_image_2; new_frame_tex = texture_image_3;
/* render the new frame into a texture ... */ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, new_frame_tex); /* ... and into a frame to display */ glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_FRAMEBUFFER, render_buffer); /* set up textures with data from previous frame and new frame */ glGenTextures(2, &textures); glBindTexture(textures[0], previous_frame_tex); glBindTextture(textures[1],new_drawing_tex); ... /* render */ ... /* flip FBO to frontbuffer */ ... /* flush */ glFlush();
/* set newly rendered frame as "previous frame" for the next invocation */ texture_image_1 = new_frame_tex; /* switch the texture ID for the next new frame */ texture_image_3 = previous_frame_tex;
Now, a) I don't know what the conditions are under which you can be confident that the frontbuffer will never be changed by anything else but ddraw (so that you can in fact use the rendering outcome of the previous frame as basis for a new one). In the case of this game, we always take the wined3d_surface_blt() path in ddraw_surface_update_frontbuffer(), whose comment says "Nothing to do, we control the frontbuffer, or at least the parts we care about.", so that suggests to me that it may be okay (along with the fact that it runs quickly on native, and that it presumably does something similar there) b) I don't know in what way the wined3d internals can conflict with this approach c) I don't know what wined3d does exactly currently that makes it so slow. Is it just blitting a complete new screen after every (small) blit, or is it in addition also getting the data from the frontbuffer for every frame? I think it's the former, but I'm not sure. d) probably other things I haven't thought of
I'll also attach the profiling data for the drawing thread. Some notes about this data. Note that it's sampling based, but the percentages *include* time spent blocked for kernel operations to complete. That's why the thread is recorded as using 8.5% of the total sampled time, as there are 12 "full time" threads in the program (I guess it's rounded up) and e.g. even threads that do almost nothing but spend time waiting for a select call to finish, are counted just as much as a thread that is constantly calculating.
The reason I'm attaching this data, is that it nicely illustrates how the game is indeed constantly waiting for glFlush() to finish in the main drawing/game logic thread. It also shows which paths it takes through ddraw and wined3d.