Port Android Vulkan color surface conversion before upload

Adds host-side conversion for color surface formats that Adreno GPUs
cannot render to natively (e.g. X1R5G5B5, X8R8G8B8, A8R8G8B8),
converting from the Xbox surface layout to the Vulkan host format
used as a fallback render target on Android.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
izzy2lost
2026-04-08 02:35:00 -04:00
co-authored by Claude Sonnet 4.6
parent 81d6b5bd92
commit 9fdec7beba
+177 -3
View File
@@ -147,6 +147,160 @@ void pgraph_vk_download_surfaces_in_range_if_dirty(PGRAPHState *pg,
}
}
#ifdef __ANDROID__
static uint8_t android_vk_surface_expand_5_to_8(uint8_t value)
{
return (value << 3) | (value >> 2);
}
static bool android_vk_surface_uses_host_conversion(const SurfaceBinding *surface)
{
if (!surface || !surface->color) {
return false;
}
switch (surface->shape.color_format) {
case NV097_SET_SURFACE_FORMAT_COLOR_LE_X1R5G5B5_Z1R5G5B5:
case NV097_SET_SURFACE_FORMAT_COLOR_LE_X1R5G5B5_O1R5G5B5:
case NV097_SET_SURFACE_FORMAT_COLOR_LE_X8R8G8B8_Z8R8G8B8:
case NV097_SET_SURFACE_FORMAT_COLOR_LE_X8R8G8B8_O8R8G8B8:
case NV097_SET_SURFACE_FORMAT_COLOR_LE_A8R8G8B8:
return true;
default:
return false;
}
}
static void android_vk_surface_convert_to_host(const SurfaceBinding *surface,
const uint8_t *src,
unsigned int width,
unsigned int height,
unsigned int src_stride,
uint8_t *dst,
unsigned int dst_stride)
{
unsigned int x, y;
switch (surface->shape.color_format) {
case NV097_SET_SURFACE_FORMAT_COLOR_LE_X1R5G5B5_Z1R5G5B5:
case NV097_SET_SURFACE_FORMAT_COLOR_LE_X1R5G5B5_O1R5G5B5:
for (y = 0; y < height; y++) {
const uint8_t *src_row = src + y * src_stride;
uint8_t *dst_row = dst + y * dst_stride;
switch (surface->host_fmt.vk_format) {
case VK_FORMAT_A1R5G5B5_UNORM_PACK16:
for (x = 0; x < width; x++) {
uint16_t pixel = lduw_le_p(src_row + x * 2) | 0x8000;
stw_le_p(dst_row + x * 2, pixel);
}
break;
case VK_FORMAT_R5G6B5_UNORM_PACK16:
for (x = 0; x < width; x++) {
uint16_t pixel = lduw_le_p(src_row + x * 2);
uint16_t r5 = (pixel >> 10) & 0x1F;
uint16_t g5 = (pixel >> 5) & 0x1F;
uint16_t b5 = pixel & 0x1F;
uint16_t packed =
(r5 << 11) | (((g5 << 1) | (g5 >> 4)) << 5) | b5;
stw_le_p(dst_row + x * 2, packed);
}
break;
case VK_FORMAT_B8G8R8A8_UNORM:
case VK_FORMAT_R8G8B8A8_UNORM:
for (x = 0; x < width; x++) {
uint16_t pixel = lduw_le_p(src_row + x * 2);
uint8_t r = android_vk_surface_expand_5_to_8((pixel >> 10) & 0x1F);
uint8_t g = android_vk_surface_expand_5_to_8((pixel >> 5) & 0x1F);
uint8_t b = android_vk_surface_expand_5_to_8(pixel & 0x1F);
if (surface->host_fmt.vk_format == VK_FORMAT_B8G8R8A8_UNORM) {
dst_row[x * 4 + 0] = b;
dst_row[x * 4 + 1] = g;
dst_row[x * 4 + 2] = r;
dst_row[x * 4 + 3] = 0xFF;
} else {
dst_row[x * 4 + 0] = r;
dst_row[x * 4 + 1] = g;
dst_row[x * 4 + 2] = b;
dst_row[x * 4 + 3] = 0xFF;
}
}
break;
default:
g_assert_not_reached();
}
}
break;
case NV097_SET_SURFACE_FORMAT_COLOR_LE_X8R8G8B8_Z8R8G8B8:
case NV097_SET_SURFACE_FORMAT_COLOR_LE_X8R8G8B8_O8R8G8B8: {
bool preserve_alpha = false;
for (y = 0; y < height; y++) {
const uint8_t *src_row = src + y * src_stride;
uint8_t *dst_row = dst + y * dst_stride;
switch (surface->host_fmt.vk_format) {
case VK_FORMAT_B8G8R8A8_UNORM:
for (x = 0; x < width; x++) {
dst_row[x * 4 + 0] = src_row[x * 4 + 0];
dst_row[x * 4 + 1] = src_row[x * 4 + 1];
dst_row[x * 4 + 2] = src_row[x * 4 + 2];
dst_row[x * 4 + 3] = preserve_alpha ? src_row[x * 4 + 3] : 0xFF;
}
break;
case VK_FORMAT_R8G8B8A8_UNORM:
for (x = 0; x < width; x++) {
const uint8_t *pixel = src_row + x * 4;
uint8_t *out = dst_row + x * 4;
out[0] = pixel[2];
out[1] = pixel[1];
out[2] = pixel[0];
out[3] = preserve_alpha ? pixel[3] : 0xFF;
}
break;
default:
g_assert_not_reached();
}
}
break;
}
case NV097_SET_SURFACE_FORMAT_COLOR_LE_A8R8G8B8: {
bool preserve_alpha = true;
for (y = 0; y < height; y++) {
const uint8_t *src_row = src + y * src_stride;
uint8_t *dst_row = dst + y * dst_stride;
switch (surface->host_fmt.vk_format) {
case VK_FORMAT_B8G8R8A8_UNORM:
for (x = 0; x < width; x++) {
dst_row[x * 4 + 0] = src_row[x * 4 + 0];
dst_row[x * 4 + 1] = src_row[x * 4 + 1];
dst_row[x * 4 + 2] = src_row[x * 4 + 2];
dst_row[x * 4 + 3] = preserve_alpha ? src_row[x * 4 + 3] : 0xFF;
}
break;
case VK_FORMAT_R8G8B8A8_UNORM:
for (x = 0; x < width; x++) {
const uint8_t *pixel = src_row + x * 4;
uint8_t *out = dst_row + x * 4;
out[0] = pixel[2];
out[1] = pixel[1];
out[2] = pixel[0];
out[3] = preserve_alpha ? pixel[3] : 0xFF;
}
break;
default:
g_assert_not_reached();
}
}
break;
}
default:
g_assert_not_reached();
}
}
#endif
static void download_surface_to_buffer(NV2AState *d, SurfaceBinding *surface,
uint8_t *pixels)
{
@@ -1083,7 +1237,27 @@ void pgraph_vk_upload_surface_data(NV2AState *d, SurfaceBinding *surface,
//
StorageBuffer *copy_buffer = &r->storage_buffers[BUFFER_STAGING_SRC];
unsigned int upload_dst_stride = surface->width * surface->fmt.bytes_per_pixel;
const uint8_t *upload_src = gl_read_buf;
unsigned int upload_src_stride = surface->pitch;
unsigned int upload_bytes_per_pixel = surface->fmt.bytes_per_pixel;
unsigned int upload_dst_stride = surface->width * upload_bytes_per_pixel;
#ifdef __ANDROID__
g_autofree uint8_t *converted_upload_buf = NULL;
if (android_vk_surface_uses_host_conversion(surface)) {
upload_bytes_per_pixel = surface->host_fmt.host_bytes_per_pixel;
upload_src_stride = surface->width * upload_bytes_per_pixel;
upload_dst_stride = upload_src_stride;
converted_upload_buf =
g_malloc((size_t)surface->height * upload_src_stride);
android_vk_surface_convert_to_host(surface, gl_read_buf,
surface->width, surface->height,
surface->pitch,
converted_upload_buf,
upload_src_stride);
upload_src = converted_upload_buf;
}
#endif
size_t uploaded_image_size = surface->height * upload_dst_stride;
assert(uploaded_image_size <= copy_buffer->buffer_size);
@@ -1101,8 +1275,8 @@ void pgraph_vk_upload_surface_data(NV2AState *d, SurfaceBinding *surface,
use_compute_to_convert_depth_stencil_format;
assert(no_conversion_necessary);
memcpy_image(mapped_memory_ptr, gl_read_buf, upload_dst_stride,
surface->pitch, surface->height);
memcpy_image(mapped_memory_ptr, upload_src, upload_dst_stride,
upload_src_stride, surface->height);
vmaFlushAllocation(r->allocator, copy_buffer->allocation, 0, VK_WHOLE_SIZE);
vmaUnmapMemory(r->allocator, copy_buffer->allocation);