Fixes https://bugs.winehq.org/show_bug.cgi?id=57283.
I believe some sort of smart two pass stretching is possible instead, but not worth it.
[dlls/wined3d/surface.c#L1570](https://gitlab.winehq.org/riyu12383/wine/-/blob/f48645249a3402801f1ca942c35e...) not exactly sure if it's related and should be removed then ?
-- v4: wined3d: Use temporary buffer when stretching a surface to itself with cpu blit. ddraw/tests: Add tests for surface blitting to itself with stretching.
From: Yuri Hérouard yuri.herouard@gmail.com
--- dlls/ddraw/tests/ddraw1.c | 148 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 148 insertions(+)
diff --git a/dlls/ddraw/tests/ddraw1.c b/dlls/ddraw/tests/ddraw1.c index e62d9283aaa..fc546856c65 100644 --- a/dlls/ddraw/tests/ddraw1.c +++ b/dlls/ddraw/tests/ddraw1.c @@ -9582,6 +9582,153 @@ done: DestroyWindow(window); }
+static void test_stretched_blit(void) +{ + IDirectDrawSurface *surface1, *surface2, *surface3; + DDSURFACEDESC surface_desc; + IDirectDraw *ddraw; + HRESULT hr; + IDirect3DDevice *device; + ULONG color1, color2, color, refcount; + HWND window; + RECT rect; + BYTE* pSurface, *buffer; + color1 = 0xffffffff; + color2 = 0x00000000; + window = create_window(); + ddraw = create_ddraw(); + ok(!!ddraw, "Failed to create a ddraw object.\n"); + if (!(device = create_device(ddraw, window, DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN))) + { + skip("Failed to create a 3D device.\n"); + IDirectDraw_Release(ddraw); + DestroyWindow(window); + return; + } + hr = IDirect3DDevice_QueryInterface(device, &IID_IDirectDrawSurface, (void **)&surface1); + ok(SUCCEEDED(hr), "Failed to get render target, hr %#lx.\n", hr); + memset(&surface_desc, 0, sizeof(surface_desc)); + surface_desc.dwSize = sizeof(surface_desc); + surface_desc.dwFlags = DDSD_WIDTH | DDSD_HEIGHT | DDSD_CAPS; + surface_desc.dwWidth = 640; + surface_desc.dwHeight = 480; + surface_desc.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN | DDSCAPS_SYSTEMMEMORY; + hr = IDirectDraw_CreateSurface(ddraw, &surface_desc, &surface1, NULL); + ok(SUCCEEDED(hr), "Failed to create surface, hr %#lx.\n", hr); + hr = IDirectDraw_CreateSurface(ddraw, &surface_desc, &surface2, NULL); + ok(SUCCEEDED(hr), "Failed to create surface, hr %#lx.\n", hr); + hr = IDirectDraw_CreateSurface(ddraw, &surface_desc, &surface3, NULL); + ok(SUCCEEDED(hr), "Failed to create surface, hr %#lx.\n", hr); + + //Setting 1px checkerboard texture + hr = IDirectDrawSurface_Lock(surface1, NULL, &surface_desc, DDLOCK_WAIT, NULL); + ok(SUCCEEDED(hr), "Failed to lock surface, hr %#lx.\n", hr); + pSurface = (BYTE*)surface_desc.lpSurface; + for (int row = 0; row < 480; row++) + { + buffer = pSurface + row*surface_desc.lPitch; + for (int col = 0; col < 640; col++) + { + ((DWORD *)buffer)[col] = (row % 2 == 0) == (col % 2 == 0) ? color1 : color2; + } + } + hr = IDirectDrawSurface_Unlock(surface1, 0); + ok(SUCCEEDED(hr), "Failed to unlock surface, hr %#lx.\n", hr); + +#define CHECK_BLITTED_TEXTURE(surface_ptr, expected_color) \ +do { \ +BOOL color_ok = TRUE; \ +hr = IDirectDrawSurface_Lock(surface_ptr, NULL, &surface_desc, DDLOCK_WAIT, NULL); \ +ok(SUCCEEDED(hr), "Failed to lock surface, hr %#lx.\n", hr); \ +pSurface = (BYTE*)surface_desc.lpSurface; \ +for (int row = 0; row < surface_desc.dwHeight; row++) \ +{ \ + for (int col = 0; col < surface_desc.dwWidth; col++) \ + { \ + buffer = pSurface + row*surface_desc.lPitch; \ + color = ((DWORD *)buffer)[col]; \ + if (color != expected_color) \ + { \ + color_ok = FALSE; \ + break; \ + } \ + if(!color_ok) break; \ + } \ +} \ +ok(color_ok, "Blitted texture color is incorrect.\n"); \ +hr = IDirectDrawSurface_Unlock(surface_ptr, 0); \ +ok(SUCCEEDED(hr), "Failed to unlock surface, hr %#lx.\n", hr); \ +} while(0) + + rect.bottom = 240; + rect.right = 640; + rect.left = 0; + rect.top = 0; + hr = IDirectDrawSurface_Blt(surface2, NULL, surface1, &rect, DDBLT_WAIT, NULL); + ok(SUCCEEDED(hr), "Failed to blit, hr %#lx.\n", hr); + + //Checking that dst is a 2px vertical checkerboard + CHECK_BLITTED_TEXTURE(surface2, ((row % 4 < 2) == (col % 2 == 0) ? color1 : color2)); + + rect.bottom = 480; + rect.right = 320; + rect.left = 0; + rect.top = 0; + hr = IDirectDrawSurface_Blt(surface2, NULL, surface1, &rect, DDBLT_WAIT, NULL); + ok(SUCCEEDED(hr), "Failed to blit, hr %#lx.\n", hr); + + //Checking that dst is a 2px horizontal checkerboard + CHECK_BLITTED_TEXTURE(surface2, ((row % 2 == 0) == (col % 4 < 2) ? color1 : color2)); + + rect.bottom = 240; + rect.right = 320; + rect.left = 0; + rect.top = 0; + hr = IDirectDrawSurface_Blt(surface3, NULL, surface1, &rect, DDBLT_WAIT, NULL); + ok(SUCCEEDED(hr), "Failed to blit, hr %#lx.\n", hr); + + //Checking that src is a 4px checkerboard + CHECK_BLITTED_TEXTURE(surface3, ((row % 4 < 2) == (col % 4 < 2) ? color1 : color2)); + + rect.bottom = 240; + rect.right = 320; + rect.left = 0; + rect.top = 0; + //Blitting surface to itself ! Both axis stretched + hr = IDirectDrawSurface_Blt(surface1, NULL, surface1, &rect, DDBLT_WAIT, NULL); + ok(SUCCEEDED(hr), "Failed to blit, hr %#lx.\n", hr); + + //Checking that src is a 4px checkerboard + CHECK_BLITTED_TEXTURE(surface1, ((row % 4 < 2) == (col % 4 < 2) ? color1 : color2)); + + rect.bottom = 240; + rect.right = 640; + rect.left = 0; + rect.top = 0; + //Blitting surface to itself ! Vertical stretch + hr = IDirectDrawSurface_Blt(surface2, NULL, surface2, &rect, DDBLT_WAIT, NULL); + ok(SUCCEEDED(hr), "Failed to blit, hr %#lx.\n", hr); + + //Checking that src is a 4px checkerboard + CHECK_BLITTED_TEXTURE(surface2, ((row % 4 < 2) == (col % 4 < 2) ? color1 : color2)); + + rect.bottom = 480; + rect.right = 320; + rect.left = 0; + rect.top = 0; + //Blitting surface to itself ! Horizontal stretch + hr = IDirectDrawSurface_Blt(surface3, NULL, surface3, &rect, DDBLT_WAIT, NULL); + ok(SUCCEEDED(hr), "Failed to blit, hr %#lx.\n", hr); + + //Checking that src is a 2x4 px checkerboard + CHECK_BLITTED_TEXTURE(surface3, ((row % 4 < 2) == (col % 8 < 4) ? color1 : color2)); + + refcount = IDirectDraw_Release(ddraw); + ok(!refcount, "Unexpected refcount %lu.\n", refcount); + if (window) DestroyWindow(window); +#undef CHECK_BLITTED_TEXTURE +} + static void test_colorkey_precision(void) { static D3DTLVERTEX quad[] = @@ -16242,4 +16389,5 @@ START_TEST(ddraw1) test_multiple_devices(); test_sysmem_x_channel(); test_yuv_blit(); + test_stretched_blit(); }
From: Yuri Hérouard yuri.herouard@gmail.com
--- dlls/wined3d/surface.c | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+)
diff --git a/dlls/wined3d/surface.c b/dlls/wined3d/surface.c index 14cfebd7074..a054caba15a 100644 --- a/dlls/wined3d/surface.c +++ b/dlls/wined3d/surface.c @@ -616,6 +616,7 @@ static HRESULT surface_cpu_blt(struct wined3d_texture *dst_texture, unsigned int const BYTE *sbase; const BYTE *sbuf; BYTE *dbuf; + BYTE *tmp_buffer = NULL;
TRACE("dst_texture %p, dst_sub_resource_idx %u, dst_box %s, src_texture %p, " "src_sub_resource_idx %u, src_box %s, flags %#x, fx %p, filter %s.\n", @@ -808,18 +809,45 @@ static HRESULT surface_cpu_blt(struct wined3d_texture *dst_texture, unsigned int else { /* Stretching in y direction only. */ + + if (same_sub_resource) + { + /* Use a temporary buffer if blitting surface to itself */ + tmp_buffer = malloc(src_height * src_map.row_pitch); + if (tmp_buffer != NULL) + { + memcpy(tmp_buffer, sbase, src_height * src_map.row_pitch); + sbase = tmp_buffer; + } + } + for (y = sy = 0; y < dst_height; ++y, sy += yinc) { sbuf = sbase + (sy >> 16) * src_map.row_pitch; memcpy(dbuf, sbuf, row_byte_count); dbuf += dst_map.row_pitch; } + + if(same_sub_resource && tmp_buffer != NULL) + free(tmp_buffer); } } else { /* Stretching in X direction. */ unsigned int last_sy = ~0u; + + if (same_sub_resource) + { + /* Use a temporary buffer if blitting surface to itself */ + tmp_buffer = malloc(src_height * src_map.row_pitch); + if (tmp_buffer != NULL) + { + memcpy(tmp_buffer, sbase, src_height * src_map.row_pitch); + sbase = tmp_buffer; + } + } + for (y = sy = 0; y < dst_height; ++y, sy += yinc) { sbuf = sbase + (sy >> 16) * src_map.row_pitch; @@ -878,6 +906,9 @@ do { \ dbuf += dst_map.row_pitch; last_sy = sy; } + + if(same_sub_resource && tmp_buffer != NULL) + free(tmp_buffer); } } else