From a65f9ea5a8438279766449191dc547fb7c99435c Mon Sep 17 00:00:00 2001 From: GPUCode Date: Thu, 22 Sep 2022 14:32:42 +0300 Subject: [PATCH] renderer_vulkan: Address more validation errors and stop memory leakage * The transition settings are temporary until I write a proper layout tracking system --- .../configuration/configure_graphics.cpp | 2 + .../rasterizer_cache/rasterizer_cache.h | 2 +- .../renderer_vulkan/renderer_vulkan.cpp | 123 +++++++---- .../renderer_vulkan/renderer_vulkan.h | 7 +- src/video_core/renderer_vulkan/vk_common.h | 4 +- .../renderer_vulkan/vk_instance.cpp | 8 +- .../renderer_vulkan/vk_pipeline_cache.cpp | 5 +- .../renderer_vulkan/vk_rasterizer.cpp | 205 ++++++++---------- .../renderer_vulkan/vk_renderpass_cache.cpp | 48 ++-- .../renderer_vulkan/vk_renderpass_cache.h | 8 +- .../renderer_vulkan/vk_shader_gen.cpp | 1 + .../renderer_vulkan/vk_stream_buffer.cpp | 1 - .../renderer_vulkan/vk_swapchain.cpp | 5 + .../renderer_vulkan/vk_task_scheduler.cpp | 8 +- .../renderer_vulkan/vk_texture_runtime.cpp | 60 +++-- .../renderer_vulkan/vk_texture_runtime.h | 23 +- src/video_core/texture/texture_decode.cpp | 2 +- 17 files changed, 275 insertions(+), 237 deletions(-) diff --git a/src/citra_qt/configuration/configure_graphics.cpp b/src/citra_qt/configuration/configure_graphics.cpp index fa2fbbc37..036b74800 100644 --- a/src/citra_qt/configuration/configure_graphics.cpp +++ b/src/citra_qt/configuration/configure_graphics.cpp @@ -74,6 +74,7 @@ void ConfigureGraphics::SetConfiguration() { ui->toggle_shader_jit->setChecked(Settings::values.use_shader_jit); ui->toggle_disk_shader_cache->setChecked(Settings::values.use_disk_shader_cache); ui->toggle_vsync_new->setChecked(Settings::values.use_vsync_new); + ui->graphics_api_combo->setCurrentIndex(static_cast(Settings::values.graphics_api)); } void ConfigureGraphics::ApplyConfiguration() { @@ -84,6 +85,7 @@ void ConfigureGraphics::ApplyConfiguration() { Settings::values.use_shader_jit = ui->toggle_shader_jit->isChecked(); Settings::values.use_disk_shader_cache = ui->toggle_disk_shader_cache->isChecked(); Settings::values.use_vsync_new = ui->toggle_vsync_new->isChecked(); + Settings::values.graphics_api = static_cast(ui->graphics_api_combo->currentIndex()); } void ConfigureGraphics::RetranslateUI() { diff --git a/src/video_core/rasterizer_cache/rasterizer_cache.h b/src/video_core/rasterizer_cache/rasterizer_cache.h index 26d50ed07..02da9e465 100644 --- a/src/video_core/rasterizer_cache/rasterizer_cache.h +++ b/src/video_core/rasterizer_cache/rasterizer_cache.h @@ -894,7 +894,7 @@ void RasterizerCache::UploadSurface(const Surface& surface, SurfaceInterval i ASSERT(load_start >= surface->addr && load_end <= surface->end); const auto& staging = runtime.FindStaging( - surface->width * surface->height * GetBytesPerPixel(surface->pixel_format), true); + surface->width * surface->height * GetBytesPerPixel(surface->pixel_format) * 2, true); MemoryRef source_ptr = VideoCore::g_memory->GetPhysicalRef(info.addr); if (!source_ptr) [[unlikely]] { return; diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.cpp b/src/video_core/renderer_vulkan/renderer_vulkan.cpp index 0c26b07df..2157e4476 100644 --- a/src/video_core/renderer_vulkan/renderer_vulkan.cpp +++ b/src/video_core/renderer_vulkan/renderer_vulkan.cpp @@ -198,6 +198,8 @@ RendererVulkan::RendererVulkan(Frontend::EmuWindow& window) RendererVulkan::~RendererVulkan() { vk::Device device = instance.GetDevice(); + device.waitIdle(); + device.destroyPipelineLayout(present_pipeline_layout); device.destroyShaderModule(present_vertex_shader); device.destroyDescriptorSetLayout(present_descriptor_layout); @@ -208,9 +210,22 @@ RendererVulkan::~RendererVulkan() { device.destroyShaderModule(present_shaders[i]); } - for (std::size_t i = 0; i < present_samplers.size(); i++) { - device.destroySampler(present_samplers[i]); + for (auto& sampler : present_samplers) { + device.destroySampler(sampler); } + + for (auto& info : screen_infos) { + const VideoCore::HostTextureTag tag = { + .format = VideoCore::PixelFormatFromGPUPixelFormat(info.texture.format), + .width = info.texture.width, + .height = info.texture.height, + .layers = 1 + }; + + runtime.Recycle(tag, std::move(info.texture.alloc)); + } + + rasterizer.reset(); } VideoCore::ResultStatus RendererVulkan::Init() { @@ -236,35 +251,56 @@ void RendererVulkan::Sync() { } void RendererVulkan::PrepareRendertarget() { - for (int i = 0; i < 3; i++) { - int fb_id = i == 2 ? 1 : 0; + for (u32 i = 0; i < 3; i++) { + const u32 fb_id = i == 2 ? 1 : 0; const auto& framebuffer = GPU::g_regs.framebuffer_config[fb_id]; // Main LCD (0): 0x1ED02204, Sub LCD (1): 0x1ED02A04 u32 lcd_color_addr = (fb_id == 0) ? LCD_REG_INDEX(color_fill_top) : LCD_REG_INDEX(color_fill_bottom); lcd_color_addr = HW::VADDR_LCD + 4 * lcd_color_addr; - LCD::Regs::ColorFill color_fill = {0}; + LCD::Regs::ColorFill color_fill{0}; LCD::Read(color_fill.raw, lcd_color_addr); if (color_fill.is_enabled) { - LoadColorToActiveGLTexture(color_fill.color_r, color_fill.color_g, color_fill.color_b, - screen_infos[i].texture); + const vk::ClearColorValue clear_color = { + .float32 = std::array{ + color_fill.color_r / 255.0f, + color_fill.color_g / 255.0f, + color_fill.color_b / 255.0f, + 1.0f + } + }; + + const vk::ImageSubresourceRange range = { + .aspectMask = vk::ImageAspectFlagBits::eColor, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1, + }; + + vk::CommandBuffer command_buffer = scheduler.GetRenderCommandBuffer(); + TextureInfo& texture = screen_infos[i].texture; + runtime.Transition(command_buffer, texture.alloc, vk::ImageLayout::eTransferDstOptimal, 0, texture.alloc.levels); + command_buffer.clearColorImage(texture.alloc.image, vk::ImageLayout::eTransferDstOptimal, + clear_color, range); } else { - if (screen_infos[i].texture.width != framebuffer.width || - screen_infos[i].texture.height != framebuffer.height || - screen_infos[i].texture.format != framebuffer.color_format) { + TextureInfo& texture = screen_infos[i].texture; + if (texture.width != framebuffer.width || texture.height != framebuffer.height || + texture.format != framebuffer.color_format) { + // Reallocate texture if the framebuffer size has changed. // This is expected to not happen very often and hence should not be a // performance problem. - ConfigureFramebufferTexture(screen_infos[i].texture, framebuffer); + ConfigureFramebufferTexture(texture, framebuffer); } LoadFBToScreenInfo(framebuffer, screen_infos[i], i == 1); // Resize the texture in case the framebuffer size has changed - screen_infos[i].texture.width = framebuffer.width; - screen_infos[i].texture.height = framebuffer.height; + texture.width = framebuffer.width; + texture.height = framebuffer.height; } } } @@ -342,25 +378,6 @@ void RendererVulkan::LoadFBToScreenInfo(const GPU::Regs::FramebufferConfig& fram } } -void RendererVulkan::LoadColorToActiveGLTexture(u8 color_r, u8 color_g, u8 color_b, const TextureInfo& texture) { - const auto color = std::array{color_r / 255.0f, color_g / 255.0f, color_b / 255.0f, 1}; - const vk::ClearColorValue clear_color = { - .float32 = color - }; - - const vk::ImageSubresourceRange range = { - .aspectMask = vk::ImageAspectFlagBits::eColor, - .baseMipLevel = 0, - .levelCount = 1, - .baseArrayLayer = 0, - .layerCount = 1, - }; - - vk::CommandBuffer command_buffer = scheduler.GetRenderCommandBuffer(); - command_buffer.clearColorImage(texture.alloc.image, vk::ImageLayout::eShaderReadOnlyOptimal, - clear_color, range); -} - void RendererVulkan::CompileShaders() { vk::Device device = instance.GetDevice(); present_vertex_shader = Compile(vertex_shader, vk::ShaderStageFlagBits::eVertex, @@ -589,6 +606,30 @@ void RendererVulkan::BuildPipelines() { } } +void RendererVulkan::ConfigureFramebufferTexture(TextureInfo& texture, const GPU::Regs::FramebufferConfig& framebuffer) { + TextureInfo old_texture = texture; + texture = TextureInfo { + .alloc = runtime.Allocate(framebuffer.width, framebuffer.height, + VideoCore::PixelFormatFromGPUPixelFormat(framebuffer.color_format), + VideoCore::TextureType::Texture2D), + .width = framebuffer.width, + .height = framebuffer.height, + .format = framebuffer.color_format, + }; + + // Recyle the old texture after allocation to avoid having duplicates of the same allocation in the recycler + if (old_texture.width != 0 && old_texture.height != 0) { + const VideoCore::HostTextureTag tag = { + .format = VideoCore::PixelFormatFromGPUPixelFormat(old_texture.format), + .width = old_texture.width, + .height = old_texture.height, + .layers = 1 + }; + + runtime.Recycle(tag, std::move(old_texture.alloc)); + } +} + void RendererVulkan::ReloadSampler() { current_sampler = !Settings::values.filter_mode; } @@ -610,16 +651,6 @@ void RendererVulkan::ReloadPipeline() { } } -void RendererVulkan::ConfigureFramebufferTexture(TextureInfo& texture, - const GPU::Regs::FramebufferConfig& framebuffer) { - texture.format = framebuffer.color_format; - texture.width = framebuffer.width; - texture.height = framebuffer.height; - texture.alloc = runtime.Allocate(framebuffer.width, framebuffer.height, - VideoCore::PixelFormatFromGPUPixelFormat(framebuffer.color_format), - VideoCore::TextureType::Texture2D); -} - void RendererVulkan::DrawSingleScreenRotated(u32 screen_id, float x, float y, float w, float h) { auto& screen_info = screen_infos[screen_id]; const auto& texcoords = screen_info.display_texcoords; @@ -659,9 +690,14 @@ void RendererVulkan::DrawSingleScreenRotated(u32 screen_id, float x, float y, fl .color = clear_color }; + const auto& layout = render_window.GetFramebufferLayout(); const vk::RenderPassBeginInfo begin_info = { .renderPass = renderpass_cache.GetPresentRenderpass(), .framebuffer = swapchain.GetFramebuffer(), + .renderArea = vk::Rect2D{ + .offset = {0, 0}, + .extent = {layout.width, layout.height} + }, .clearValueCount = 1, .pClearValues = &clear_value, }; @@ -894,6 +930,7 @@ void RendererVulkan::DrawScreens(const Layout::FramebufferLayout& layout, bool f } } + return; draw_info.layer = 0; if (layout.bottom_screen_enabled) { if (layout.is_rotated) { @@ -989,7 +1026,7 @@ void RendererVulkan::SwapBuffers() { for (auto& info : screen_infos) { auto alloc = info.display_texture ? info.display_texture : &info.texture.alloc; - runtime.Transition(command_buffer, *alloc, vk::ImageLayout::eShaderReadOnlyOptimal, 0, 1); + runtime.Transition(command_buffer, *alloc, vk::ImageLayout::eShaderReadOnlyOptimal, 0, alloc->levels); } DrawScreens(layout, false); diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.h b/src/video_core/renderer_vulkan/renderer_vulkan.h index 5923e232a..8f4153311 100644 --- a/src/video_core/renderer_vulkan/renderer_vulkan.h +++ b/src/video_core/renderer_vulkan/renderer_vulkan.h @@ -79,10 +79,10 @@ private: void CompileShaders(); void BuildLayouts(); void BuildPipelines(); + void ConfigureFramebufferTexture(TextureInfo& texture, const GPU::Regs::FramebufferConfig& framebuffer); void ConfigureRenderPipeline(); void PrepareRendertarget(); void BeginRendering(); - void ConfigureFramebufferTexture(TextureInfo& texture, const GPU::Regs::FramebufferConfig& framebuffer); void DrawScreens(const Layout::FramebufferLayout& layout, bool flipped); void DrawSingleScreenRotated(u32 screen_id, float x, float y, float w, float h); @@ -96,9 +96,6 @@ private: void LoadFBToScreenInfo(const GPU::Regs::FramebufferConfig& framebuffer, ScreenInfo& screen_info, bool right_eye); - /// Fills active OpenGL texture with the given RGB color. - void LoadColorToActiveGLTexture(u8 color_r, u8 color_g, u8 color_b, const TextureInfo& texture); - private: Instance instance; TaskScheduler scheduler; @@ -121,7 +118,7 @@ private: u32 current_sampler = 0; /// Display information for top and bottom screens respectively - std::array screen_infos; + std::array screen_infos{}; PresentUniformData draw_info{}; vk::ClearColorValue clear_color{}; }; diff --git a/src/video_core/renderer_vulkan/vk_common.h b/src/video_core/renderer_vulkan/vk_common.h index ba9bb6f86..34d30a98e 100644 --- a/src/video_core/renderer_vulkan/vk_common.h +++ b/src/video_core/renderer_vulkan/vk_common.h @@ -47,7 +47,7 @@ constexpr vk::ImageUsageFlags GetImageUsage(vk::ImageAspectFlags aspect) { return usage | vk::ImageUsageFlagBits::eStorage | vk::ImageUsageFlagBits::eColorAttachment; } -}; +} /// Returns a bit mask with the required features of a format with a particular aspect constexpr vk::FormatFeatureFlags GetFormatFeatures(vk::ImageAspectFlags aspect) { @@ -63,6 +63,6 @@ constexpr vk::FormatFeatureFlags GetFormatFeatures(vk::ImageAspectFlags aspect) return usage | vk::FormatFeatureFlagBits::eStorageImage | vk::FormatFeatureFlagBits::eColorAttachment; } -}; +} } // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_instance.cpp b/src/video_core/renderer_vulkan/vk_instance.cpp index a5958c484..ad76df4e1 100644 --- a/src/video_core/renderer_vulkan/vk_instance.cpp +++ b/src/video_core/renderer_vulkan/vk_instance.cpp @@ -81,6 +81,10 @@ bool Instance::IsFormatSupported(vk::Format format, vk::FormatFeatureFlags usage } vk::Format Instance::GetFormatAlternative(vk::Format format) const { + if (format == vk::Format::eUndefined) { + return format; + } + vk::FormatFeatureFlags features = GetFormatFeatures(GetImageAspect(format)); if (IsFormatSupported(format, features)) { return format; @@ -104,8 +108,8 @@ vk::Format Instance::GetFormatAlternative(vk::Format format) const { // B4G4R4A4 is not guaranteed by the spec to support attachments return GetFormatAlternative(vk::Format::eB4G4R4A4UnormPack16); default: - LOG_WARNING(Render_Vulkan, "Unable to find compatible alternative to format = {} with usage {}", - vk::to_string(format), vk::to_string(features)); + LOG_WARNING(Render_Vulkan, "Format {} doesn't support attachments, falling back to RGBA8", + vk::to_string(format)); return vk::Format::eR8G8B8A8Unorm; } } diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index 3b049b31b..7cdd510b8 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -150,6 +150,7 @@ PipelineCache::~PipelineCache() { SaveDiskCache(); device.destroyPipelineLayout(layout); + device.destroyShaderModule(trivial_vertex_shader); for (std::size_t i = 0; i < MAX_DESCRIPTOR_SETS; i++) { device.destroyDescriptorSetLayout(descriptor_set_layouts[i]); device.destroyDescriptorUpdateTemplate(update_templates[i]); @@ -624,9 +625,9 @@ void PipelineCache::LoadDiskCache() { void PipelineCache::SaveDiskCache() { const std::string cache_path = - FileUtil::GetUserPath(FileUtil::UserPath::ShaderDir) + DIR_SEP "vulkan" + DIR_SEP "pipelines.bin"; + FileUtil::GetUserPath(FileUtil::UserPath::ShaderDir) + "vulkan" + DIR_SEP "pipelines.bin"; - FileUtil::IOFile cache_file{cache_path, "w"}; + FileUtil::IOFile cache_file{cache_path, "wb"}; if (!cache_file.IsOpen()) { LOG_INFO(Render_Vulkan, "Unable to open pipeline cache for writing"); return; diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index 604d1373b..d64ff81bf 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -172,9 +172,11 @@ RasterizerVulkan::RasterizerVulkan(Frontend::EmuWindow& emu_window, const Instan } RasterizerVulkan::~RasterizerVulkan() { + // Submit any remaining work + scheduler.Submit(true, false); + VmaAllocator allocator = instance.GetAllocator(); vk::Device device = instance.GetDevice(); - device.waitIdle(); for (auto& [key, sampler] : samplers) { device.destroySampler(sampler); @@ -540,11 +542,6 @@ bool RasterizerVulkan::Draw(bool accelerate, bool is_indexed) { MICROPROFILE_SCOPE(OpenGL_Drawing); const auto& regs = Pica::g_state.regs; - pipeline_info.color_attachment = - VideoCore::PixelFormatFromColorFormat(regs.framebuffer.framebuffer.color_format); - pipeline_info.depth_attachment = - VideoCore::PixelFormatFromDepthFormat(regs.framebuffer.framebuffer.depth_format); - const bool shadow_rendering = regs.framebuffer.output_merger.fragment_operation_mode == Pica::FramebufferRegs::FragmentOperationMode::Shadow; const bool has_stencil = @@ -573,6 +570,11 @@ bool RasterizerVulkan::Draw(bool accelerate, bool is_indexed) { auto [color_surface, depth_surface, surfaces_rect] = res_cache.GetFramebufferSurfaces(using_color_fb, using_depth_fb, viewport_rect_unscaled); + pipeline_info.color_attachment = + color_surface ? color_surface->pixel_format : VideoCore::PixelFormat::Invalid; + pipeline_info.depth_attachment = + depth_surface ? depth_surface->pixel_format : VideoCore::PixelFormat::Invalid; + const u16 res_scale = color_surface != nullptr ? color_surface->res_scale : (depth_surface == nullptr ? 1u : depth_surface->res_scale); @@ -635,6 +637,43 @@ bool RasterizerVulkan::Draw(bool accelerate, bool is_indexed) { } }; + const auto BindCubeFace = [&](Pica::TexturingRegs::CubeFace face, + Pica::Texture::TextureInfo& info) { + info.physical_address = regs.texturing.GetCubePhysicalAddress(face); + auto surface = res_cache.GetTextureSurface(info); + + const u32 binding = static_cast(face); + if (surface != nullptr) { + pipeline_cache.BindStorageImage(binding, surface->alloc.image_view); + } else { + pipeline_cache.BindStorageImage(binding, default_texture.image_view); + } + }; + + const auto BindSampler = [&](u32 binding, SamplerInfo& info, + const Pica::TexturingRegs::FullTextureConfig& texture) { + info = SamplerInfo{ + .mag_filter = texture.config.mag_filter, + .min_filter = texture.config.min_filter, + .mip_filter = texture.config.mip_filter, + .wrap_s = texture.config.wrap_s, + .wrap_t = texture.config.wrap_t, + .border_color = texture.config.border_color.raw, + .lod_min = texture.config.lod.min_level, + .lod_max = texture.config.lod.max_level, + .lod_bias = texture.config.lod.bias + }; + + // Search the cache and bind the appropriate sampler + if (auto it = samplers.find(info); it != samplers.end()) { + pipeline_cache.BindSampler(binding, it->second); + } else { + vk::Sampler texture_sampler = CreateSampler(info); + samplers.emplace(info, texture_sampler); + pipeline_cache.BindSampler(binding, texture_sampler); + } + }; + vk::CommandBuffer command_buffer = scheduler.GetRenderCommandBuffer(); // Sync and bind the texture surfaces @@ -643,136 +682,69 @@ bool RasterizerVulkan::Draw(bool accelerate, bool is_indexed) { const auto& texture = pica_textures[texture_index]; if (texture.enabled) { - /*if (texture_index == 0) { + if (texture_index == 0) { using TextureType = Pica::TexturingRegs::TextureConfig::TextureType; switch (texture.config.type.Value()) { case TextureType::Shadow2D: { - if (!allow_shadow) - continue; - - Surface surface = res_cache.GetTextureSurface(texture); + auto surface = res_cache.GetTextureSurface(texture); if (surface != nullptr) { - CheckBarrier(state.image_shadow_texture_px = surface->texture.handle); + pipeline_cache.BindStorageImage(0, surface->alloc.image_view); } else { - state.image_shadow_texture_px = 0; + pipeline_cache.BindStorageImage(0, default_texture.image_view); } continue; } case TextureType::ShadowCube: { - if (!allow_shadow) - continue; - Pica::Texture::TextureInfo info = Pica::Texture::TextureInfo::FromPicaRegister( - texture.config, texture.format); - Surface surface; - using CubeFace = Pica::TexturingRegs::CubeFace; - info.physical_address = - regs.texturing.GetCubePhysicalAddress(CubeFace::PositiveX); - surface = res_cache.GetTextureSurface(info); - if (surface != nullptr) { - CheckBarrier(state.image_shadow_texture_px = surface->texture.handle); - } else { - state.image_shadow_texture_px = 0; - } - - info.physical_address = - regs.texturing.GetCubePhysicalAddress(CubeFace::NegativeX); - surface = res_cache.GetTextureSurface(info); - if (surface != nullptr) { - CheckBarrier(state.image_shadow_texture_nx = surface->texture.handle); - } else { - state.image_shadow_texture_nx = 0; - } - - info.physical_address = - regs.texturing.GetCubePhysicalAddress(CubeFace::PositiveY); - surface = res_cache.GetTextureSurface(info); - if (surface != nullptr) { - CheckBarrier(state.image_shadow_texture_py = surface->texture.handle); - } else { - state.image_shadow_texture_py = 0; - } - - info.physical_address = - regs.texturing.GetCubePhysicalAddress(CubeFace::NegativeY); - surface = res_cache.GetTextureSurface(info); - if (surface != nullptr) { - CheckBarrier(state.image_shadow_texture_ny = surface->texture.handle); - } else { - state.image_shadow_texture_ny = 0; - } - - info.physical_address = - regs.texturing.GetCubePhysicalAddress(CubeFace::PositiveZ); - surface = res_cache.GetTextureSurface(info); - if (surface != nullptr) { - CheckBarrier(state.image_shadow_texture_pz = surface->texture.handle); - } else { - state.image_shadow_texture_pz = 0; - } - - info.physical_address = - regs.texturing.GetCubePhysicalAddress(CubeFace::NegativeZ); - surface = res_cache.GetTextureSurface(info); - if (surface != nullptr) { - CheckBarrier(state.image_shadow_texture_nz = surface->texture.handle); - } else { - state.image_shadow_texture_nz = 0; - } - + auto info = Pica::Texture::TextureInfo::FromPicaRegister(texture.config, + texture.format); + BindCubeFace(CubeFace::PositiveX, info); + BindCubeFace(CubeFace::NegativeX, info); + BindCubeFace(CubeFace::PositiveY, info); + BindCubeFace(CubeFace::NegativeY, info); + BindCubeFace(CubeFace::PositiveZ, info); + BindCubeFace(CubeFace::NegativeZ, info); continue; } - case TextureType::TextureCube: + case TextureType::TextureCube: { using CubeFace = Pica::TexturingRegs::CubeFace; - TextureCubeConfig config; - config.px = regs.texturing.GetCubePhysicalAddress(CubeFace::PositiveX); - config.nx = regs.texturing.GetCubePhysicalAddress(CubeFace::NegativeX); - config.py = regs.texturing.GetCubePhysicalAddress(CubeFace::PositiveY); - config.ny = regs.texturing.GetCubePhysicalAddress(CubeFace::NegativeY); - config.pz = regs.texturing.GetCubePhysicalAddress(CubeFace::PositiveZ); - config.nz = regs.texturing.GetCubePhysicalAddress(CubeFace::NegativeZ); - config.width = texture.config.width; - config.format = texture.format; - state.texture_cube_unit.texture_cube = - res_cache.GetTextureCube(config).texture.handle; + const VideoCore::TextureCubeConfig config = { + .px = regs.texturing.GetCubePhysicalAddress(CubeFace::PositiveX), + .nx = regs.texturing.GetCubePhysicalAddress(CubeFace::NegativeX), + .py = regs.texturing.GetCubePhysicalAddress(CubeFace::PositiveY), + .ny = regs.texturing.GetCubePhysicalAddress(CubeFace::NegativeY), + .pz = regs.texturing.GetCubePhysicalAddress(CubeFace::PositiveZ), + .nz = regs.texturing.GetCubePhysicalAddress(CubeFace::NegativeZ), + .width = texture.config.width, + .format = texture.format + }; - texture_cube_sampler.SyncWithConfig(texture.config); - state.texture_units[texture_index].texture_2d = 0; + auto surface = res_cache.GetTextureCube(config); + if (surface != nullptr) { + runtime.Transition(command_buffer, surface->alloc, + vk::ImageLayout::eShaderReadOnlyOptimal, + 0, surface->alloc.levels, 0, 6); + pipeline_cache.BindTexture(3, surface->alloc.image_view); + } else { + pipeline_cache.BindTexture(3, default_texture.image_view); + } + + BindSampler(3, texture_cube_sampler, texture); continue; // Texture unit 0 setup finished. Continue to next unit - default: - state.texture_cube_unit.texture_cube = 0; } - }*/ - - //texture_samplers[texture_index].SyncWithConfig(texture.config); + default: + break; + } + } // Update sampler key - texture_samplers[texture_index] = SamplerInfo{ - .mag_filter = texture.config.mag_filter, - .min_filter = texture.config.min_filter, - .mip_filter = texture.config.mip_filter, - .wrap_s = texture.config.wrap_s, - .wrap_t = texture.config.wrap_t, - .border_color = texture.config.border_color.raw, - .lod_min = texture.config.lod.min_level, - .lod_max = texture.config.lod.max_level, - .lod_bias = texture.config.lod.bias - }; - - // Search the cache and bind the appropriate sampler - const SamplerInfo& key = texture_samplers[texture_index]; - if (auto it = samplers.find(key); it != samplers.end()) { - pipeline_cache.BindSampler(texture_index, it->second); - } else { - vk::Sampler texture_sampler = CreateSampler(key); - samplers.emplace(key, texture_sampler); - pipeline_cache.BindSampler(texture_index, texture_sampler); - } + BindSampler(texture_index, texture_samplers[texture_index], texture); auto surface = res_cache.GetTextureSurface(texture); if (surface != nullptr) { runtime.Transition(command_buffer, surface->alloc, - vk::ImageLayout::eShaderReadOnlyOptimal, 0, surface->alloc.levels); + vk::ImageLayout::eShaderReadOnlyOptimal, + 0, surface->alloc.levels); CheckBarrier(surface->alloc.image_view, texture_index); } else { // Can occur when texture addr is null or its memory is unmapped/invalid @@ -808,6 +780,8 @@ bool RasterizerVulkan::Draw(bool accelerate, bool is_indexed) { // Enable scissor test to prevent drawing outside of the framebuffer region pipeline_cache.SetScissor(draw_rect.left, draw_rect.bottom, draw_rect.GetWidth(), draw_rect.GetHeight()); + //return true; + auto valid_surface = color_surface ? color_surface : depth_surface; const FramebufferInfo framebuffer_info = { .color = color_surface ? color_surface->alloc.image_view : VK_NULL_HANDLE, @@ -1599,10 +1573,13 @@ vk::Sampler RasterizerVulkan::CreateSampler(const SamplerInfo& info) { .mipmapMode = PicaToVK::TextureMipFilterMode(info.mip_filter), .addressModeU = PicaToVK::WrapMode(info.wrap_s), .addressModeV = PicaToVK::WrapMode(info.wrap_t), + .mipLodBias = info.lod_bias / 256.0f, .anisotropyEnable = true, .maxAnisotropy = properties.limits.maxSamplerAnisotropy, .compareEnable = false, .compareOp = vk::CompareOp::eAlways, + .minLod = static_cast(info.lod_min), + .maxLod = static_cast(info.lod_max), .borderColor = vk::BorderColor::eIntOpaqueBlack, .unnormalizedCoordinates = false }; diff --git a/src/video_core/renderer_vulkan/vk_renderpass_cache.cpp b/src/video_core/renderer_vulkan/vk_renderpass_cache.cpp index 8e9293efb..f1fecef77 100644 --- a/src/video_core/renderer_vulkan/vk_renderpass_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_renderpass_cache.cpp @@ -13,19 +13,20 @@ namespace Vulkan { vk::Format ToVkFormatColor(u32 index) { switch (index) { - case 1: return vk::Format::eR8G8B8A8Unorm; - case 2: return vk::Format::eR8G8B8Unorm; - case 3: return vk::Format::eR5G5B5A1UnormPack16; - case 4: return vk::Format::eR5G6B5UnormPack16; - case 5: return vk::Format::eR4G4B4A4UnormPack16; + case 0: return vk::Format::eR8G8B8A8Unorm; + case 1: return vk::Format::eR8G8B8Unorm; + case 2: return vk::Format::eR5G5B5A1UnormPack16; + case 3: return vk::Format::eR5G6B5UnormPack16; + case 4: return vk::Format::eR4G4B4A4UnormPack16; default: return vk::Format::eUndefined; } } vk::Format ToVkFormatDepth(u32 index) { switch (index) { - case 1: return vk::Format::eD16Unorm; - case 2: return vk::Format::eX8D24UnormPack32; + case 0: return vk::Format::eD16Unorm; + case 1: return vk::Format::eX8D24UnormPack32; + // Notice the similar gap in PixelFormat case 3: return vk::Format::eD24UnormS8Uint; default: return vk::Format::eUndefined; } @@ -36,14 +37,15 @@ RenderpassCache::RenderpassCache(const Instance& instance, TaskScheduler& schedu // Pre-create all needed renderpasses by the renderer for (u32 color = 0; color <= MAX_COLOR_FORMATS; color++) { for (u32 depth = 0; depth <= MAX_DEPTH_FORMATS; depth++) { - if (color == 0 && depth == 0) { - continue; - } - const vk::Format color_format = - color == 0 ? vk::Format::eUndefined : instance.GetFormatAlternative(ToVkFormatColor(color)); + instance.GetFormatAlternative(ToVkFormatColor(color)); const vk::Format depth_stencil_format = - depth == 0 ? vk::Format::eUndefined : instance.GetFormatAlternative(ToVkFormatDepth(depth)); + instance.GetFormatAlternative(ToVkFormatDepth(depth)); + + if (color_format == vk::Format::eUndefined && + depth_stencil_format == vk::Format::eUndefined) { + continue; + } cached_renderpasses[color][depth][0] = CreateRenderPass(color_format, depth_stencil_format, vk::AttachmentLoadOp::eLoad, @@ -61,16 +63,13 @@ RenderpassCache::~RenderpassCache() { vk::Device device = instance.GetDevice(); for (u32 color = 0; color <= MAX_COLOR_FORMATS; color++) { for (u32 depth = 0; depth <= MAX_DEPTH_FORMATS; depth++) { - if (color == 0 && depth == 0) { - continue; - } + if (vk::RenderPass load_pass = cached_renderpasses[color][depth][0]; load_pass) { + device.destroyRenderPass(load_pass); + } - auto& load_pass = cached_renderpasses[color][depth][0]; - auto& clear_pass = cached_renderpasses[color][depth][1]; - - // Destroy renderpasses - device.destroyRenderPass(load_pass); - device.destroyRenderPass(clear_pass); + if (vk::RenderPass clear_pass = cached_renderpasses[color][depth][1]; clear_pass) { + device.destroyRenderPass(clear_pass); + } } } @@ -109,9 +108,9 @@ void RenderpassCache::CreatePresentRenderpass(vk::Format format) { vk::RenderPass RenderpassCache::GetRenderpass(VideoCore::PixelFormat color, VideoCore::PixelFormat depth, bool is_clear) const { const u32 color_index = - color == VideoCore::PixelFormat::Invalid ? 0 : (static_cast(color) + 1); + color == VideoCore::PixelFormat::Invalid ? MAX_COLOR_FORMATS : static_cast(color); const u32 depth_index = - depth == VideoCore::PixelFormat::Invalid ? 0 : (static_cast(depth) - 14); + depth == VideoCore::PixelFormat::Invalid ? MAX_DEPTH_FORMATS : (static_cast(depth) - 14); ASSERT(color_index <= MAX_COLOR_FORMATS && depth_index <= MAX_DEPTH_FORMATS); return cached_renderpasses[color_index][depth_index][is_clear]; @@ -120,7 +119,6 @@ vk::RenderPass RenderpassCache::GetRenderpass(VideoCore::PixelFormat color, Vide vk::RenderPass RenderpassCache::CreateRenderPass(vk::Format color, vk::Format depth, vk::AttachmentLoadOp load_op, vk::ImageLayout initial_layout, vk::ImageLayout final_layout) const { // Define attachments - u32 attachment_count = 0; std::array attachments; diff --git a/src/video_core/renderer_vulkan/vk_renderpass_cache.h b/src/video_core/renderer_vulkan/vk_renderpass_cache.h index 47187aa62..a73c8e892 100644 --- a/src/video_core/renderer_vulkan/vk_renderpass_cache.h +++ b/src/video_core/renderer_vulkan/vk_renderpass_cache.h @@ -13,7 +13,7 @@ class Instance; class TaskScheduler; constexpr u32 MAX_COLOR_FORMATS = 5; -constexpr u32 MAX_DEPTH_FORMATS = 3; +constexpr u32 MAX_DEPTH_FORMATS = 4; class RenderpassCache { public: @@ -27,11 +27,11 @@ public: void ExitRenderpass(); /// Returns the renderpass associated with the color-depth format pair - vk::RenderPass GetRenderpass(VideoCore::PixelFormat color, VideoCore::PixelFormat depth, - bool is_clear) const; + [[nodiscard]] vk::RenderPass GetRenderpass(VideoCore::PixelFormat color, VideoCore::PixelFormat depth, + bool is_clear) const; /// Returns the swapchain clear renderpass - vk::RenderPass GetPresentRenderpass() const { + [[nodiscard]] vk::RenderPass GetPresentRenderpass() const { return present_renderpass; } diff --git a/src/video_core/renderer_vulkan/vk_shader_gen.cpp b/src/video_core/renderer_vulkan/vk_shader_gen.cpp index e2a151b8b..3069b095e 100644 --- a/src/video_core/renderer_vulkan/vk_shader_gen.cpp +++ b/src/video_core/renderer_vulkan/vk_shader_gen.cpp @@ -1568,6 +1568,7 @@ void main() { normquat = vert_normquat; view = vert_view; gl_Position = vert_position; + gl_Position.z = (gl_Position.z + gl_Position.w) / 2.0; #if !defined(CITRA_GLES) || defined(GL_EXT_clip_cull_distance) gl_ClipDistance[0] = -vert_position.z; // fixed PICA clipping plane z <= 0 gl_ClipDistance[1] = dot(clip_coef, vert_position); diff --git a/src/video_core/renderer_vulkan/vk_stream_buffer.cpp b/src/video_core/renderer_vulkan/vk_stream_buffer.cpp index 11f39712f..9c142e5ba 100644 --- a/src/video_core/renderer_vulkan/vk_stream_buffer.cpp +++ b/src/video_core/renderer_vulkan/vk_stream_buffer.cpp @@ -171,7 +171,6 @@ void StreamBuffer::Commit(u32 size) { command_buffer.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, stage_mask, vk::DependencyFlagBits::eByRegion, {}, buffer_barrier, {}); - buffer_offset += size; available_size -= size; } diff --git a/src/video_core/renderer_vulkan/vk_swapchain.cpp b/src/video_core/renderer_vulkan/vk_swapchain.cpp index d3e397ef4..98a88ecfe 100644 --- a/src/video_core/renderer_vulkan/vk_swapchain.cpp +++ b/src/video_core/renderer_vulkan/vk_swapchain.cpp @@ -24,6 +24,11 @@ Swapchain::~Swapchain() { device.destroySemaphore(render_finished); device.destroySemaphore(image_available); device.destroySwapchainKHR(swapchain); + + for (auto& image : swapchain_images) { + device.destroyImageView(image.image_view); + device.destroyFramebuffer(image.framebuffer); + } } void Swapchain::Create(u32 width, u32 height, bool vsync_enabled) { diff --git a/src/video_core/renderer_vulkan/vk_task_scheduler.cpp b/src/video_core/renderer_vulkan/vk_task_scheduler.cpp index b5557bc2f..ef5b91d67 100644 --- a/src/video_core/renderer_vulkan/vk_task_scheduler.cpp +++ b/src/video_core/renderer_vulkan/vk_task_scheduler.cpp @@ -61,10 +61,9 @@ TaskScheduler::TaskScheduler(const Instance& instance) : instance{instance} { } TaskScheduler::~TaskScheduler() { - // Submit any remaining work - Submit(true, false); - vk::Device device = instance.GetDevice(); + device.waitIdle(); + for (const auto& command : commands) { device.destroyFence(command.fence); device.destroyDescriptorPool(command.descriptor_pool); @@ -96,7 +95,8 @@ void TaskScheduler::WaitFence(u32 counter) { } } - UNREACHABLE_MSG("Invalid fence counter!"); + LOG_CRITICAL(Render_Vulkan,"Invalid fence counter {}!", counter); + UNREACHABLE(); } void TaskScheduler::Submit(bool wait_completion, bool begin_next, diff --git a/src/video_core/renderer_vulkan/vk_texture_runtime.cpp b/src/video_core/renderer_vulkan/vk_texture_runtime.cpp index aba6aade0..9a7d81620 100644 --- a/src/video_core/renderer_vulkan/vk_texture_runtime.cpp +++ b/src/video_core/renderer_vulkan/vk_texture_runtime.cpp @@ -87,7 +87,9 @@ TextureRuntime::TextureRuntime(const Instance& instance, TaskScheduler& schedule TextureRuntime::~TextureRuntime() { VmaAllocator allocator = instance.GetAllocator(); vk::Device device = instance.GetDevice(); - for (auto& [key, alloc] : texture_recycler) { + device.waitIdle(); + + for (const auto& [key, alloc] : texture_recycler) { vmaDestroyImage(allocator, alloc.image, alloc.allocation); device.destroyImageView(alloc.image_view); } @@ -180,7 +182,7 @@ ImageAlloc TextureRuntime::Allocate(u32 width, u32 height, VideoCore::PixelForma .baseMipLevel = 0, .levelCount = 1, .baseArrayLayer = 0, - .layerCount = 1 + .layerCount = layers } }; @@ -196,11 +198,17 @@ ImageAlloc TextureRuntime::Allocate(u32 width, u32 height, VideoCore::PixelForma }; } +void TextureRuntime::Recycle(const VideoCore::HostTextureTag tag, ImageAlloc&& alloc) { + texture_recycler.emplace(tag, std::move(alloc)); +} + void TextureRuntime::FormatConvert(VideoCore::PixelFormat format, bool upload, std::span source, std::span dest) { const VideoCore::SurfaceType type = VideoCore::GetFormatType(format); const vk::FormatFeatureFlagBits feature = ToVkFormatFeatures(type); - if (!instance.IsFormatSupported(ToVkFormat(format), feature)) { + if (instance.IsFormatSupported(ToVkFormat(format), feature)) { + std::memcpy(dest.data(), source.data(), source.size()); + } else { if (format == VideoCore::PixelFormat::RGB8 && upload) { return Pica::Texture::ConvertBGRToRGBA(source, dest); } @@ -215,7 +223,8 @@ bool TextureRuntime::ClearTexture(Surface& surface, const VideoCore::TextureClea renderpass_cache.ExitRenderpass(); vk::CommandBuffer command_buffer = scheduler.GetRenderCommandBuffer(); - Transition(command_buffer, surface.alloc, vk::ImageLayout::eTransferDstOptimal, clear.texture_level, 1); + Transition(command_buffer, surface.alloc, vk::ImageLayout::eTransferDstOptimal, + 0, surface.alloc.levels, 0, surface.texture_type == VideoCore::TextureType::CubeMap ? 6 : 1); // For full clears we can use vkCmdClearColorImage/vkCmdClearDepthStencilImage if (clear.texture_rect == surface.GetScaledRect()) { @@ -281,8 +290,8 @@ bool TextureRuntime::CopyTextures(Surface& source, Surface& dest, const VideoCor }; vk::CommandBuffer command_buffer = scheduler.GetRenderCommandBuffer(); - Transition(command_buffer, source.alloc, vk::ImageLayout::eTransferSrcOptimal, copy.src_level, 1); - Transition(command_buffer, dest.alloc, vk::ImageLayout::eTransferDstOptimal, copy.dst_level, 1); + Transition(command_buffer, source.alloc, vk::ImageLayout::eTransferSrcOptimal, 0, source.alloc.levels); + Transition(command_buffer, dest.alloc, vk::ImageLayout::eTransferDstOptimal, 0, dest.alloc.levels); command_buffer.copyImage(source.alloc.image, vk::ImageLayout::eTransferSrcOptimal, dest.alloc.image, vk::ImageLayout::eTransferDstOptimal, image_copy); @@ -294,8 +303,10 @@ bool TextureRuntime::BlitTextures(Surface& source, Surface& dest, const VideoCor renderpass_cache.ExitRenderpass(); vk::CommandBuffer command_buffer = scheduler.GetRenderCommandBuffer(); - Transition(command_buffer, source.alloc, vk::ImageLayout::eTransferSrcOptimal, blit.src_level, 1); - Transition(command_buffer, dest.alloc, vk::ImageLayout::eTransferDstOptimal, blit.dst_level, 1); + Transition(command_buffer, source.alloc, vk::ImageLayout::eTransferSrcOptimal, + 0, source.alloc.levels, 0, source.texture_type == VideoCore::TextureType::CubeMap ? 6 : 1); + Transition(command_buffer, dest.alloc, vk::ImageLayout::eTransferDstOptimal, + 0, dest.alloc.levels, 0, dest.texture_type == VideoCore::TextureType::CubeMap ? 6 : 1); const std::array source_offsets = { vk::Offset3D{static_cast(blit.src_rect.left), static_cast(blit.src_rect.bottom), 0}, @@ -380,7 +391,8 @@ void TextureRuntime::GenerateMipmaps(Surface& surface, u32 max_level) { } void TextureRuntime::Transition(vk::CommandBuffer command_buffer, ImageAlloc& alloc, - vk::ImageLayout new_layout, u32 level, u32 level_count) { + vk::ImageLayout new_layout, u32 level, u32 level_count, + u32 layer, u32 layer_count) { if (new_layout == alloc.layout || !alloc.image) { return; } @@ -460,10 +472,10 @@ void TextureRuntime::Transition(vk::CommandBuffer command_buffer, ImageAlloc& al .image = alloc.image, .subresourceRange = { .aspectMask = alloc.aspect, - .baseMipLevel = level, - .levelCount = level_count, - .baseArrayLayer = 0, - .layerCount = 1 + .baseMipLevel = /*level*/0, + .levelCount = /*level_count*/alloc.levels, + .baseArrayLayer = layer, + .layerCount = layer_count } }; @@ -477,20 +489,23 @@ void TextureRuntime::Transition(vk::CommandBuffer command_buffer, ImageAlloc& al Surface::Surface(VideoCore::SurfaceParams& params, TextureRuntime& runtime) : VideoCore::SurfaceBase{params}, runtime{runtime}, instance{runtime.GetInstance()}, scheduler{runtime.GetScheduler()} { - if (params.pixel_format != VideoCore::PixelFormat::Invalid) { + + if (pixel_format != VideoCore::PixelFormat::Invalid) { alloc = runtime.Allocate(GetScaledWidth(), GetScaledHeight(), params.pixel_format, texture_type); } } Surface::~Surface() { - const VideoCore::HostTextureTag tag = { - .format = pixel_format, - .width = GetScaledWidth(), - .height = GetScaledHeight(), - .layers = texture_type == VideoCore::TextureType::CubeMap ? 6u : 1u - }; + if (pixel_format != VideoCore::PixelFormat::Invalid) { + const VideoCore::HostTextureTag tag = { + .format = pixel_format, + .width = GetScaledWidth(), + .height = GetScaledHeight(), + .layers = texture_type == VideoCore::TextureType::CubeMap ? 6u : 1u + }; - runtime.texture_recycler.emplace(tag, std::move(alloc)); + runtime.Recycle(tag, std::move(alloc)); + } } MICROPROFILE_DEFINE(Vulkan_Upload, "VulkanSurface", "Texture Upload", MP_RGB(128, 192, 64)); @@ -517,7 +532,8 @@ void Surface::Upload(const VideoCore::BufferTextureCopy& upload, const StagingDa .imageExtent = {rect.GetWidth(), rect.GetHeight(), 1} }; - runtime.Transition(command_buffer, alloc, vk::ImageLayout::eTransferDstOptimal, upload.texture_level, 1); + runtime.Transition(command_buffer, alloc, vk::ImageLayout::eTransferDstOptimal, 0, alloc.levels, + 0, texture_type == VideoCore::TextureType::CubeMap ? 6 : 1); command_buffer.copyBufferToImage(staging.buffer, alloc.image, vk::ImageLayout::eTransferDstOptimal, copy_region); diff --git a/src/video_core/renderer_vulkan/vk_texture_runtime.h b/src/video_core/renderer_vulkan/vk_texture_runtime.h index 4ce5ae1e5..023c7305a 100644 --- a/src/video_core/renderer_vulkan/vk_texture_runtime.h +++ b/src/video_core/renderer_vulkan/vk_texture_runtime.h @@ -44,15 +44,15 @@ public: RenderpassCache& renderpass_cache); ~TextureRuntime(); - TextureRuntime(const TextureRuntime&) = delete; - TextureRuntime& operator=(const TextureRuntime&) = delete; - /// Maps an internal staging buffer of the provided size of pixel uploads/downloads - StagingData FindStaging(u32 size, bool upload); + [[nodiscard]] StagingData FindStaging(u32 size, bool upload); /// Allocates a vulkan image possibly resusing an existing one - ImageAlloc Allocate(u32 width, u32 height, VideoCore::PixelFormat format, - VideoCore::TextureType type); + [[nodiscard]] ImageAlloc Allocate(u32 width, u32 height, VideoCore::PixelFormat format, + VideoCore::TextureType type); + + /// Takes back ownership of the allocation for recycling + void Recycle(const VideoCore::HostTextureTag tag, ImageAlloc&& alloc); /// Performs required format convertions on the staging data void FormatConvert(VideoCore::PixelFormat format, bool upload, @@ -60,10 +60,8 @@ public: /// Transitions the mip level range of the surface to new_layout void Transition(vk::CommandBuffer command_buffer, ImageAlloc& alloc, - vk::ImageLayout new_layout, u32 level, u32 level_count); - - /// Performs operations that need to be done on every scheduler slot switch - void OnSlotSwitch(u32 new_slot); + vk::ImageLayout new_layout, u32 level, u32 level_count, + u32 layer = 0, u32 layer_count = 1); /// Fills the rectangle of the texture with the clear value provided bool ClearTexture(Surface& surface, const VideoCore::TextureClear& clear, @@ -78,6 +76,9 @@ public: /// Generates mipmaps for all the available levels of the texture void GenerateMipmaps(Surface& surface, u32 max_level); + /// Performs operations that need to be done on every scheduler slot switch + void OnSlotSwitch(u32 new_slot); + private: /// Returns the current Vulkan instance const Instance& GetInstance() const { @@ -95,7 +96,7 @@ private: RenderpassCache& renderpass_cache; std::array, SCHEDULER_COMMAND_COUNT> staging_buffers; std::array staging_offsets{}; - std::unordered_map texture_recycler; + std::unordered_multimap texture_recycler; }; class Surface : public VideoCore::SurfaceBase { diff --git a/src/video_core/texture/texture_decode.cpp b/src/video_core/texture/texture_decode.cpp index feb48ff85..7981feefd 100644 --- a/src/video_core/texture/texture_decode.cpp +++ b/src/video_core/texture/texture_decode.cpp @@ -233,7 +233,7 @@ void ConvertBGRToRGB(std::span source, std::span des void ConvertBGRToRGBA(std::span source, std::span dest) { u32 j = 0; - for (u32 i = 0; i < source.size(); i += 3) { + for (std::size_t i = 0; i < source.size(); i += 3) { dest[j] = source[i + 2]; dest[j + 1] = source[i + 1]; dest[j + 2] = source[i];