From 2bfee809f0b64fd744753993f51979d008859318 Mon Sep 17 00:00:00 2001 From: Matt Borgerson Date: Thu, 1 Aug 2024 17:41:52 -0700 Subject: [PATCH] nv2a/vk: Add pvideo support --- hw/xbox/nv2a/pgraph/vk/display.c | 411 ++++++++++++++++++++++-------- hw/xbox/nv2a/pgraph/vk/renderer.h | 35 +++ 2 files changed, 335 insertions(+), 111 deletions(-) diff --git a/hw/xbox/nv2a/pgraph/vk/display.c b/hw/xbox/nv2a/pgraph/vk/display.c index c839cc79394..93caeff8b24 100644 --- a/hw/xbox/nv2a/pgraph/vk/display.c +++ b/hw/xbox/nv2a/pgraph/vk/display.c @@ -19,40 +19,220 @@ #include "renderer.h" +static uint8_t *convert_texture_data__CR8YB8CB8YA8(uint8_t *data_out, + const uint8_t *data_in, + unsigned int width, + unsigned int height, + unsigned int pitch) +{ + int x, y; + for (y = 0; y < height; y++) { + const uint8_t *line = &data_in[y * pitch]; + const uint32_t row_offset = y * width; + for (x = 0; x < width; x++) { + uint8_t *pixel = &data_out[(row_offset + x) * 4]; + convert_yuy2_to_rgb(line, x, &pixel[0], &pixel[1], &pixel[2]); + pixel[3] = 255; + } + } + return data_out; +} + +static float pvideo_calculate_scale(unsigned int din_dout, + unsigned int output_size) +{ + float calculated_in = din_dout * (output_size - 1); + calculated_in = floorf(calculated_in / (1 << 20) + 0.5f); + return (calculated_in + 1.0f) / output_size; +} + +static void destroy_pvideo_image(PGRAPHState *pg) +{ + PGRAPHVkState *r = pg->vk_renderer_state; + PGRAPHVkDisplayState *d = &r->display; + + if (d->pvideo.sampler != VK_NULL_HANDLE) { + vkDestroySampler(r->device, d->pvideo.sampler, NULL); + d->pvideo.sampler = VK_NULL_HANDLE; + } + + if (d->pvideo.image_view != VK_NULL_HANDLE) { + vkDestroyImageView(r->device, d->pvideo.image_view, NULL); + d->pvideo.image_view = VK_NULL_HANDLE; + } + + if (d->pvideo.image != VK_NULL_HANDLE) { + vmaDestroyImage(r->allocator, d->pvideo.image, d->pvideo.allocation); + d->pvideo.image = VK_NULL_HANDLE; + d->pvideo.allocation = VK_NULL_HANDLE; + } +} + +static void create_pvideo_image(PGRAPHState *pg, int width, int height) +{ + PGRAPHVkState *r = pg->vk_renderer_state; + PGRAPHVkDisplayState *d = &r->display; + + if (d->pvideo.image == VK_NULL_HANDLE || d->pvideo.width != width || + d->pvideo.height != height) { + destroy_pvideo_image(pg); + } + + VkImageCreateInfo image_create_info = { + .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, + .imageType = VK_IMAGE_TYPE_2D, + .extent.width = width, + .extent.height = height, + .extent.depth = 1, + .mipLevels = 1, + .arrayLayers = 1, + .format = VK_FORMAT_R8G8B8A8_UNORM, + .tiling = VK_IMAGE_TILING_OPTIMAL, + .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, + .usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, + .samples = VK_SAMPLE_COUNT_1_BIT, + .sharingMode = VK_SHARING_MODE_EXCLUSIVE, + .flags = 0, + }; + VmaAllocationCreateInfo alloc_create_info = { + .usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE, + }; + VK_CHECK(vmaCreateImage(r->allocator, &image_create_info, + &alloc_create_info, &d->pvideo.image, + &d->pvideo.allocation, NULL)); + + VkImageViewCreateInfo image_view_create_info = { + .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, + .image = d->pvideo.image, + .viewType = VK_IMAGE_VIEW_TYPE_2D, + .format = VK_FORMAT_R8G8B8A8_UNORM, + .subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .subresourceRange.baseMipLevel = 0, + .subresourceRange.levelCount = image_create_info.mipLevels, + .subresourceRange.baseArrayLayer = 0, + .subresourceRange.layerCount = image_create_info.arrayLayers, + }; + VK_CHECK(vkCreateImageView(r->device, &image_view_create_info, NULL, + &d->pvideo.image_view)); + + VkSamplerCreateInfo sampler_create_info = { + .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, + .magFilter = VK_FILTER_LINEAR, + .minFilter = VK_FILTER_NEAREST, + .addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT, + .addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT, + .addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT, + .borderColor = VK_BORDER_COLOR_INT_OPAQUE_WHITE, + .mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST, + }; + VK_CHECK(vkCreateSampler(r->device, &sampler_create_info, NULL, + &d->pvideo.sampler)); +} + +static void upload_pvideo_image(PGRAPHState *pg, PvideoState state) +{ + NV2AState *d = container_of(pg, NV2AState, pgraph); + PGRAPHVkState *r = pg->vk_renderer_state; + PGRAPHVkDisplayState *disp = &r->display; + + create_pvideo_image(pg, state.in_width, state.in_height); + + // FIXME: Dirty tracking. We don't necessarily need to upload so much. + + // Copy texture data to mapped device buffer + uint8_t *mapped_memory_ptr; + + VK_CHECK(vmaMapMemory(r->allocator, + r->storage_buffers[BUFFER_STAGING_SRC].allocation, + (void *)&mapped_memory_ptr)); + + convert_texture_data__CR8YB8CB8YA8( + mapped_memory_ptr, d->vram_ptr + state.base + state.offset, + state.in_width, state.in_height, state.pitch); + + vmaFlushAllocation(r->allocator, + r->storage_buffers[BUFFER_STAGING_SRC].allocation, 0, + VK_WHOLE_SIZE); + + vmaUnmapMemory(r->allocator, + r->storage_buffers[BUFFER_STAGING_SRC].allocation); + + // FIXME: Merge with display renderer command buffer + + VkCommandBuffer cmd = pgraph_vk_begin_single_time_commands(pg); + + VkBufferMemoryBarrier host_barrier = { + .sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER, + .srcAccessMask = VK_ACCESS_HOST_WRITE_BIT, + .dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .buffer = r->storage_buffers[BUFFER_STAGING_SRC].buffer, + .size = VK_WHOLE_SIZE + }; + vkCmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_HOST_BIT, + VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, NULL, 1, + &host_barrier, 0, NULL); + + pgraph_vk_transition_image_layout( + pg, cmd, disp->pvideo.image, VK_FORMAT_R8_UNORM, + VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); + + VkBufferImageCopy region = { + .bufferOffset = 0, + .bufferRowLength = 0, + .bufferImageHeight = 0, + .imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .imageSubresource.mipLevel = 0, + .imageSubresource.baseArrayLayer = 0, + .imageSubresource.layerCount = 1, + .imageOffset = (VkOffset3D){ 0, 0, 0 }, + .imageExtent = (VkExtent3D){ state.in_width, state.in_height, 1 }, + }; + vkCmdCopyBufferToImage(cmd, r->storage_buffers[BUFFER_STAGING_SRC].buffer, + disp->pvideo.image, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion); + + pgraph_vk_transition_image_layout(pg, cmd, disp->pvideo.image, + VK_FORMAT_R8G8B8A8_UNORM, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); + pgraph_vk_end_single_time_commands(pg, cmd); +} + static const char *display_frag_glsl = "#version 450\n" "layout(binding = 0) uniform sampler2D tex;\n" "layout(binding = 1) uniform sampler2D pvideo_tex;\n" "layout(push_constant, std430) uniform PushConstants {\n" + " float line_offset;\n" + " vec2 display_size;\n" " bool pvideo_enable;\n" " vec2 pvideo_in_pos;\n" " vec4 pvideo_pos;\n" - " vec3 pvideo_scale;\n" + " vec4 pvideo_scale;\n" " bool pvideo_color_key_enable;\n" - " vec2 display_size;\n" - " float line_offset;\n" " vec4 pvideo_color_key;\n" "};\n" "layout(location = 0) out vec4 out_Color;\n" "void main()\n" "{\n" - " vec2 texCoord = gl_FragCoord.xy/display_size;\n" - " texCoord.y = 1 - texCoord.y;\n" // GL compat + " vec2 tex_coord = gl_FragCoord.xy/display_size;\n" + " tex_coord.y = 1 - tex_coord.y;\n" // GL compat " float rel = display_size.y/textureSize(tex, 0).y/line_offset;\n" - " texCoord.y = 1 + rel*(texCoord.y - 1);" - " out_Color.rgba = texture(tex, texCoord);\n" - // " if (pvideo_enable) {\n" - // " vec2 screenCoord = gl_FragCoord.xy - 0.5;\n" - // " vec4 output_region = vec4(pvideo_pos.xy, pvideo_pos.xy + pvideo_pos.zw);\n" - // " bvec4 clip = bvec4(lessThan(screenCoord, output_region.xy),\n" - // " greaterThan(screenCoord, output_region.zw));\n" - // " if (!any(clip) && (!pvideo_color_key_enable || out_Color.rgba == pvideo_color_key)) {\n" - // " vec2 out_xy = (screenCoord - pvideo_pos.xy) * pvideo_scale.z;\n" - // " vec2 in_st = (pvideo_in_pos + out_xy * pvideo_scale.xy) / textureSize(pvideo_tex, 0);\n" - // " in_st.y *= -1.0;\n" - // " out_Color.rgba = texture(pvideo_tex, in_st);\n" - // " }\n" - // " }\n" + " tex_coord.y = 1 + rel*(tex_coord.y - 1);" + " out_Color.rgba = texture(tex, tex_coord);\n" + " if (pvideo_enable) {\n" + " vec2 screen_coord = vec2(gl_FragCoord.x, display_size.y - gl_FragCoord.y) * pvideo_scale.z;\n" + " vec4 output_region = vec4(pvideo_pos.xy, pvideo_pos.xy + pvideo_pos.zw);\n" + " bvec4 clip = bvec4(lessThan(screen_coord, output_region.xy),\n" + " greaterThan(screen_coord, output_region.zw));\n" + " if (!any(clip) && (!pvideo_color_key_enable || out_Color.rgba == pvideo_color_key)) {\n" + " vec2 out_xy = screen_coord - pvideo_pos.xy;\n" + " vec2 in_st = (pvideo_in_pos + out_xy * pvideo_scale.xy) / textureSize(pvideo_tex, 0);\n" + " out_Color.rgba = texture(pvideo_tex, in_st);\n" + " }\n" + " }\n" "}\n"; static void create_descriptor_pool(PGRAPHState *pg) @@ -516,7 +696,7 @@ static void create_display_image(PGRAPHState *pg, int width, int height) assert(glGetError() == GL_NO_ERROR); #endif // WIN32 - + glGenTextures(1, &d->gl_texture_id); glBindTexture(GL_TEXTURE_2D, d->gl_texture_id); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); @@ -561,11 +741,21 @@ static void update_descriptor_set(PGRAPHState *pg, SurfaceBinding *surface) }; // FIXME: PVIDEO Overlay - image_infos[1] = (VkDescriptorImageInfo){ - .imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, - .imageView = r->dummy_texture.image_view, - .sampler = r->dummy_texture.sampler, - }; + if (r->display.pvideo.state.enabled) { + assert(r->display.pvideo.image_view != VK_NULL_HANDLE); + assert(r->display.pvideo.sampler != VK_NULL_HANDLE); + image_infos[1] = (VkDescriptorImageInfo){ + .imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + .imageView = r->display.pvideo.image_view, + .sampler = r->display.pvideo.sampler, + }; + } else { + image_infos[1] = (VkDescriptorImageInfo){ + .imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + .imageView = r->dummy_texture.image_view, + .sampler = r->dummy_texture.sampler, + }; + } descriptor_writes[1] = (VkWriteDescriptorSet){ .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, .dstSet = r->display.descriptor_set, @@ -580,22 +770,11 @@ static void update_descriptor_set(PGRAPHState *pg, SurfaceBinding *surface) descriptor_writes, 0, NULL); } -static void update_uniforms(PGRAPHState *pg, SurfaceBinding *surface) +static PvideoState get_pvideo_state(PGRAPHState *pg) { NV2AState *d = container_of(pg, NV2AState, pgraph); - PGRAPHVkState *r = pg->vk_renderer_state; - ShaderUniformLayout *l = &r->display.display_frag->push_constants; - - int display_size_loc = uniform_index(l, "display_size"); // FIXME: Cache - uniform2f(l, display_size_loc, r->display.width, r->display.height); - - uint32_t pline_offset, pstart_addr, pline_compare; - d->vga.get_offsets(&d->vga, &pline_offset, &pstart_addr, &pline_compare); - int line_offset = surface->pitch / pline_offset; - int line_offset_loc = uniform_index(l, "line_offset"); - uniform1f(l, line_offset_loc, line_offset); + PvideoState state; -#if 0 // FIXME: PVIDEO overlay // FIXME: This check against PVIDEO_SIZE_IN does not match HW behavior. // Many games seem to pass this value when initializing or tearing down // PVIDEO. On its own, this generally does not result in the overlay being @@ -605,109 +784,112 @@ static void update_uniforms(PGRAPHState *pg, SurfaceBinding *surface) // Since the value seems to be set to 0xFFFFFFFF only in cases where the // content is not valid, it is probably good enough to treat it as an // implicit stop. - bool enabled = (d->pvideo.regs[NV_PVIDEO_BUFFER] & NV_PVIDEO_BUFFER_0_USE) + state.enabled = (d->pvideo.regs[NV_PVIDEO_BUFFER] & NV_PVIDEO_BUFFER_0_USE) && d->pvideo.regs[NV_PVIDEO_SIZE_IN] != 0xFFFFFFFF; - glUniform1ui(d->pgraph.renderer_state->disp_rndr.pvideo_enable_loc, enabled); - if (!enabled) { - return; + if (!state.enabled) { + return state; } - hwaddr base = d->pvideo.regs[NV_PVIDEO_BASE]; - hwaddr limit = d->pvideo.regs[NV_PVIDEO_LIMIT]; - hwaddr offset = d->pvideo.regs[NV_PVIDEO_OFFSET]; + state.base = d->pvideo.regs[NV_PVIDEO_BASE]; + state.limit = d->pvideo.regs[NV_PVIDEO_LIMIT]; + state.offset = d->pvideo.regs[NV_PVIDEO_OFFSET]; - int in_width = - GET_MASK(d->pvideo.regs[NV_PVIDEO_SIZE_IN], NV_PVIDEO_SIZE_IN_WIDTH); - int in_height = - GET_MASK(d->pvideo.regs[NV_PVIDEO_SIZE_IN], NV_PVIDEO_SIZE_IN_HEIGHT); - - int in_s = GET_MASK(d->pvideo.regs[NV_PVIDEO_POINT_IN], - NV_PVIDEO_POINT_IN_S); - int in_t = GET_MASK(d->pvideo.regs[NV_PVIDEO_POINT_IN], - NV_PVIDEO_POINT_IN_T); - - int in_pitch = + state.pitch = GET_MASK(d->pvideo.regs[NV_PVIDEO_FORMAT], NV_PVIDEO_FORMAT_PITCH); - int in_color = + state.format = GET_MASK(d->pvideo.regs[NV_PVIDEO_FORMAT], NV_PVIDEO_FORMAT_COLOR); - unsigned int out_width = + /* TODO: support other color formats */ + assert(state.format == NV_PVIDEO_FORMAT_COLOR_LE_CR8YB8CB8YA8); + + state.in_width = + GET_MASK(d->pvideo.regs[NV_PVIDEO_SIZE_IN], NV_PVIDEO_SIZE_IN_WIDTH); + state.in_height = + GET_MASK(d->pvideo.regs[NV_PVIDEO_SIZE_IN], NV_PVIDEO_SIZE_IN_HEIGHT); + + state.out_width = GET_MASK(d->pvideo.regs[NV_PVIDEO_SIZE_OUT], NV_PVIDEO_SIZE_OUT_WIDTH); - unsigned int out_height = + state.out_height = GET_MASK(d->pvideo.regs[NV_PVIDEO_SIZE_OUT], NV_PVIDEO_SIZE_OUT_HEIGHT); - float scale_x = 1.0f; - float scale_y = 1.0f; - unsigned int ds_dx = d->pvideo.regs[NV_PVIDEO_DS_DX]; - unsigned int dt_dy = d->pvideo.regs[NV_PVIDEO_DT_DY]; - if (ds_dx != NV_PVIDEO_DIN_DOUT_UNITY) { - scale_x = pvideo_calculate_scale(ds_dx, out_width); - } - if (dt_dy != NV_PVIDEO_DIN_DOUT_UNITY) { - scale_y = pvideo_calculate_scale(dt_dy, out_height); - } + state.in_s = GET_MASK(d->pvideo.regs[NV_PVIDEO_POINT_IN], + NV_PVIDEO_POINT_IN_S); + state.in_t = GET_MASK(d->pvideo.regs[NV_PVIDEO_POINT_IN], + NV_PVIDEO_POINT_IN_T); + + uint32_t ds_dx = d->pvideo.regs[NV_PVIDEO_DS_DX]; + uint32_t dt_dy = d->pvideo.regs[NV_PVIDEO_DT_DY]; + state.scale_x = ds_dx == NV_PVIDEO_DIN_DOUT_UNITY ? + 1.0f : + pvideo_calculate_scale(ds_dx, state.out_width); + state.scale_y = dt_dy == NV_PVIDEO_DIN_DOUT_UNITY ? + 1.0f : + pvideo_calculate_scale(dt_dy, state.out_height); // On HW, setting NV_PVIDEO_SIZE_IN larger than NV_PVIDEO_SIZE_OUT results // in them being capped to the output size, content is not scaled. This is // particularly important as NV_PVIDEO_SIZE_IN may be set to 0xFFFFFFFF // during initialization or teardown. - if (in_width > out_width) { - in_width = floorf((float)out_width * scale_x + 0.5f); + if (state.in_width > state.out_width) { + state.in_width = floorf((float)state.out_width * state.scale_x + 0.5f); } - if (in_height > out_height) { - in_height = floorf((float)out_height * scale_y + 0.5f); + if (state.in_height > state.out_height) { + state.in_height = floorf((float)state.out_height * state.scale_y + 0.5f); } - /* TODO: support other color formats */ - assert(in_color == NV_PVIDEO_FORMAT_COLOR_LE_CR8YB8CB8YA8); - - unsigned int out_x = + state.out_x = GET_MASK(d->pvideo.regs[NV_PVIDEO_POINT_OUT], NV_PVIDEO_POINT_OUT_X); - unsigned int out_y = + state.out_y = GET_MASK(d->pvideo.regs[NV_PVIDEO_POINT_OUT], NV_PVIDEO_POINT_OUT_Y); - unsigned int color_key_enabled = + state.color_key_enabled = GET_MASK(d->pvideo.regs[NV_PVIDEO_FORMAT], NV_PVIDEO_FORMAT_DISPLAY); - glUniform1ui(d->pgraph.renderer_state->disp_rndr.pvideo_color_key_enable_loc, - color_key_enabled); // TODO: Verify that masking off the top byte is correct. // SeaBlade sets a color key of 0x80000000 but the texture passed into the // shader is cleared to 0 alpha. - unsigned int color_key = d->pvideo.regs[NV_PVIDEO_COLOR_KEY] & 0xFFFFFF; - glUniform4f(d->pgraph.renderer_state->disp_rndr.pvideo_color_key_loc, - GET_MASK(color_key, NV_PVIDEO_COLOR_KEY_RED) / 255.0, - GET_MASK(color_key, NV_PVIDEO_COLOR_KEY_GREEN) / 255.0, - GET_MASK(color_key, NV_PVIDEO_COLOR_KEY_BLUE) / 255.0, - GET_MASK(color_key, NV_PVIDEO_COLOR_KEY_ALPHA) / 255.0); - - assert(offset + in_pitch * in_height <= limit); - hwaddr end = base + offset + in_pitch * in_height; + state.color_key = d->pvideo.regs[NV_PVIDEO_COLOR_KEY] & 0xFFFFFF; + + assert(state.offset + state.pitch * state.in_height <= state.limit); + hwaddr end = state.base + state.offset + state.pitch * state.in_height; assert(end <= memory_region_size(d->vram)); - pgraph_apply_scaling_factor(pg, &out_x, &out_y); - pgraph_apply_scaling_factor(pg, &out_width, &out_height); + return state; +} + +static void update_uniforms(PGRAPHState *pg, SurfaceBinding *surface) +{ + NV2AState *d = container_of(pg, NV2AState, pgraph); + PGRAPHVkState *r = pg->vk_renderer_state; + ShaderUniformLayout *l = &r->display.display_frag->push_constants; + + int display_size_loc = uniform_index(l, "display_size"); // FIXME: Cache + uniform2f(l, display_size_loc, r->display.width, r->display.height); - // Translate for the GL viewport origin. - out_y = MAX(pg->renderer_state->gl_display_buffer_height - 1 - (int)(out_y + out_height), 0); + uint32_t pline_offset, pstart_addr, pline_compare; + d->vga.get_offsets(&d->vga, &pline_offset, &pstart_addr, &pline_compare); + int line_offset = surface->pitch / pline_offset; + int line_offset_loc = uniform_index(l, "line_offset"); + uniform1f(l, line_offset_loc, line_offset); - glActiveTexture(GL_TEXTURE0 + 1); - glBindTexture(GL_TEXTURE_2D, d->pgraph.renderer_state->disp_rndr.pvideo_tex); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - uint8_t *tex_rgba = convert_texture_data__CR8YB8CB8YA8( - d->vram_ptr + base + offset, in_width, in_height, in_pitch); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, in_width, in_height, 0, GL_RGBA, - GL_UNSIGNED_BYTE, tex_rgba); - g_free(tex_rgba); - glUniform1i(d->pgraph.renderer_state->disp_rndr.pvideo_tex_loc, 1); - glUniform2f(d->pgraph.renderer_state->disp_rndr.pvideo_in_pos_loc, in_s, in_t); - glUniform4f(d->pgraph.renderer_state->disp_rndr.pvideo_pos_loc, - out_x, out_y, out_width, out_height); - glUniform3f(d->pgraph.renderer_state->disp_rndr.pvideo_scale_loc, - scale_x, scale_y, 1.0f / pg->surface_scale_factor); -#endif + PvideoState *pvideo = &r->display.pvideo.state; + uniform1i(l, uniform_index(l, "pvideo_enable"), pvideo->enabled); + if (pvideo->enabled) { + uniform1i(l, uniform_index(l, "pvideo_color_key_enable"), + pvideo->color_key_enabled); + uniform4f( + l, uniform_index(l, "pvideo_color_key"), + GET_MASK(pvideo->color_key, NV_PVIDEO_COLOR_KEY_RED) / 255.0, + GET_MASK(pvideo->color_key, NV_PVIDEO_COLOR_KEY_GREEN) / 255.0, + GET_MASK(pvideo->color_key, NV_PVIDEO_COLOR_KEY_BLUE) / 255.0, + GET_MASK(pvideo->color_key, NV_PVIDEO_COLOR_KEY_ALPHA) / 255.0); + uniform2f(l, uniform_index(l, "pvideo_in_pos"), pvideo->in_s, + pvideo->in_t); + uniform4f(l, uniform_index(l, "pvideo_pos"), pvideo->out_x, + pvideo->out_y, pvideo->out_width, pvideo->out_height); + uniform4f(l, uniform_index(l, "pvideo_scale"), pvideo->scale_x, + pvideo->scale_y, 1.0f / pg->surface_scale_factor, 1.0); + } } static void render_display(PGRAPHState *pg, SurfaceBinding *surface) @@ -724,6 +906,11 @@ static void render_display(PGRAPHState *pg, SurfaceBinding *surface) pgraph_vk_finish(pg, VK_FINISH_REASON_PRESENTING); } + disp->pvideo.state = get_pvideo_state(pg); + if (disp->pvideo.state.enabled) { + upload_pvideo_image(pg, disp->pvideo.state); + } + update_uniforms(pg, surface); update_descriptor_set(pg, surface); @@ -854,6 +1041,8 @@ void pgraph_vk_finalize_display(PGRAPHState *pg) { PGRAPHVkState *r = pg->vk_renderer_state; + destroy_pvideo_image(pg); + if (r->display.image != VK_NULL_HANDLE) { destroy_current_display_image(pg); } diff --git a/hw/xbox/nv2a/pgraph/vk/renderer.h b/hw/xbox/nv2a/pgraph/vk/renderer.h index 7baa49acb0b..8a6b5090aa4 100644 --- a/hw/xbox/nv2a/pgraph/vk/renderer.h +++ b/hw/xbox/nv2a/pgraph/vk/renderer.h @@ -229,6 +229,32 @@ typedef struct QueryReport { unsigned int query_count; } QueryReport; +typedef struct PvideoState { + bool enabled; + hwaddr base; + hwaddr limit; + hwaddr offset; + + int pitch; + int format; + + int in_width; + int in_height; + int out_width; + int out_height; + + int in_s; + int in_t; + int out_x; + int out_y; + + float scale_x; + float scale_y; + + bool color_key_enabled; + uint32_t color_key; +} PvideoState; + typedef struct PGRAPHVkDisplayState { ShaderModuleInfo *display_frag; @@ -247,6 +273,15 @@ typedef struct PGRAPHVkDisplayState { VkDeviceMemory memory; VkSampler sampler; + struct { + PvideoState state; + int width, height; + VkImage image; + VkImageView image_view; + VmaAllocation allocation; + VkSampler sampler; + } pvideo; + int width, height; int draw_time;