Yakuza 6's Puyo Puyo minigame creates a texture from a DDS file that is the equivalent format of `DXGI_FORMAT_B4G4R4A4_UNORM`, which windowscodecs does not support.
This patch converts the texture into the equivalent of `DXGI_FORMAT_R8G8B8A8_UNORM` DDS file at the start of `load_texture_data()`, which allows us to avoid having an entirely separate code path for this specific image type. This current patch just handles the most basic of DDS files, if this approach seems reasonable I will flesh it out more, I just wanted to see if it was a good/bad idea before putting anymore work into it. :)
From: Connor McAdams cmcadams@codeweavers.com
Windowscodecs has no WIC format for this, and cannot do conversion from it.
Signed-off-by: Connor McAdams cmcadams@codeweavers.com --- dlls/d3dx10_43/tests/d3dx10.c | 23 ++++++ dlls/d3dx10_43/texture.c | 140 +++++++++++++++++++++++++++++++++- 2 files changed, 162 insertions(+), 1 deletion(-)
diff --git a/dlls/d3dx10_43/tests/d3dx10.c b/dlls/d3dx10_43/tests/d3dx10.c index 5853669d9c5..f9bf3aae4d7 100644 --- a/dlls/d3dx10_43/tests/d3dx10.c +++ b/dlls/d3dx10_43/tests/d3dx10.c @@ -343,6 +343,25 @@ static const BYTE test_dds_16bpp_data[] = 0x84, 0x84, 0x73, 0xff };
+/* 1x1 16bpp dds image */ +static const BYTE test_dds_16bpp_bgra[] = +{ + 0x44, 0x44, 0x53, 0x20, 0x7c, 0x00, 0x00, 0x00, 0x07, 0x10, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, + 0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, + 0xf0, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x12, 0x34, +}; + +static const BYTE test_dds_16bpp_bgra_data[] = +{ + 0x44, 0x11, 0x22, 0x33 +}; + /* 1x1 24bpp dds image */ static const BYTE test_dds_24bpp[] = { @@ -796,6 +815,10 @@ test_image[] = test_dds_16bpp, sizeof(test_dds_16bpp), test_dds_16bpp_data, {1, 1, 1, 1, 1, 0, DXGI_FORMAT_R8G8B8A8_UNORM, D3D10_RESOURCE_DIMENSION_TEXTURE2D, D3DX10_IFF_DDS} }, + { + test_dds_16bpp_bgra, sizeof(test_dds_16bpp_bgra), test_dds_16bpp_bgra_data, + {1, 1, 1, 1, 1, 0, DXGI_FORMAT_R8G8B8A8_UNORM, D3D10_RESOURCE_DIMENSION_TEXTURE2D, D3DX10_IFF_DDS} + }, { test_dds_24bpp, sizeof(test_dds_24bpp), test_dds_24bpp_data, {1, 1, 1, 1, 1, 0, DXGI_FORMAT_R8G8B8A8_UNORM, D3D10_RESOURCE_DIMENSION_TEXTURE2D, D3DX10_IFF_DDS} diff --git a/dlls/d3dx10_43/texture.c b/dlls/d3dx10_43/texture.c index b925a07dd08..6854627d6ab 100644 --- a/dlls/d3dx10_43/texture.c +++ b/dlls/d3dx10_43/texture.c @@ -276,6 +276,138 @@ static DXGI_FORMAT get_d3dx10_dds_format(DXGI_FORMAT format) return format; }
+/* DDS format helper functions. */ +#define DDS_MAGIC 0x20534444 +#define GET_MAGIC(buf_ptr) (*((DWORD *)buf_ptr)) + +#define GET_B4G4R4A4_R(ptr) ((((BYTE *)ptr)[1]) & 0x0f) +#define GET_B4G4R4A4_G(ptr) (((((BYTE *)ptr)[0]) & 0xf0) >> 4) +#define GET_B4G4R4A4_B(ptr) ((((BYTE *)ptr)[0]) & 0x0f) +#define GET_B4G4R4A4_A(ptr) (((((BYTE *)ptr)[1]) & 0xf0) >> 4) + +#define GET_R8G8B8A8_R(ptr) (((BYTE *)ptr)[0]) +#define GET_R8G8B8A8_G(ptr) (((BYTE *)ptr)[1]) +#define GET_R8G8B8A8_B(ptr) (((BYTE *)ptr)[2]) +#define GET_R8G8B8A8_A(ptr) (((BYTE *)ptr)[3]) + +#define DDPF_RGB 0x00000040 + +typedef struct { + DWORD size; + DWORD flags; + DWORD fourCC; + DWORD rgbBitCount; + DWORD rBitMask; + DWORD gBitMask; + DWORD bBitMask; + DWORD aBitMask; +} DDS_PIXELFORMAT; + +typedef struct { + DWORD size; + DWORD flags; + DWORD height; + DWORD width; + DWORD pitchOrLinearSize; + DWORD depth; + DWORD mipMapCount; + DWORD reserved1[11]; + DDS_PIXELFORMAT ddspf; + DWORD caps; + DWORD caps2; + DWORD caps3; + DWORD caps4; + DWORD reserved2; +} DDS_HEADER; + +#define DDS_HEADER_OFFSET sizeof(DWORD) +#define DDS_TEXTURE_DATA_OFFSET DDS_HEADER_OFFSET + sizeof(DDS_HEADER) +static const struct dds_format_conversion { + DDS_PIXELFORMAT src_pixel_format; + DDS_PIXELFORMAT dst_pixel_format; +} dds_format_conversion_table[] = { + { { sizeof(DDS_PIXELFORMAT), DDPF_RGB, 0, 16, 0xF00,0xF0,0xF,0xF000 }, /* DXGI_FORMAT_B4G4R4A4_UNORM */ + { sizeof(DDS_PIXELFORMAT), DDPF_RGB, 0, 32, 0xFF,0xFF00,0xFF0000,0xFF000000 }, }, /* DXGI_FORMAT_R8G8B8A8_UNORM */ +}; + +static const struct dds_format_conversion *get_dds_format_conversion(const DDS_PIXELFORMAT *pixel_format) +{ + UINT i; + + for (i = 0; i < ARRAY_SIZE(dds_format_conversion_table); i++) + { + if ((pixel_format->flags & dds_format_conversion_table[i].src_pixel_format.flags) && + (pixel_format->fourCC == dds_format_conversion_table[i].src_pixel_format.fourCC) && + (pixel_format->rgbBitCount == dds_format_conversion_table[i].src_pixel_format.rgbBitCount) && + (pixel_format->rBitMask == dds_format_conversion_table[i].src_pixel_format.rBitMask) && + (pixel_format->gBitMask == dds_format_conversion_table[i].src_pixel_format.gBitMask) && + (pixel_format->bBitMask == dds_format_conversion_table[i].src_pixel_format.bBitMask) && + (pixel_format->aBitMask == dds_format_conversion_table[i].src_pixel_format.aBitMask)) + return dds_format_conversion_table + i; + } + + return NULL; +} + +static void dds_conversion_check(const void *data, SIZE_T size, const void **ret_data, SIZE_T *ret_size) +{ + unsigned int src_tex_bytes_per_pixel, dst_tex_bytes_per_pixel, src_tex_stride, dst_tex_stride, i, x; + const struct dds_format_conversion *dds_format_conversion; + SIZE_T src_tex_data_size, dst_tex_data_size; + const DDS_HEADER *dds_header; + const BYTE *src_data = data; + DDS_HEADER *dds_header_new; + BYTE *buf; + + *ret_data = data; + *ret_size = size; + if (!data || (size < DDS_HEADER_OFFSET) || (GET_MAGIC(data) != DDS_MAGIC)) + return; + + dds_header = (const DDS_HEADER *)(src_data + DDS_HEADER_OFFSET); + if (!(dds_format_conversion = get_dds_format_conversion(&dds_header->ddspf))) + return; + + TRACE("Doing DDS conversion.\n"); + + src_tex_data_size = size - ((src_data + DDS_TEXTURE_DATA_OFFSET) - src_data); + src_tex_bytes_per_pixel = dds_header->ddspf.rgbBitCount / 8; + src_tex_stride = dds_header->pitchOrLinearSize; + if (!src_tex_stride) + src_tex_stride = dds_header->width * src_tex_bytes_per_pixel; + + dst_tex_bytes_per_pixel = dds_format_conversion->dst_pixel_format.rgbBitCount / 8; + dst_tex_stride = (src_tex_stride / src_tex_bytes_per_pixel) * dst_tex_bytes_per_pixel; + dst_tex_data_size = (src_tex_data_size / src_tex_bytes_per_pixel) * dst_tex_bytes_per_pixel; + + buf = malloc(DDS_TEXTURE_DATA_OFFSET + dst_tex_data_size); + memcpy(buf, data, sizeof(DWORD)); /* Copy magic value. */ + + dds_header_new = (DDS_HEADER *)(buf + DDS_HEADER_OFFSET); + *dds_header_new = *dds_header; + dds_header_new->ddspf = dds_format_conversion->dst_pixel_format; + dds_header_new->pitchOrLinearSize = dst_tex_stride; + + for (i = 0; i < dds_header->height; i++) + { + const BYTE *src_tex_data = (src_data + DDS_TEXTURE_DATA_OFFSET) + (i * src_tex_stride); + BYTE *dst_tex_data = (buf + DDS_TEXTURE_DATA_OFFSET) + (i * dst_tex_stride); + + for (x = 0; x < dds_header->width; x++) + { + GET_R8G8B8A8_R(dst_tex_data) = GET_B4G4R4A4_R(src_tex_data) * 17; + GET_R8G8B8A8_G(dst_tex_data) = GET_B4G4R4A4_G(src_tex_data) * 17; + GET_R8G8B8A8_B(dst_tex_data) = GET_B4G4R4A4_B(src_tex_data) * 17; + GET_R8G8B8A8_A(dst_tex_data) = GET_B4G4R4A4_A(src_tex_data) * 17; + src_tex_data += src_tex_bytes_per_pixel; + dst_tex_data += dst_tex_bytes_per_pixel; + } + } + + *ret_data = buf; + *ret_size = dst_tex_data_size + DDS_TEXTURE_DATA_OFFSET; +} + HRESULT WINAPI D3DX10GetImageInfoFromFileA(const char *src_file, ID3DX10ThreadPump *pump, D3DX10_IMAGE_INFO *info, HRESULT *result) { @@ -819,7 +951,7 @@ static HRESULT convert_image(IWICImagingFactory *factory, IWICBitmapFrameDecode return hr; }
-HRESULT load_texture_data(const void *data, SIZE_T size, D3DX10_IMAGE_LOAD_INFO *load_info, +HRESULT load_texture_data(const void *src_data, SIZE_T src_size, D3DX10_IMAGE_LOAD_INFO *load_info, D3D10_SUBRESOURCE_DATA **resource_data) { unsigned int stride, frame_size, i, j; @@ -832,8 +964,12 @@ HRESULT load_texture_data(const void *data, SIZE_T size, D3DX10_IMAGE_LOAD_INFO D3DX10_IMAGE_INFO img_info; IWICStream *stream = NULL; const GUID *dst_format; + void *data = NULL; + SIZE_T size = 0; HRESULT hr;
+ dds_conversion_check(src_data, src_size, (const void **)&data, &size); + if (load_info->Width != D3DX10_DEFAULT) FIXME("load_info->Width is ignored.\n"); if (load_info->Height != D3DX10_DEFAULT) @@ -1021,6 +1157,8 @@ end: IWICStream_Release(stream); if (factory) IWICImagingFactory_Release(factory); + if (src_data != data) + free(data); return hr; }
I don't want to get in the way... but I think this not the way to go. Mostly because using WIC to load generic DDS files is not what native does, or what our d3dx9 does. So instead of doubling down on our current questionable design, we might want to take the occasion to fix it.
Ideally we should probably reuse code from d3dx9_36/surface.c:load_surface_from_dds() / D3DXLoadSurfaceFromMemory() (e.g. via PARENTSRC) and eventually phase out d3dx10 WIC DDS loading code. One problem is that these functions are currently tied to d3d[x]9 types (IDirect3DSurface9 most notably) so they can't really be used from d3dx10 in their current form. I'd move the meat of D3DXLoadSurfaceFromMemory() to a helper function (which is probably not a terrible idea regardless), eschewing d3d9-isms from it, and call this new helper from d3dx10.
I haven't explored this idea thoroughly, so there might certainly be issues I'm overlooking. Hopefully I'll be able to have a better look soon and at least sketch something, if you don't feel like taking it head-on (which would be a perfectly reasonable reaction :slight_smile:)
On Wed Feb 7 15:12:32 2024 +0000, Matteo Bruni wrote:
I don't want to get in the way... but I think this not the way to go. Mostly because using WIC to load generic DDS files is not what native does, or what our d3dx9 does. So instead of doubling down on our current questionable design, we might want to take the occasion to fix it. Ideally we should probably reuse code from d3dx9_36/surface.c:load_surface_from_dds() / D3DXLoadSurfaceFromMemory() (e.g. via PARENTSRC) and eventually phase out d3dx10 WIC DDS loading code. One problem is that these functions are currently tied to d3d[x]9 types (IDirect3DSurface9 most notably) so they can't really be used from d3dx10 in their current form. I'd move the meat of D3DXLoadSurfaceFromMemory() to a helper function (which is probably not a terrible idea regardless), eschewing d3d9-isms from it, and call this new helper from d3dx10. I haven't explored this idea thoroughly, so there might certainly be issues I'm overlooking. Hopefully I'll be able to have a better look soon and at least sketch something, if you don't feel like taking it head-on (which would be a perfectly reasonable reaction :slight_smile:)
That's fair, I had read a comment somewhere where you had mentioned wanting to get away from using WIC which is why I didn't fully flesh this out.
I will explore the idea you're describing and see what I can do, thanks for the feedback. :)
On Fri Mar 1 14:39:20 2024 +0000, Connor McAdams wrote:
That's fair, I had read a comment somewhere where you had mentioned wanting to get away from using WIC which is why I didn't fully flesh this out. I will explore the idea you're describing and see what I can do, thanks for the feedback. :)
Going to go ahead and close this MR now that I've got !5202 open.
This merge request was closed by Connor McAdams.