diff --git a/android/app/src/main/res/values/arrays.xml b/android/app/src/main/res/values/arrays.xml index 9fc39365c..6fdd9cb48 100644 --- a/android/app/src/main/res/values/arrays.xml +++ b/android/app/src/main/res/values/arrays.xml @@ -450,4 +450,14 @@ light dark + + Disabled + Box (Downsample 3D/Smooth All) + Adaptive (Preserve 3D/Smooth 2D) + + + Disabled + Box + Adaptive + diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml index f0aac9365..d303900c1 100644 --- a/android/app/src/main/res/values/strings.xml +++ b/android/app/src/main/res/values/strings.xml @@ -182,4 +182,5 @@ Allows you to use the analog sticks to control the d-pad in digital mode, as well as the buttons. Disable All Enhancements Temporarily disables all enhancements, which can be useful when debugging issues. + Downsampling diff --git a/android/app/src/main/res/xml/display_preferences.xml b/android/app/src/main/res/xml/display_preferences.xml index ca642fc95..a2a079d10 100644 --- a/android/app/src/main/res/xml/display_preferences.xml +++ b/android/app/src/main/res/xml/display_preferences.xml @@ -34,6 +34,15 @@ app:useSimpleSummaryProvider="true" app:iconSpaceReserved="false" /> + + AddFormattedOSDMessage( - 20.0f, g_host_interface->TranslateString("OSDMessage", "Texture filter '%s' is not supported on your device."), + 20.0f, + g_host_interface->TranslateString("OSDMessage", + "Texture filter '%s' is not supported with the current renderer."), Settings::GetTextureFilterDisplayName(m_texture_filtering)); m_texture_filtering = GPUTextureFilter::Nearest; } + if (!m_supports_adaptive_downsampling && g_settings.gpu_resolution_scale > 1 && + g_settings.gpu_downsample_mode == GPUDownsampleMode::Adaptive) + { + g_host_interface->AddOSDMessage( + g_host_interface->TranslateStdString( + "OSDMessage", "Adaptive downsampling is not supported with the current renderer, using box filter instead."), + 20.0f); + } m_pgxp_depth_buffer = g_settings.gpu_pgxp_depth_buffer; PrintSettingsToLog(); @@ -117,15 +128,17 @@ void GPU_HW::UpdateHWSettings(bool* framebuffer_changed, bool* shaders_changed) const u32 resolution_scale = CalculateResolutionScale(); const u32 multisamples = std::min(m_max_multisamples, g_settings.gpu_multisamples); const bool per_sample_shading = g_settings.gpu_per_sample_shading && m_supports_per_sample_shading; + const GPUDownsampleMode downsample_mode = GetDownsampleMode(resolution_scale); const bool use_uv_limits = ShouldUseUVLimits(); - *framebuffer_changed = (m_resolution_scale != resolution_scale || m_multisamples != multisamples); + *framebuffer_changed = + (m_resolution_scale != resolution_scale || m_multisamples != multisamples || m_downsample_mode != downsample_mode); *shaders_changed = (m_resolution_scale != resolution_scale || m_multisamples != multisamples || m_true_color != g_settings.gpu_true_color || m_per_sample_shading != per_sample_shading || m_scaled_dithering != g_settings.gpu_scaled_dithering || m_texture_filtering != g_settings.gpu_texture_filter || m_using_uv_limits != use_uv_limits || m_chroma_smoothing != g_settings.gpu_24bit_chroma_smoothing || - m_pgxp_depth_buffer != g_settings.UsingPGXPDepthBuffer()); + m_downsample_mode != downsample_mode || m_pgxp_depth_buffer != g_settings.UsingPGXPDepthBuffer()); if (m_resolution_scale != resolution_scale) { @@ -159,6 +172,7 @@ void GPU_HW::UpdateHWSettings(bool* framebuffer_changed, bool* shaders_changed) m_texture_filtering = g_settings.gpu_texture_filter; m_using_uv_limits = use_uv_limits; m_chroma_smoothing = g_settings.gpu_24bit_chroma_smoothing; + m_downsample_mode = downsample_mode; if (!m_supports_dual_source_blend && TextureFilterRequiresDualSourceBlend(m_texture_filtering)) m_texture_filtering = GPUTextureFilter::Nearest; @@ -176,16 +190,30 @@ void GPU_HW::UpdateHWSettings(bool* framebuffer_changed, bool* shaders_changed) u32 GPU_HW::CalculateResolutionScale() const { + u32 scale; if (g_settings.gpu_resolution_scale != 0) - return std::clamp(g_settings.gpu_resolution_scale, 1, m_max_resolution_scale); + { + scale = std::clamp(g_settings.gpu_resolution_scale, 1, m_max_resolution_scale); + } + else + { + // auto scaling + const s32 height = (m_crtc_state.display_height != 0) ? static_cast(m_crtc_state.display_height) : 480; + const s32 preferred_scale = + static_cast(std::ceil(static_cast(m_host_display->GetWindowHeight()) / height)); + Log_InfoPrintf("Height = %d, preferred scale = %d", height, preferred_scale); - // auto scaling - const s32 height = (m_crtc_state.display_height != 0) ? static_cast(m_crtc_state.display_height) : 480; - const s32 preferred_scale = - static_cast(std::ceil(static_cast(m_host_display->GetWindowHeight()) / height)); - Log_InfoPrintf("Height = %d, preferred scale = %d", height, preferred_scale); + scale = static_cast(std::clamp(preferred_scale, 1, m_max_resolution_scale)); + } - return static_cast(std::clamp(preferred_scale, 1, m_max_resolution_scale)); + if (g_settings.gpu_downsample_mode == GPUDownsampleMode::Adaptive && m_supports_adaptive_downsampling && scale > 1 && + (scale % 2) != 0) + { + Log_InfoPrintf("Resolution scale %u not supported for adaptive smoothing, using %u", scale, (scale - 1)); + scale--; + } + + return scale; } void GPU_HW::UpdateResolutionScale() @@ -196,6 +224,17 @@ void GPU_HW::UpdateResolutionScale() UpdateSettings(); } +GPUDownsampleMode GPU_HW::GetDownsampleMode(u32 resolution_scale) const +{ + if (resolution_scale == 1) + return GPUDownsampleMode::Disabled; + + if (g_settings.gpu_downsample_mode == GPUDownsampleMode::Adaptive) + return m_supports_adaptive_downsampling ? GPUDownsampleMode::Adaptive : GPUDownsampleMode::Box; + + return g_settings.gpu_downsample_mode; +} + std::tuple GPU_HW::GetEffectiveDisplayResolution() { return std::make_tuple(m_crtc_state.display_vram_width * m_resolution_scale, @@ -213,6 +252,7 @@ void GPU_HW::PrintSettingsToLog() Log_InfoPrintf("Dual-source blending: %s", m_supports_dual_source_blend ? "Supported" : "Not supported"); Log_InfoPrintf("Using UV limits: %s", m_using_uv_limits ? "YES" : "NO"); Log_InfoPrintf("Depth buffer: %s", m_pgxp_depth_buffer ? "YES" : "NO"); + Log_InfoPrintf("Downsampling: %s", Settings::GetDownsampleModeDisplayName(m_downsample_mode)); } void GPU_HW::UpdateVRAMReadTexture() @@ -368,6 +408,36 @@ void GPU_HW::CheckForDepthClear(const BatchVertex* vertices, u32 num_vertices) m_last_depth_z = average_z; } +u32 GPU_HW::GetAdaptiveDownsamplingMipLevels() const +{ + u32 levels = 0; + u32 current_width = VRAM_WIDTH * m_resolution_scale; + while (current_width >= VRAM_WIDTH) + { + levels++; + current_width /= 2; + } + + return levels; +} + +GPU_HW::SmoothingUBOData GPU_HW::GetSmoothingUBO(u32 level, u32 left, u32 top, u32 width, u32 height, u32 tex_width, + u32 tex_height) const +{ + const float rcp_width = 1.0f / static_cast(tex_width >> level); + const float rcp_height = 1.0f / static_cast(tex_height >> level); + + SmoothingUBOData data; + data.min_uv[0] = static_cast(left >> level) * rcp_width; + data.min_uv[1] = static_cast(top >> level) * rcp_height; + data.max_uv[0] = static_cast((left + width) >> level) * rcp_width; + data.max_uv[1] = static_cast((top + height) >> level) * rcp_height; + data.rcp_size[0] = rcp_width; + data.rcp_size[1] = rcp_height; + + return data; +} + void GPU_HW::DrawLine(float x0, float y0, u32 col0, float x1, float y1, u32 col1, float depth) { const float dx = x1 - x0; diff --git a/src/core/gpu_hw.h b/src/core/gpu_hw.h index 8abf1121a..95fe8f0ee 100644 --- a/src/core/gpu_hw.h +++ b/src/core/gpu_hw.h @@ -188,8 +188,10 @@ protected: virtual void DrawBatchVertices(BatchRenderMode render_mode, u32 base_vertex, u32 num_vertices) = 0; u32 CalculateResolutionScale() const; + GPUDownsampleMode GetDownsampleMode(u32 resolution_scale) const; ALWAYS_INLINE bool IsUsingMultisampling() const { return m_multisamples > 1; } + ALWAYS_INLINE bool IsUsingDownsampling() const { return (m_downsample_mode != GPUDownsampleMode::Disabled); } void SetFullVRAMDirtyRectangle() { @@ -287,6 +289,21 @@ protected: void SetBatchDepthBuffer(bool enabled); void CheckForDepthClear(const BatchVertex* vertices, u32 num_vertices); + /// UBO data for adaptive smoothing. + struct SmoothingUBOData + { + float min_uv[2]; + float max_uv[2]; + float rcp_size[2]; + }; + + /// Returns the number of mipmap levels used for adaptive smoothing. + u32 GetAdaptiveDownsamplingMipLevels() const; + + /// Returns the UBO data for an adaptive smoothing pass. + SmoothingUBOData GetSmoothingUBO(u32 level, u32 left, u32 top, u32 width, u32 height, u32 tex_width, + u32 tex_height) const; + HeapArray m_vram_shadow; BatchVertex* m_batch_start_vertex_ptr = nullptr; @@ -301,13 +318,22 @@ protected: u32 m_max_resolution_scale = 1; u32 m_max_multisamples = 1; HostDisplay::RenderAPI m_render_api = HostDisplay::RenderAPI::None; - bool m_per_sample_shading = false; bool m_true_color = true; - bool m_scaled_dithering = false; + + union + { + BitField m_supports_per_sample_shading; + BitField m_supports_dual_source_blend; + BitField m_supports_adaptive_downsampling; + BitField m_per_sample_shading; + BitField m_scaled_dithering; + BitField m_chroma_smoothing; + + u8 bits = 0; + }; + GPUTextureFilter m_texture_filtering = GPUTextureFilter::Nearest; - bool m_chroma_smoothing = false; - bool m_supports_per_sample_shading = false; - bool m_supports_dual_source_blend = false; + GPUDownsampleMode m_downsample_mode = GPUDownsampleMode::Disabled; bool m_using_uv_limits = false; bool m_pgxp_depth_buffer = false; diff --git a/src/core/gpu_hw_d3d11.cpp b/src/core/gpu_hw_d3d11.cpp index d1379d6ae..c7204b628 100644 --- a/src/core/gpu_hw_d3d11.cpp +++ b/src/core/gpu_hw_d3d11.cpp @@ -179,6 +179,7 @@ void GPU_HW_D3D11::SetCapabilities() m_max_resolution_scale = max_texture_scale; m_supports_dual_source_blend = true; m_supports_per_sample_shading = (m_device->GetFeatureLevel() >= D3D_FEATURE_LEVEL_10_1); + m_supports_adaptive_downsampling = true; m_max_multisamples = 1; for (u32 multisamples = 2; multisamples < D3D11_MAX_MULTISAMPLE_SAMPLE_COUNT; multisamples++) @@ -226,6 +227,47 @@ bool GPU_HW_D3D11::CreateFramebuffer() if (FAILED(hr)) return false; + if (m_downsample_mode == GPUDownsampleMode::Adaptive) + { + const u32 levels = GetAdaptiveDownsamplingMipLevels(); + + if (!m_downsample_texture.Create(m_device.Get(), texture_width, texture_height, static_cast(levels), 1, + texture_format, D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET) || + !m_downsample_weight_texture.Create(m_device.Get(), texture_width >> (levels - 1), + texture_height >> (levels - 1), 1, 1, DXGI_FORMAT_R8_UNORM, + D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET)) + { + return false; + } + + m_downsample_mip_views.resize(levels); + for (u32 i = 0; i < levels; i++) + { + const CD3D11_SHADER_RESOURCE_VIEW_DESC srv_desc(m_downsample_texture, D3D11_SRV_DIMENSION_TEXTURE2D, + texture_format, i, 1); + const CD3D11_RENDER_TARGET_VIEW_DESC rtv_desc(m_downsample_texture, D3D11_RTV_DIMENSION_TEXTURE2D, texture_format, + i, 1); + + hr = m_device->CreateShaderResourceView(m_downsample_texture, &srv_desc, + m_downsample_mip_views[i].first.GetAddressOf()); + if (FAILED(hr)) + return false; + + hr = m_device->CreateRenderTargetView(m_downsample_texture, &rtv_desc, + m_downsample_mip_views[i].second.GetAddressOf()); + if (FAILED(hr)) + return false; + } + } + else if (m_downsample_mode == GPUDownsampleMode::Box) + { + if (!m_downsample_texture.Create(m_device.Get(), VRAM_WIDTH, VRAM_HEIGHT, 1, 1, texture_format, + D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET)) + { + return false; + } + } + m_context->OMSetRenderTargets(1, m_vram_texture.GetD3DRTVArray(), nullptr); SetFullVRAMDirtyRectangle(); return true; @@ -243,6 +285,10 @@ void GPU_HW_D3D11::ClearFramebuffer() void GPU_HW_D3D11::DestroyFramebuffer() { + m_downsample_mip_views.clear(); + m_downsample_weight_texture.Destroy(); + m_downsample_texture.Destroy(); + m_vram_read_texture.Destroy(); m_vram_depth_view.Reset(); m_vram_depth_texture.Destroy(); @@ -351,6 +397,11 @@ bool GPU_HW_D3D11::CreateStateObjects() if (FAILED(hr)) return false; + sampler_desc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR; + hr = m_device->CreateSamplerState(&sampler_desc, m_trilinear_sampler_state.ReleaseAndGetAddressOf()); + if (FAILED(hr)) + return false; + for (u8 transparency_mode = 0; transparency_mode < 5; transparency_mode++) { bl_desc = CD3D11_BLEND_DESC(CD3D11_DEFAULT()); @@ -382,6 +433,7 @@ void GPU_HW_D3D11::DestroyStateObjects() m_batch_blend_states = {}; m_linear_sampler_state.Reset(); m_point_sampler_state.Reset(); + m_trilinear_sampler_state.Reset(); m_blend_no_color_writes_state.Reset(); m_blend_disabled_state.Reset(); m_depth_test_greater_state.Reset(); @@ -403,7 +455,7 @@ bool GPU_HW_D3D11::CompileShaders() m_pgxp_depth_buffer, m_supports_dual_source_blend); Common::Timer compile_time; - const int progress_total = 1 + 1 + 2 + (4 * 9 * 2 * 2) + 7 + (2 * 3); + const int progress_total = 1 + 1 + 2 + (4 * 9 * 2 * 2) + 7 + (2 * 3) + 1; int progress_value = 0; #define UPDATE_PROGRESS() \ do \ @@ -446,7 +498,8 @@ bool GPU_HW_D3D11::CompileShaders() m_screen_quad_vertex_shader = shader_cache.GetVertexShader(m_device.Get(), shadergen.GenerateScreenQuadVertexShader()); - if (!m_screen_quad_vertex_shader) + m_uv_quad_vertex_shader = shader_cache.GetVertexShader(m_device.Get(), shadergen.GenerateUVQuadVertexShader()); + if (!m_screen_quad_vertex_shader || !m_uv_quad_vertex_shader) return false; UPDATE_PROGRESS(); @@ -546,6 +599,33 @@ bool GPU_HW_D3D11::CompileShaders() UPDATE_PROGRESS(); + if (m_downsample_mode == GPUDownsampleMode::Adaptive) + { + m_downsample_first_pass_pixel_shader = + shader_cache.GetPixelShader(m_device.Get(), shadergen.GenerateAdaptiveDownsampleMipFragmentShader(true)); + m_downsample_mid_pass_pixel_shader = + shader_cache.GetPixelShader(m_device.Get(), shadergen.GenerateAdaptiveDownsampleMipFragmentShader(false)); + m_downsample_blur_pass_pixel_shader = + shader_cache.GetPixelShader(m_device.Get(), shadergen.GenerateAdaptiveDownsampleBlurFragmentShader()); + m_downsample_composite_pixel_shader = + shader_cache.GetPixelShader(m_device.Get(), shadergen.GenerateAdaptiveDownsampleCompositeFragmentShader()); + + if (!m_downsample_first_pass_pixel_shader || !m_downsample_mid_pass_pixel_shader || + !m_downsample_blur_pass_pixel_shader || !m_downsample_composite_pixel_shader) + { + return false; + } + } + else if (m_downsample_mode == GPUDownsampleMode::Box) + { + m_downsample_first_pass_pixel_shader = + shader_cache.GetPixelShader(m_device.Get(), shadergen.GenerateBoxSampleDownsampleFragmentShader()); + if (!m_downsample_first_pass_pixel_shader) + return false; + } + + UPDATE_PROGRESS(); + #undef UPDATE_PROGRESS return true; @@ -553,6 +633,10 @@ bool GPU_HW_D3D11::CompileShaders() void GPU_HW_D3D11::DestroyShaders() { + m_downsample_composite_pixel_shader.Reset(); + m_downsample_blur_pass_pixel_shader.Reset(); + m_downsample_mid_pass_pixel_shader.Reset(); + m_downsample_first_pass_pixel_shader.Reset(); m_display_pixel_shaders = {}; m_vram_update_depth_pixel_shader.Reset(); m_vram_copy_pixel_shader.Reset(); @@ -561,6 +645,7 @@ void GPU_HW_D3D11::DestroyShaders() m_vram_interlaced_fill_pixel_shader.Reset(); m_vram_fill_pixel_shader.Reset(); m_copy_pixel_shader.Reset(); + m_uv_quad_vertex_shader.Reset(); m_screen_quad_vertex_shader.Reset(); m_batch_pixel_shaders = {}; m_batch_vertex_shaders = {}; @@ -747,9 +832,18 @@ void GPU_HW_D3D11::UpdateDisplay() !IsUsingMultisampling() && (scaled_vram_offset_x + scaled_display_width) <= m_vram_texture.GetWidth() && (scaled_vram_offset_y + scaled_display_height) <= m_vram_texture.GetHeight()) { - m_host_display->SetDisplayTexture(m_vram_texture.GetD3DSRV(), HostDisplayPixelFormat::RGBA8, - m_vram_texture.GetWidth(), m_vram_texture.GetHeight(), scaled_vram_offset_x, - scaled_vram_offset_y, scaled_display_width, scaled_display_height); + + if (IsUsingDownsampling()) + { + DownsampleFramebuffer(m_vram_texture, scaled_vram_offset_x, scaled_vram_offset_y, scaled_display_width, + scaled_display_height); + } + else + { + m_host_display->SetDisplayTexture(m_vram_texture.GetD3DSRV(), HostDisplayPixelFormat::RGBA8, + m_vram_texture.GetWidth(), m_vram_texture.GetHeight(), scaled_vram_offset_x, + scaled_vram_offset_y, scaled_display_width, scaled_display_height); + } } else { @@ -769,9 +863,16 @@ void GPU_HW_D3D11::UpdateDisplay() SetViewportAndScissor(0, 0, scaled_display_width, scaled_display_height); DrawUtilityShader(display_pixel_shader, uniforms, sizeof(uniforms)); - m_host_display->SetDisplayTexture(m_display_texture.GetD3DSRV(), HostDisplayPixelFormat::RGBA8, - m_display_texture.GetWidth(), m_display_texture.GetHeight(), 0, 0, - scaled_display_width, scaled_display_height); + if (IsUsingDownsampling()) + { + DownsampleFramebuffer(m_display_texture, 0, 0, scaled_display_width, scaled_display_height); + } + else + { + m_host_display->SetDisplayTexture(m_display_texture.GetD3DSRV(), HostDisplayPixelFormat::RGBA8, + m_display_texture.GetWidth(), m_display_texture.GetHeight(), 0, 0, + scaled_display_width, scaled_display_height); + } RestoreGraphicsAPIState(); } @@ -972,6 +1073,103 @@ void GPU_HW_D3D11::ClearDepthBuffer() m_last_depth_z = 1.0f; } +void GPU_HW_D3D11::DownsampleFramebuffer(D3D11::Texture& source, u32 left, u32 top, u32 width, u32 height) +{ + if (m_downsample_mode == GPUDownsampleMode::Adaptive) + DownsampleFramebufferAdaptive(source, left, top, width, height); + else + DownsampleFramebufferBoxFilter(source, left, top, width, height); +} + +void GPU_HW_D3D11::DownsampleFramebufferAdaptive(D3D11::Texture& source, u32 left, u32 top, u32 width, u32 height) +{ + CD3D11_BOX src_box(left, top, 0, left + width, top + height, 1); + m_context->OMSetDepthStencilState(m_depth_disabled_state.Get(), 0); + m_context->OMSetBlendState(m_blend_disabled_state.Get(), nullptr, 0xFFFFFFFFu); + m_context->CopySubresourceRegion(m_downsample_texture, 0, left, top, 0, source, 0, &src_box); + m_context->PSSetSamplers(0, 1, m_point_sampler_state.GetAddressOf()); + m_context->VSSetShader(m_uv_quad_vertex_shader.Get(), nullptr, 0); + + // create mip chain + const u32 levels = m_downsample_texture.GetLevels(); + for (u32 level = 1; level < levels; level++) + { + SetViewportAndScissor(left >> level, top >> level, width >> level, height >> level); + m_context->OMSetRenderTargets(1, m_downsample_mip_views[level].second.GetAddressOf(), nullptr); + m_context->PSSetShaderResources(0, 1, m_downsample_mip_views[level - 1].first.GetAddressOf()); + + const SmoothingUBOData ubo = GetSmoothingUBO(level, left, top, width, height, m_downsample_texture.GetWidth(), + m_downsample_texture.GetHeight()); + m_context->PSSetShader( + (level == 1) ? m_downsample_first_pass_pixel_shader.Get() : m_downsample_mid_pass_pixel_shader.Get(), nullptr, 0); + UploadUniformBuffer(&ubo, sizeof(ubo)); + m_context->Draw(3, 0); + } + + // blur pass at lowest level + { + const u32 last_level = levels - 1; + + SetViewportAndScissor(left >> last_level, top >> last_level, width >> last_level, height >> last_level); + m_context->OMSetRenderTargets(1, m_downsample_weight_texture.GetD3DRTVArray(), nullptr); + m_context->PSSetShaderResources(0, 1, m_downsample_mip_views.back().first.GetAddressOf()); + m_context->PSSetShader(m_downsample_blur_pass_pixel_shader.Get(), nullptr, 0); + + const SmoothingUBOData ubo = GetSmoothingUBO(last_level, left, top, width, height, m_downsample_texture.GetWidth(), + m_downsample_texture.GetHeight()); + m_context->PSSetShader(m_downsample_blur_pass_pixel_shader.Get(), nullptr, 0); + UploadUniformBuffer(&ubo, sizeof(ubo)); + m_context->Draw(3, 0); + } + + // composite downsampled and upsampled images together + { + SetViewportAndScissor(left, top, width, height); + m_context->OMSetRenderTargets(1, m_display_texture.GetD3DRTVArray(), nullptr); + + ID3D11ShaderResourceView* const srvs[2] = {m_downsample_texture.GetD3DSRV(), + m_downsample_weight_texture.GetD3DSRV()}; + ID3D11SamplerState* const samplers[2] = {m_trilinear_sampler_state.Get(), m_linear_sampler_state.Get()}; + m_context->PSSetShaderResources(0, countof(srvs), srvs); + m_context->PSSetSamplers(0, countof(samplers), samplers); + m_context->PSSetShader(m_downsample_composite_pixel_shader.Get(), nullptr, 0); + m_context->Draw(3, 0); + } + + ID3D11ShaderResourceView* const null_srvs[2] = {}; + m_context->PSSetShaderResources(0, countof(null_srvs), null_srvs); + m_batch_ubo_dirty = true; + + RestoreGraphicsAPIState(); + + m_host_display->SetDisplayTexture(m_display_texture.GetD3DSRV(), HostDisplayPixelFormat::RGBA8, + m_display_texture.GetWidth(), m_display_texture.GetHeight(), left, top, width, + height); +} + +void GPU_HW_D3D11::DownsampleFramebufferBoxFilter(D3D11::Texture& source, u32 left, u32 top, u32 width, u32 height) +{ + const u32 ds_left = left / m_resolution_scale; + const u32 ds_top = top / m_resolution_scale; + const u32 ds_width = width / m_resolution_scale; + const u32 ds_height = height / m_resolution_scale; + + m_context->OMSetDepthStencilState(m_depth_disabled_state.Get(), 0); + m_context->OMSetRenderTargets(1, m_downsample_texture.GetD3DRTVArray(), nullptr); + m_context->OMSetBlendState(m_blend_disabled_state.Get(), nullptr, 0xFFFFFFFFu); + m_context->VSSetShader(m_screen_quad_vertex_shader.Get(), nullptr, 0); + m_context->PSSetShader(m_downsample_first_pass_pixel_shader.Get(), nullptr, 0); + m_context->PSSetShaderResources(0, 1, source.GetD3DSRVArray()); + SetViewportAndScissor(ds_left, ds_top, ds_width, ds_height); + m_context->Draw(3, 0); + + RestoreGraphicsAPIState(); + + m_host_display->SetDisplayTexture(m_downsample_texture.GetD3DSRV(), HostDisplayPixelFormat::RGBA8, + m_downsample_texture.GetWidth(), m_downsample_texture.GetHeight(), ds_left, ds_top, + ds_width, ds_height); +} + std::unique_ptr GPU::CreateHardwareD3D11Renderer() { return std::make_unique(); diff --git a/src/core/gpu_hw_d3d11.h b/src/core/gpu_hw_d3d11.h index bab3afd9a..d343e75f4 100644 --- a/src/core/gpu_hw_d3d11.h +++ b/src/core/gpu_hw_d3d11.h @@ -71,6 +71,10 @@ private: bool BlitVRAMReplacementTexture(const TextureReplacementTexture* tex, u32 dst_x, u32 dst_y, u32 width, u32 height); + void DownsampleFramebuffer(D3D11::Texture& source, u32 left, u32 top, u32 width, u32 height); + void DownsampleFramebufferAdaptive(D3D11::Texture& source, u32 left, u32 top, u32 width, u32 height); + void DownsampleFramebufferBoxFilter(D3D11::Texture& source, u32 left, u32 top, u32 width, u32 height); + ComPtr m_device; ComPtr m_context; @@ -105,6 +109,7 @@ private: ComPtr m_point_sampler_state; ComPtr m_linear_sampler_state; + ComPtr m_trilinear_sampler_state; std::array, 5> m_batch_blend_states; // [transparency_mode] ComPtr m_batch_input_layout; @@ -113,6 +118,7 @@ private: m_batch_pixel_shaders; // [render_mode][texture_mode][dithering][interlacing] ComPtr m_screen_quad_vertex_shader; + ComPtr m_uv_quad_vertex_shader; ComPtr m_copy_pixel_shader; ComPtr m_vram_fill_pixel_shader; ComPtr m_vram_interlaced_fill_pixel_shader; @@ -123,4 +129,13 @@ private: std::array, 3>, 2> m_display_pixel_shaders; // [depth_24][interlaced] D3D11::Texture m_vram_replacement_texture; + + // downsampling + ComPtr m_downsample_first_pass_pixel_shader; + ComPtr m_downsample_mid_pass_pixel_shader; + ComPtr m_downsample_blur_pass_pixel_shader; + ComPtr m_downsample_composite_pixel_shader; + D3D11::Texture m_downsample_texture; + D3D11::Texture m_downsample_weight_texture; + std::vector, ComPtr>> m_downsample_mip_views; }; diff --git a/src/core/gpu_hw_opengl.cpp b/src/core/gpu_hw_opengl.cpp index a253a9474..5fa00ccc1 100644 --- a/src/core/gpu_hw_opengl.cpp +++ b/src/core/gpu_hw_opengl.cpp @@ -273,6 +273,9 @@ void GPU_HW_OpenGL::SetCapabilities(HostDisplay* host_display) m_max_resolution_scale = std::min(m_max_resolution_scale, line_width_range[1]); } + + // adaptive smoothing would require texture views, which aren't in GLES. + m_supports_adaptive_downsampling = false; } bool GPU_HW_OpenGL::CreateFramebuffer() @@ -307,6 +310,15 @@ bool GPU_HW_OpenGL::CreateFramebuffer() m_vram_depth_texture.GetGLId(), 0); Assert(glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE); + if (m_downsample_mode == GPUDownsampleMode::Box) + { + if (!m_downsample_texture.Create(VRAM_WIDTH, VRAM_HEIGHT, 1, GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE) || + !m_downsample_texture.CreateFramebuffer()) + { + return false; + } + } + SetFullVRAMDirtyRectangle(); return true; } @@ -394,7 +406,7 @@ bool GPU_HW_OpenGL::CompilePrograms() m_pgxp_depth_buffer, m_supports_dual_source_blend); Common::Timer compile_time; - const int progress_total = (4 * 9 * 2 * 2) + (2 * 3) + 5; + const int progress_total = (4 * 9 * 2 * 2) + (2 * 3) + 6; int progress_value = 0; #define UPDATE_PROGRESS() \ do \ @@ -579,6 +591,28 @@ bool GPU_HW_OpenGL::CompilePrograms() } UPDATE_PROGRESS(); + + if (m_downsample_mode == GPUDownsampleMode::Box) + { + prog = shader_cache.GetProgram(shadergen.GenerateScreenQuadVertexShader(), {}, + shadergen.GenerateBoxSampleDownsampleFragmentShader(), + [this, use_binding_layout](GL::Program& prog) { + if (!IsGLES() && !use_binding_layout) + prog.BindFragData(0, "o_col0"); + }); + if (!prog) + return false; + + if (!use_binding_layout) + { + prog->Bind(); + prog->Uniform1i("samp0", 0); + } + + m_downsample_program = std::move(*prog); + } + + UPDATE_PROGRESS(); #undef UPDATE_PROGRESS return true; @@ -748,11 +782,19 @@ void GPU_HW_OpenGL::UpdateDisplay() !IsUsingMultisampling() && (scaled_vram_offset_x + scaled_display_width) <= m_vram_texture.GetWidth() && (scaled_vram_offset_y + scaled_display_height) <= m_vram_texture.GetHeight()) { - m_host_display->SetDisplayTexture(reinterpret_cast(static_cast(m_vram_texture.GetGLId())), - HostDisplayPixelFormat::RGBA8, m_vram_texture.GetWidth(), - m_vram_texture.GetHeight(), scaled_vram_offset_x, - m_vram_texture.GetHeight() - scaled_vram_offset_y, scaled_display_width, - -static_cast(scaled_display_height)); + if (IsUsingDownsampling()) + { + DownsampleFramebuffer(m_vram_texture, scaled_vram_offset_x, scaled_vram_offset_y, scaled_display_width, + scaled_display_height); + } + else + { + m_host_display->SetDisplayTexture(reinterpret_cast(static_cast(m_vram_texture.GetGLId())), + HostDisplayPixelFormat::RGBA8, m_vram_texture.GetWidth(), + m_vram_texture.GetHeight(), scaled_vram_offset_x, + m_vram_texture.GetHeight() - scaled_vram_offset_y, scaled_display_width, + -static_cast(scaled_display_height)); + } } else { @@ -779,10 +821,17 @@ void GPU_HW_OpenGL::UpdateDisplay() glBindVertexArray(m_attributeless_vao_id); glDrawArrays(GL_TRIANGLES, 0, 3); - m_host_display->SetDisplayTexture(reinterpret_cast(static_cast(m_display_texture.GetGLId())), - HostDisplayPixelFormat::RGBA8, m_display_texture.GetWidth(), - m_display_texture.GetHeight(), 0, scaled_display_height, scaled_display_width, - -static_cast(scaled_display_height)); + if (IsUsingDownsampling()) + { + DownsampleFramebuffer(m_display_texture, 0, 0, scaled_display_width, scaled_display_height); + } + else + { + m_host_display->SetDisplayTexture(reinterpret_cast(static_cast(m_display_texture.GetGLId())), + HostDisplayPixelFormat::RGBA8, m_display_texture.GetWidth(), + m_display_texture.GetHeight(), 0, scaled_display_height, scaled_display_width, + -static_cast(scaled_display_height)); + } // restore state glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_vram_fbo_id); @@ -1136,6 +1185,37 @@ void GPU_HW_OpenGL::ClearDepthBuffer() m_last_depth_z = 1.0f; } +void GPU_HW_OpenGL::DownsampleFramebuffer(GL::Texture& source, u32 left, u32 top, u32 width, u32 height) +{ + DebugAssert(m_downsample_mode != GPUDownsampleMode::Adaptive); + DownsampleFramebufferBoxFilter(source, left, top, width, height); +} + +void GPU_HW_OpenGL::DownsampleFramebufferBoxFilter(GL::Texture& source, u32 left, u32 top, u32 width, u32 height) +{ + const u32 ds_left = left / m_resolution_scale; + const u32 ds_top = top / m_resolution_scale; + const u32 ds_width = width / m_resolution_scale; + const u32 ds_height = height / m_resolution_scale; + + glDisable(GL_BLEND); + glDisable(GL_DEPTH_TEST); + glDisable(GL_SCISSOR_TEST); + glViewport(ds_left, m_downsample_texture.GetHeight() - ds_top - ds_height, ds_width, ds_height); + glBindVertexArray(m_attributeless_vao_id); + source.Bind(); + m_downsample_texture.BindFramebuffer(GL_DRAW_FRAMEBUFFER); + m_downsample_program.Bind(); + glDrawArrays(GL_TRIANGLES, 0, 3); + + RestoreGraphicsAPIState(); + + m_host_display->SetDisplayTexture(reinterpret_cast(static_cast(m_downsample_texture.GetGLId())), + HostDisplayPixelFormat::RGBA8, m_downsample_texture.GetWidth(), + m_downsample_texture.GetHeight(), ds_left, + m_downsample_texture.GetHeight() - ds_top, ds_width, -static_cast(ds_height)); +} + std::unique_ptr GPU::CreateHardwareOpenGLRenderer() { return std::make_unique(); diff --git a/src/core/gpu_hw_opengl.h b/src/core/gpu_hw_opengl.h index 9c3255cbf..2f6d44834 100644 --- a/src/core/gpu_hw_opengl.h +++ b/src/core/gpu_hw_opengl.h @@ -69,6 +69,8 @@ private: void SetBlendMode(); bool BlitVRAMReplacementTexture(const TextureReplacementTexture* tex, u32 dst_x, u32 dst_y, u32 width, u32 height); + void DownsampleFramebuffer(GL::Texture& source, u32 left, u32 top, u32 width, u32 height); + void DownsampleFramebufferBoxFilter(GL::Texture& source, u32 left, u32 top, u32 width, u32 height); // downsample texture - used for readbacks at >1xIR. GL::Texture m_vram_texture; @@ -107,4 +109,7 @@ private: GLenum m_current_depth_test = 0; GPUTransparencyMode m_current_transparency_mode = GPUTransparencyMode::Disabled; BatchRenderMode m_current_render_mode = BatchRenderMode::TransparencyDisabled; + + GL::Texture m_downsample_texture; + GL::Program m_downsample_program; }; diff --git a/src/core/gpu_hw_shadergen.cpp b/src/core/gpu_hw_shadergen.cpp index 7152ef118..f24418631 100644 --- a/src/core/gpu_hw_shadergen.cpp +++ b/src/core/gpu_hw_shadergen.cpp @@ -1340,3 +1340,143 @@ std::string GPU_HW_ShaderGen::GenerateVRAMUpdateDepthFragmentShader() return ss.str(); } + +std::string GPU_HW_ShaderGen::GenerateAdaptiveDownsampleMipFragmentShader(bool first_pass) +{ + std::stringstream ss; + WriteHeader(ss); + WriteCommonFunctions(ss); + DeclareTexture(ss, "samp0", 0, false); + DeclareUniformBuffer(ss, { "float2 u_uv_min", "float2 u_uv_max", "float2 u_rcp_resolution" }, true); + DefineMacro(ss, "FIRST_PASS", first_pass); + + // mipmap_energy.glsl ported from parallel-rsx. + ss << R"( + +float4 get_bias(float3 c00, float3 c01, float3 c10, float3 c11) +{ + // Measure the "energy" (variance) in the pixels. + // If the pixels are all the same (2D content), use maximum bias, otherwise, taper off quickly back to 0 (edges) + float3 avg = 0.25 * (c00 + c01 + c10 + c11); + float s00 = dot(c00 - avg, c00 - avg); + float s01 = dot(c01 - avg, c01 - avg); + float s10 = dot(c10 - avg, c10 - avg); + float s11 = dot(c11 - avg, c11 - avg); + return float4(avg, 1.0 - log2(1000.0 * (s00 + s01 + s10 + s11) + 1.0)); +} + +float4 get_bias(float4 c00, float4 c01, float4 c10, float4 c11) +{ + // Measure the "energy" (variance) in the pixels. + // If the pixels are all the same (2D content), use maximum bias, otherwise, taper off quickly back to 0 (edges) + float avg = 0.25 * (c00.a + c01.a + c10.a + c11.a); + float4 bias = get_bias(c00.rgb, c01.rgb, c10.rgb, c11.rgb); + bias.a *= avg; + return bias; +} + +)"; + + DeclareFragmentEntryPoint(ss, 0, 1, {}, false, 1, false, false, false, false); + ss << R"( +{ + float2 uv = v_tex0 - (u_rcp_resolution * 0.25); +#ifdef FIRST_PASS + vec3 c00 = SAMPLE_TEXTURE_OFFSET(samp0, uv, int2(0, 0)).rgb; + vec3 c01 = SAMPLE_TEXTURE_OFFSET(samp0, uv, int2(0, 1)).rgb; + vec3 c10 = SAMPLE_TEXTURE_OFFSET(samp0, uv, int2(1, 0)).rgb; + vec3 c11 = SAMPLE_TEXTURE_OFFSET(samp0, uv, int2(1, 1)).rgb; + o_col0 = get_bias(c00, c01, c10, c11); +#else + vec4 c00 = SAMPLE_TEXTURE_OFFSET(samp0, uv, int2(0, 0)); + vec4 c01 = SAMPLE_TEXTURE_OFFSET(samp0, uv, int2(0, 1)); + vec4 c10 = SAMPLE_TEXTURE_OFFSET(samp0, uv, int2(1, 0)); + vec4 c11 = SAMPLE_TEXTURE_OFFSET(samp0, uv, int2(1, 1)); + o_col0 = get_bias(c00, c01, c10, c11); +#endif +} +)"; + + return ss.str(); +} + +std::string GPU_HW_ShaderGen::GenerateAdaptiveDownsampleBlurFragmentShader() +{ + std::stringstream ss; + WriteHeader(ss); + WriteCommonFunctions(ss); + DeclareTexture(ss, "samp0", 0, false); + DeclareUniformBuffer(ss, {"float2 u_uv_min", "float2 u_uv_max", "float2 u_rcp_resolution", "float sample_level"}, true); + + // mipmap_blur.glsl ported from parallel-rsx. + DeclareFragmentEntryPoint(ss, 0, 1, {}, false, 1, false, false, false, false); + ss << R"( +{ + float bias = 0.0; + const float w0 = 0.25; + const float w1 = 0.125; + const float w2 = 0.0625; +#define UV(x, y) clamp((v_tex0 + float2(x, y) * u_rcp_resolution), u_uv_min, u_uv_max) + bias += w2 * SAMPLE_TEXTURE(samp0, UV(-1.0, -1.0)).a; + bias += w2 * SAMPLE_TEXTURE(samp0, UV(+1.0, -1.0)).a; + bias += w2 * SAMPLE_TEXTURE(samp0, UV(-1.0, +1.0)).a; + bias += w2 * SAMPLE_TEXTURE(samp0, UV(+1.0, +1.0)).a; + bias += w1 * SAMPLE_TEXTURE(samp0, UV( 0.0, -1.0)).a; + bias += w1 * SAMPLE_TEXTURE(samp0, UV(-1.0, 0.0)).a; + bias += w1 * SAMPLE_TEXTURE(samp0, UV(+1.0, 0.0)).a; + bias += w1 * SAMPLE_TEXTURE(samp0, UV( 0.0, +1.0)).a; + bias += w0 * SAMPLE_TEXTURE(samp0, UV( 0.0, 0.0)).a; + o_col0 = float4(bias, bias, bias, bias); +} +)"; + + return ss.str(); +} + +std::string GPU_HW_ShaderGen::GenerateAdaptiveDownsampleCompositeFragmentShader() +{ + std::stringstream ss; + WriteHeader(ss); + WriteCommonFunctions(ss); + DeclareTexture(ss, "samp0", 0, false); + DeclareTexture(ss, "samp1", 1, false); + + // mipmap_resolve.glsl ported from parallel-rsx. + DeclareFragmentEntryPoint(ss, 0, 1, {}, true, 1, false, false, false, false); + ss << R"( +{ + float2 uv = v_pos.xy * RCP_VRAM_SIZE; + float bias = SAMPLE_TEXTURE(samp1, uv).r; + float mip = float(RESOLUTION_SCALE - 1u) * bias; + float3 color = SAMPLE_TEXTURE_LEVEL(samp0, uv, mip).rgb; + o_col0 = float4(color, 1.0); +} +)"; + + return ss.str(); +} + +std::string GPU_HW_ShaderGen::GenerateBoxSampleDownsampleFragmentShader() +{ + std::stringstream ss; + WriteHeader(ss); + WriteCommonFunctions(ss); + DeclareTexture(ss, "samp0", 0, false); + + DeclareFragmentEntryPoint(ss, 0, 1, {}, true, 1, false, false, false, false); + ss << R"( +{ + float3 color = float3(0.0, 0.0, 0.0); + uint2 base_coords = uint2(v_pos.xy) * uint2(RESOLUTION_SCALE, RESOLUTION_SCALE); + for (uint offset_x = 0u; offset_x < RESOLUTION_SCALE; offset_x++) + { + for (uint offset_y = 0u; offset_y < RESOLUTION_SCALE; offset_y++) + color += LOAD_TEXTURE(samp0, int2(base_coords + uint2(offset_x, offset_y)), 0).rgb; + } + color /= float(RESOLUTION_SCALE * RESOLUTION_SCALE); + o_col0 = float4(color, 1.0); +} +)"; + + return ss.str(); +} diff --git a/src/core/gpu_hw_shadergen.h b/src/core/gpu_hw_shadergen.h index 842257765..dff617e97 100644 --- a/src/core/gpu_hw_shadergen.h +++ b/src/core/gpu_hw_shadergen.h @@ -21,6 +21,11 @@ public: std::string GenerateVRAMCopyFragmentShader(); std::string GenerateVRAMUpdateDepthFragmentShader(); + std::string GenerateAdaptiveDownsampleMipFragmentShader(bool first_pass); + std::string GenerateAdaptiveDownsampleBlurFragmentShader(); + std::string GenerateAdaptiveDownsampleCompositeFragmentShader(); + std::string GenerateBoxSampleDownsampleFragmentShader(); + private: ALWAYS_INLINE bool UsingMSAA() const { return m_multisamples > 1; } ALWAYS_INLINE bool UsingPerSampleShading() const { return m_multisamples > 1 && m_per_sample_shading; } diff --git a/src/core/gpu_hw_vulkan.cpp b/src/core/gpu_hw_vulkan.cpp index 32e5e475e..10f8d4597 100644 --- a/src/core/gpu_hw_vulkan.cpp +++ b/src/core/gpu_hw_vulkan.cpp @@ -243,6 +243,8 @@ void GPU_HW_Vulkan::SetCapabilities() m_supports_dual_source_blend = g_vulkan_context->GetDeviceFeatures().dualSrcBlend; m_supports_per_sample_shading = g_vulkan_context->GetDeviceFeatures().sampleRateShading; + m_supports_adaptive_downsampling = true; + Log_InfoPrintf("Dual-source blend: %s", m_supports_dual_source_blend ? "supported" : "not supported"); Log_InfoPrintf("Per-sample shading: %s", m_supports_per_sample_shading ? "supported" : "not supported"); Log_InfoPrintf("Max multisamples: %u", m_max_multisamples); @@ -270,6 +272,10 @@ void GPU_HW_Vulkan::DestroyResources() DestroyFramebuffer(); DestroyPipelines(); + Vulkan::Util::SafeDestroyPipelineLayout(m_downsample_pipeline_layout); + Vulkan::Util::SafeDestroyDescriptorSetLayout(m_downsample_composite_descriptor_set_layout); + Vulkan::Util::SafeDestroyPipelineLayout(m_downsample_composite_pipeline_layout); + Vulkan::Util::SafeFreeGlobalDescriptorSet(m_vram_write_descriptor_set); Vulkan::Util::SafeDestroyBufferView(m_texture_stream_buffer_view); @@ -286,6 +292,7 @@ void GPU_HW_Vulkan::DestroyResources() Vulkan::Util::SafeDestroyDescriptorSetLayout(m_batch_descriptor_set_layout); Vulkan::Util::SafeDestroySampler(m_point_sampler); Vulkan::Util::SafeDestroySampler(m_linear_sampler); + Vulkan::Util::SafeDestroySampler(m_trilinear_sampler); } void GPU_HW_Vulkan::BeginRenderPass(VkRenderPass render_pass, VkFramebuffer framebuffer, u32 x, u32 y, u32 width, @@ -371,6 +378,24 @@ bool GPU_HW_Vulkan::CreatePipelineLayouts() if (m_vram_write_pipeline_layout == VK_NULL_HANDLE) return false; + plbuilder.AddDescriptorSet(m_single_sampler_descriptor_set_layout); + plbuilder.AddPushConstants(VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, MAX_PUSH_CONSTANTS_SIZE); + m_downsample_pipeline_layout = plbuilder.Create(device); + if (m_downsample_pipeline_layout == VK_NULL_HANDLE) + return false; + + dslbuilder.AddBinding(1, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_FRAGMENT_BIT); + dslbuilder.AddBinding(2, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_FRAGMENT_BIT); + m_downsample_composite_descriptor_set_layout = dslbuilder.Create(device); + if (m_downsample_composite_descriptor_set_layout == VK_NULL_HANDLE) + return false; + + plbuilder.AddDescriptorSet(m_downsample_composite_descriptor_set_layout); + plbuilder.AddPushConstants(VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, MAX_PUSH_CONSTANTS_SIZE); + m_downsample_composite_pipeline_layout = plbuilder.Create(device); + if (m_downsample_composite_pipeline_layout == VK_NULL_HANDLE) + return false; + return true; } @@ -393,6 +418,11 @@ bool GPU_HW_Vulkan::CreateSamplers() if (m_linear_sampler == VK_NULL_HANDLE) return false; + sbuilder.SetLinearSampler(true, VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER); + m_trilinear_sampler = sbuilder.Create(device); + if (m_trilinear_sampler == VK_NULL_HANDLE) + return false; + return true; } @@ -447,16 +477,14 @@ bool GPU_HW_Vulkan::CreateFramebuffer() } // vram framebuffer has both colour and depth - { - Vulkan::FramebufferBuilder fbb; - fbb.AddAttachment(m_vram_texture.GetView()); - fbb.AddAttachment(m_vram_depth_texture.GetView()); - fbb.SetRenderPass(m_vram_render_pass); - fbb.SetSize(m_vram_texture.GetWidth(), m_vram_texture.GetHeight(), m_vram_texture.GetLayers()); - m_vram_framebuffer = fbb.Create(g_vulkan_context->GetDevice()); - if (m_vram_framebuffer == VK_NULL_HANDLE) - return false; - } + Vulkan::FramebufferBuilder fbb; + fbb.AddAttachment(m_vram_texture.GetView()); + fbb.AddAttachment(m_vram_depth_texture.GetView()); + fbb.SetRenderPass(m_vram_render_pass); + fbb.SetSize(m_vram_texture.GetWidth(), m_vram_texture.GetHeight(), m_vram_texture.GetLayers()); + m_vram_framebuffer = fbb.Create(g_vulkan_context->GetDevice()); + if (m_vram_framebuffer == VK_NULL_HANDLE) + return false; m_vram_update_depth_framebuffer = m_vram_depth_texture.CreateFramebuffer(m_vram_update_depth_render_pass); m_vram_readback_framebuffer = m_vram_readback_texture.CreateFramebuffer(m_vram_readback_render_pass); @@ -477,8 +505,9 @@ bool GPU_HW_Vulkan::CreateFramebuffer() m_batch_descriptor_set = g_vulkan_context->AllocateGlobalDescriptorSet(m_batch_descriptor_set_layout); m_vram_copy_descriptor_set = g_vulkan_context->AllocateGlobalDescriptorSet(m_single_sampler_descriptor_set_layout); m_vram_read_descriptor_set = g_vulkan_context->AllocateGlobalDescriptorSet(m_single_sampler_descriptor_set_layout); + m_display_descriptor_set = g_vulkan_context->AllocateGlobalDescriptorSet(m_single_sampler_descriptor_set_layout); if (m_batch_descriptor_set == VK_NULL_HANDLE || m_vram_copy_descriptor_set == VK_NULL_HANDLE || - m_vram_read_descriptor_set == VK_NULL_HANDLE) + m_vram_read_descriptor_set == VK_NULL_HANDLE || m_display_descriptor_set == VK_NULL_HANDLE) { return false; } @@ -491,8 +520,109 @@ bool GPU_HW_Vulkan::CreateFramebuffer() m_point_sampler, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); dsubuilder.AddCombinedImageSamplerDescriptorWrite(m_vram_read_descriptor_set, 1, m_vram_texture.GetView(), m_point_sampler, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); + dsubuilder.AddCombinedImageSamplerDescriptorWrite(m_display_descriptor_set, 1, m_display_texture.GetView(), + m_point_sampler, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); dsubuilder.Update(g_vulkan_context->GetDevice()); + if (m_downsample_mode == GPUDownsampleMode::Adaptive) + { + const u32 levels = GetAdaptiveDownsamplingMipLevels(); + + if (!m_downsample_texture.Create(texture_width, texture_height, levels, 1, texture_format, VK_SAMPLE_COUNT_1_BIT, + VK_IMAGE_VIEW_TYPE_2D, VK_IMAGE_TILING_OPTIMAL, + VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT | + VK_IMAGE_USAGE_TRANSFER_DST_BIT) || + !m_downsample_weight_texture.Create(VRAM_WIDTH, VRAM_HEIGHT, 1, 1, VK_FORMAT_R8_UNORM, VK_SAMPLE_COUNT_1_BIT, + VK_IMAGE_VIEW_TYPE_2D, VK_IMAGE_TILING_OPTIMAL, + VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT)) + { + return false; + } + + m_downsample_texture.TransitionToLayout(cmdbuf, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); + + m_downsample_render_pass = g_vulkan_context->GetRenderPass(m_downsample_texture.GetFormat(), VK_FORMAT_UNDEFINED, + VK_SAMPLE_COUNT_1_BIT, VK_ATTACHMENT_LOAD_OP_DONT_CARE); + m_downsample_weight_render_pass = + g_vulkan_context->GetRenderPass(m_downsample_weight_texture.GetFormat(), VK_FORMAT_UNDEFINED, + VK_SAMPLE_COUNT_1_BIT, VK_ATTACHMENT_LOAD_OP_DONT_CARE); + if (m_downsample_render_pass == VK_NULL_HANDLE || m_downsample_weight_render_pass == VK_NULL_HANDLE) + return false; + + m_downsample_weight_framebuffer = m_downsample_weight_texture.CreateFramebuffer(m_downsample_weight_render_pass); + if (m_downsample_weight_framebuffer == VK_NULL_HANDLE) + return false; + + m_downsample_mip_views.resize(levels); + for (u32 i = 0; i < levels; i++) + { + SmoothMipView& mv = m_downsample_mip_views[i]; + + const VkImageViewCreateInfo vci = {VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, + nullptr, + 0, + m_downsample_texture.GetImage(), + VK_IMAGE_VIEW_TYPE_2D, + m_downsample_texture.GetFormat(), + {VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY, + VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY}, + {VK_IMAGE_ASPECT_COLOR_BIT, i, 1u, 0u, 1u}}; + VkResult res = vkCreateImageView(g_vulkan_context->GetDevice(), &vci, nullptr, &mv.image_view); + if (res != VK_SUCCESS) + { + LOG_VULKAN_ERROR(res, "vkCreateImageView() for smooth mip failed: "); + return false; + } + + mv.descriptor_set = g_vulkan_context->AllocateGlobalDescriptorSet(m_single_sampler_descriptor_set_layout); + if (mv.descriptor_set == VK_NULL_HANDLE) + return false; + + dsubuilder.AddCombinedImageSamplerDescriptorWrite(m_downsample_mip_views[i].descriptor_set, 1, + m_downsample_mip_views[i].image_view, m_point_sampler, + VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); + + fbb.AddAttachment(mv.image_view); + fbb.SetRenderPass(m_downsample_render_pass); + fbb.SetSize(texture_width >> i, texture_height >> i, 1); + mv.framebuffer = fbb.Create(g_vulkan_context->GetDevice()); + if (mv.framebuffer == VK_NULL_HANDLE) + return false; + } + + m_downsample_composite_descriptor_set = + g_vulkan_context->AllocateGlobalDescriptorSet(m_downsample_composite_descriptor_set_layout); + if (m_downsample_composite_descriptor_set_layout == VK_NULL_HANDLE) + return false; + + dsubuilder.AddCombinedImageSamplerDescriptorWrite(m_downsample_composite_descriptor_set, 1, + m_downsample_texture.GetView(), m_trilinear_sampler, + VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); + dsubuilder.AddCombinedImageSamplerDescriptorWrite(m_downsample_composite_descriptor_set, 2, + m_downsample_weight_texture.GetView(), m_linear_sampler, + VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); + + dsubuilder.Update(g_vulkan_context->GetDevice()); + } + else if (m_downsample_mode == GPUDownsampleMode::Box) + { + if (!m_downsample_texture.Create(VRAM_WIDTH, VRAM_HEIGHT, 1, 1, texture_format, VK_SAMPLE_COUNT_1_BIT, + VK_IMAGE_VIEW_TYPE_2D, VK_IMAGE_TILING_OPTIMAL, + VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT | + VK_IMAGE_USAGE_TRANSFER_SRC_BIT)) + { + return false; + } + + m_downsample_render_pass = g_vulkan_context->GetRenderPass(m_downsample_texture.GetFormat(), VK_FORMAT_UNDEFINED, + VK_SAMPLE_COUNT_1_BIT, VK_ATTACHMENT_LOAD_OP_DONT_CARE); + + m_downsample_mip_views.resize(1); + m_downsample_mip_views[0].framebuffer = m_downsample_texture.CreateFramebuffer(m_downsample_render_pass); + if (m_downsample_mip_views[0].framebuffer == VK_NULL_HANDLE) + return false; + } + ClearDisplay(); SetFullVRAMDirtyRectangle(); return true; @@ -522,9 +652,23 @@ void GPU_HW_Vulkan::ClearFramebuffer() void GPU_HW_Vulkan::DestroyFramebuffer() { + Vulkan::Util::SafeFreeGlobalDescriptorSet(m_downsample_composite_descriptor_set); + + for (SmoothMipView& mv : m_downsample_mip_views) + { + Vulkan::Util::SafeFreeGlobalDescriptorSet(mv.descriptor_set); + Vulkan::Util::SafeDestroyImageView(mv.image_view); + Vulkan::Util::SafeDestroyFramebuffer(mv.framebuffer); + } + m_downsample_mip_views.clear(); + m_downsample_texture.Destroy(false); + Vulkan::Util::SafeDestroyFramebuffer(m_downsample_weight_framebuffer); + m_downsample_weight_texture.Destroy(false); + Vulkan::Util::SafeFreeGlobalDescriptorSet(m_batch_descriptor_set); Vulkan::Util::SafeFreeGlobalDescriptorSet(m_vram_copy_descriptor_set); Vulkan::Util::SafeFreeGlobalDescriptorSet(m_vram_read_descriptor_set); + Vulkan::Util::SafeFreeGlobalDescriptorSet(m_display_descriptor_set); Vulkan::Util::SafeDestroyFramebuffer(m_vram_framebuffer); Vulkan::Util::SafeDestroyFramebuffer(m_vram_update_depth_framebuffer); @@ -601,7 +745,7 @@ bool GPU_HW_Vulkan::CompilePipelines() m_pgxp_depth_buffer, m_supports_dual_source_blend); Common::Timer compile_time; - const int progress_total = 2 + (4 * 9 * 2 * 2) + (2 * 4 * 5 * 9 * 2 * 2) + 1 + 2 + 2 + 2 + 2 + (2 * 3); + const int progress_total = 2 + (4 * 9 * 2 * 2) + (2 * 4 * 5 * 9 * 2 * 2) + 1 + 2 + 2 + 2 + 2 + (2 * 3) + 1; int progress_value = 0; #define UPDATE_PROGRESS() \ do \ @@ -738,11 +882,18 @@ bool GPU_HW_Vulkan::CompilePipelines() g_vulkan_shader_cache->GetVertexShader(shadergen.GenerateScreenQuadVertexShader()); if (fullscreen_quad_vertex_shader == VK_NULL_HANDLE) return false; + VkShaderModule uv_quad_vertex_shader = g_vulkan_shader_cache->GetVertexShader(shadergen.GenerateUVQuadVertexShader()); + if (uv_quad_vertex_shader == VK_NULL_HANDLE) + { + vkDestroyShaderModule(g_vulkan_context->GetDevice(), uv_quad_vertex_shader, nullptr); + return false; + } UPDATE_PROGRESS(); - Common::ScopeGuard fullscreen_quad_vertex_shader_guard([&fullscreen_quad_vertex_shader]() { + Common::ScopeGuard fullscreen_quad_vertex_shader_guard([&fullscreen_quad_vertex_shader, &uv_quad_vertex_shader]() { vkDestroyShaderModule(g_vulkan_context->GetDevice(), fullscreen_quad_vertex_shader, nullptr); + vkDestroyShaderModule(g_vulkan_context->GetDevice(), uv_quad_vertex_shader, nullptr); }); // common state @@ -907,6 +1058,87 @@ bool GPU_HW_Vulkan::CompilePipelines() } } + UPDATE_PROGRESS(); + + if (m_downsample_mode == GPUDownsampleMode::Adaptive) + { + gpbuilder.Clear(); + gpbuilder.SetRenderPass(m_downsample_render_pass, 0); + gpbuilder.SetPipelineLayout(m_downsample_pipeline_layout); + gpbuilder.SetVertexShader(uv_quad_vertex_shader); + gpbuilder.SetNoCullRasterizationState(); + gpbuilder.SetNoDepthTestState(); + gpbuilder.SetNoBlendingState(); + gpbuilder.SetDynamicViewportAndScissorState(); + + VkShaderModule fs = + g_vulkan_shader_cache->GetFragmentShader(shadergen.GenerateAdaptiveDownsampleMipFragmentShader(true)); + if (fs == VK_NULL_HANDLE) + return false; + + gpbuilder.SetFragmentShader(fs); + m_downsample_first_pass_pipeline = gpbuilder.Create(device, pipeline_cache, false); + vkDestroyShaderModule(g_vulkan_context->GetDevice(), fs, nullptr); + if (m_downsample_first_pass_pipeline == VK_NULL_HANDLE) + return false; + + fs = g_vulkan_shader_cache->GetFragmentShader(shadergen.GenerateAdaptiveDownsampleMipFragmentShader(false)); + if (fs == VK_NULL_HANDLE) + return false; + + gpbuilder.SetFragmentShader(fs); + m_downsample_mid_pass_pipeline = gpbuilder.Create(device, pipeline_cache, false); + vkDestroyShaderModule(g_vulkan_context->GetDevice(), fs, nullptr); + if (m_downsample_mid_pass_pipeline == VK_NULL_HANDLE) + return false; + + fs = g_vulkan_shader_cache->GetFragmentShader(shadergen.GenerateAdaptiveDownsampleBlurFragmentShader()); + if (fs == VK_NULL_HANDLE) + return false; + + gpbuilder.SetFragmentShader(fs); + gpbuilder.SetRenderPass(m_downsample_weight_render_pass, 0); + m_downsample_blur_pass_pipeline = gpbuilder.Create(device, pipeline_cache, false); + vkDestroyShaderModule(g_vulkan_context->GetDevice(), fs, nullptr); + if (m_downsample_blur_pass_pipeline == VK_NULL_HANDLE) + return false; + + fs = g_vulkan_shader_cache->GetFragmentShader(shadergen.GenerateAdaptiveDownsampleCompositeFragmentShader()); + if (fs == VK_NULL_HANDLE) + return false; + + gpbuilder.SetFragmentShader(fs); + gpbuilder.SetPipelineLayout(m_downsample_composite_pipeline_layout); + gpbuilder.SetRenderPass(m_display_render_pass, 0); + m_downsample_composite_pass_pipeline = gpbuilder.Create(device, pipeline_cache, false); + vkDestroyShaderModule(g_vulkan_context->GetDevice(), fs, nullptr); + if (m_downsample_composite_pass_pipeline == VK_NULL_HANDLE) + return false; + } + else if (m_downsample_mode == GPUDownsampleMode::Box) + { + gpbuilder.Clear(); + gpbuilder.SetRenderPass(m_downsample_render_pass, 0); + gpbuilder.SetPipelineLayout(m_single_sampler_pipeline_layout); + gpbuilder.SetVertexShader(fullscreen_quad_vertex_shader); + gpbuilder.SetNoCullRasterizationState(); + gpbuilder.SetNoDepthTestState(); + gpbuilder.SetNoBlendingState(); + gpbuilder.SetDynamicViewportAndScissorState(); + + VkShaderModule fs = g_vulkan_shader_cache->GetFragmentShader(shadergen.GenerateBoxSampleDownsampleFragmentShader()); + if (fs == VK_NULL_HANDLE) + return false; + + gpbuilder.SetFragmentShader(fs); + m_downsample_first_pass_pipeline = gpbuilder.Create(device, pipeline_cache, false); + vkDestroyShaderModule(g_vulkan_context->GetDevice(), fs, nullptr); + if (m_downsample_first_pass_pipeline == VK_NULL_HANDLE) + return false; + } + + UPDATE_PROGRESS(); + #undef UPDATE_PROGRESS return true; @@ -928,6 +1160,11 @@ void GPU_HW_Vulkan::DestroyPipelines() Vulkan::Util::SafeDestroyPipeline(m_vram_readback_pipeline); Vulkan::Util::SafeDestroyPipeline(m_vram_update_depth_pipeline); + Vulkan::Util::SafeDestroyPipeline(m_downsample_first_pass_pipeline); + Vulkan::Util::SafeDestroyPipeline(m_downsample_mid_pass_pipeline); + Vulkan::Util::SafeDestroyPipeline(m_downsample_blur_pass_pipeline); + Vulkan::Util::SafeDestroyPipeline(m_downsample_composite_pass_pipeline); + m_display_pipelines.enumerate(Vulkan::Util::SafeDestroyPipeline); } @@ -1014,11 +1251,19 @@ void GPU_HW_Vulkan::UpdateDisplay() !IsUsingMultisampling() && (scaled_vram_offset_x + scaled_display_width) <= m_vram_texture.GetWidth() && (scaled_vram_offset_y + scaled_display_height) <= m_vram_texture.GetHeight()) { - m_vram_texture.TransitionToLayout(g_vulkan_context->GetCurrentCommandBuffer(), - VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); - m_host_display->SetDisplayTexture(&m_vram_texture, HostDisplayPixelFormat::RGBA8, m_vram_texture.GetWidth(), - m_vram_texture.GetHeight(), scaled_vram_offset_x, scaled_vram_offset_y, - scaled_display_width, scaled_display_height); + if (IsUsingDownsampling()) + { + DownsampleFramebuffer(m_vram_texture, scaled_vram_offset_x, scaled_vram_offset_y, scaled_display_width, + scaled_display_height); + } + else + { + m_vram_texture.TransitionToLayout(g_vulkan_context->GetCurrentCommandBuffer(), + VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); + m_host_display->SetDisplayTexture(&m_vram_texture, HostDisplayPixelFormat::RGBA8, m_vram_texture.GetWidth(), + m_vram_texture.GetHeight(), scaled_vram_offset_x, scaled_vram_offset_y, + scaled_display_width, scaled_display_height); + } } else { @@ -1051,11 +1296,17 @@ void GPU_HW_Vulkan::UpdateDisplay() m_vram_texture.TransitionToLayout(cmdbuf, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL); m_display_texture.TransitionToLayout(cmdbuf, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); - m_host_display->SetDisplayTexture(&m_display_texture, HostDisplayPixelFormat::RGBA8, m_display_texture.GetWidth(), - m_display_texture.GetHeight(), 0, 0, scaled_display_width, - scaled_display_height); - - RestoreGraphicsAPIState(); + if (IsUsingDownsampling()) + { + DownsampleFramebuffer(m_display_texture, 0, 0, scaled_display_width, scaled_display_height); + } + else + { + m_host_display->SetDisplayTexture(&m_display_texture, HostDisplayPixelFormat::RGBA8, + m_display_texture.GetWidth(), m_display_texture.GetHeight(), 0, 0, + scaled_display_width, scaled_display_height); + RestoreGraphicsAPIState(); + } } m_host_display->SetDisplayParameters(m_crtc_state.display_width, m_crtc_state.display_height, @@ -1410,6 +1661,138 @@ bool GPU_HW_Vulkan::BlitVRAMReplacementTexture(const TextureReplacementTexture* return true; } +void GPU_HW_Vulkan::DownsampleFramebuffer(Vulkan::Texture& source, u32 left, u32 top, u32 width, u32 height) +{ + if (m_downsample_mode == GPUDownsampleMode::Adaptive) + DownsampleFramebufferAdaptive(source, left, top, width, height); + else + DownsampleFramebufferBoxFilter(source, left, top, width, height); +} + +void GPU_HW_Vulkan::DownsampleFramebufferBoxFilter(Vulkan::Texture& source, u32 left, u32 top, u32 width, u32 height) +{ + VkCommandBuffer cmdbuf = g_vulkan_context->GetCurrentCommandBuffer(); + source.TransitionToLayout(cmdbuf, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); + m_downsample_texture.TransitionToLayout(cmdbuf, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL); + + Assert(&source == &m_vram_texture || &source == &m_display_texture); + VkDescriptorSet ds = (&source == &m_vram_texture) ? m_vram_copy_descriptor_set : m_display_descriptor_set; + + const u32 ds_left = left / m_resolution_scale; + const u32 ds_top = top / m_resolution_scale; + const u32 ds_width = width / m_resolution_scale; + const u32 ds_height = height / m_resolution_scale; + + BeginRenderPass(m_downsample_render_pass, m_downsample_mip_views[0].framebuffer, ds_left, ds_top, ds_width, + ds_height); + Vulkan::Util::SetViewportAndScissor(cmdbuf, ds_left, ds_top, ds_width, ds_height); + vkCmdBindPipeline(cmdbuf, VK_PIPELINE_BIND_POINT_GRAPHICS, m_downsample_first_pass_pipeline); + vkCmdBindDescriptorSets(cmdbuf, VK_PIPELINE_BIND_POINT_GRAPHICS, m_single_sampler_pipeline_layout, 0, 1, &ds, 0, + nullptr); + vkCmdDraw(cmdbuf, 3, 1, 0, 0); + EndRenderPass(); + + m_downsample_texture.TransitionToLayout(cmdbuf, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); + + RestoreGraphicsAPIState(); + + m_host_display->SetDisplayTexture(&m_downsample_texture, HostDisplayPixelFormat::RGBA8, + m_downsample_texture.GetWidth(), m_downsample_texture.GetHeight(), ds_left, ds_top, + ds_width, ds_height); +} + +void GPU_HW_Vulkan::DownsampleFramebufferAdaptive(Vulkan::Texture& source, u32 left, u32 top, u32 width, u32 height) +{ + const VkImageCopy copy{{VK_IMAGE_ASPECT_COLOR_BIT, 0u, 0u, 1u}, + {static_cast(left), static_cast(top), 0}, + {VK_IMAGE_ASPECT_COLOR_BIT, 0u, 0u, 1u}, + {static_cast(left), static_cast(top), 0}, + {width, height, 1u}}; + + VkCommandBuffer cmdbuf = g_vulkan_context->GetCurrentCommandBuffer(); + source.TransitionToLayout(cmdbuf, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL); + m_downsample_texture.TransitionSubresourcesToLayout(cmdbuf, 0, 1, 0, 1, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); + vkCmdCopyImage(cmdbuf, source.GetImage(), source.GetLayout(), m_downsample_texture.GetImage(), + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ©); + + m_downsample_texture.TransitionSubresourcesToLayout(cmdbuf, 0, 1, 0, 1, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); + + // creating mip chain + const u32 levels = m_downsample_texture.GetLevels(); + for (u32 level = 1; level < levels; level++) + { + m_downsample_texture.TransitionSubresourcesToLayout( + cmdbuf, level, 1, 0, 1, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL); + + BeginRenderPass(m_downsample_render_pass, m_downsample_mip_views[level].framebuffer, left >> level, top >> level, + width >> level, height >> level); + Vulkan::Util::SetViewportAndScissor(cmdbuf, left >> level, top >> level, width >> level, height >> level); + vkCmdBindPipeline(cmdbuf, VK_PIPELINE_BIND_POINT_GRAPHICS, + (level == 1) ? m_downsample_first_pass_pipeline : m_downsample_mid_pass_pipeline); + vkCmdBindDescriptorSets(cmdbuf, VK_PIPELINE_BIND_POINT_GRAPHICS, m_downsample_pipeline_layout, 0, 1, + &m_downsample_mip_views[level - 1].descriptor_set, 0, nullptr); + + const SmoothingUBOData ubo = GetSmoothingUBO(level, left, top, width, height, m_downsample_texture.GetWidth(), + m_downsample_texture.GetHeight()); + vkCmdPushConstants(cmdbuf, m_downsample_pipeline_layout, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, + 0, sizeof(ubo), &ubo); + + vkCmdDraw(cmdbuf, 3, 1, 0, 0); + + EndRenderPass(); + + m_downsample_texture.TransitionSubresourcesToLayout( + cmdbuf, level, 1, 0, 1, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); + } + + // blur pass at lowest resolution + { + const u32 last_level = levels - 1; + + m_downsample_weight_texture.TransitionToLayout(cmdbuf, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL); + + BeginRenderPass(m_downsample_weight_render_pass, m_downsample_weight_framebuffer, left >> last_level, + top >> last_level, width >> last_level, height >> last_level); + Vulkan::Util::SetViewportAndScissor(cmdbuf, left >> last_level, top >> last_level, width >> last_level, + height >> last_level); + vkCmdBindPipeline(cmdbuf, VK_PIPELINE_BIND_POINT_GRAPHICS, m_downsample_blur_pass_pipeline); + vkCmdBindDescriptorSets(cmdbuf, VK_PIPELINE_BIND_POINT_GRAPHICS, m_downsample_pipeline_layout, 0, 1, + &m_downsample_mip_views[last_level].descriptor_set, 0, nullptr); + + const SmoothingUBOData ubo = GetSmoothingUBO(last_level, left, top, width, height, m_downsample_texture.GetWidth(), + m_downsample_texture.GetHeight()); + vkCmdPushConstants(cmdbuf, m_downsample_pipeline_layout, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, + 0, sizeof(ubo), &ubo); + + vkCmdDraw(cmdbuf, 3, 1, 0, 0); + EndRenderPass(); + + m_downsample_weight_texture.TransitionToLayout(cmdbuf, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); + } + + // resolve pass + { + m_display_texture.TransitionToLayout(cmdbuf, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL); + + BeginRenderPass(m_display_render_pass, m_display_framebuffer, left, top, width, height); + Vulkan::Util::SetViewportAndScissor(cmdbuf, left, top, width, height); + vkCmdBindPipeline(cmdbuf, VK_PIPELINE_BIND_POINT_GRAPHICS, m_downsample_composite_pass_pipeline); + vkCmdBindDescriptorSets(cmdbuf, VK_PIPELINE_BIND_POINT_GRAPHICS, m_downsample_composite_pipeline_layout, 0, 1, + &m_downsample_composite_descriptor_set, 0, nullptr); + vkCmdDraw(cmdbuf, 3, 1, 0, 0); + EndRenderPass(); + + m_display_texture.TransitionToLayout(cmdbuf, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); + } + + RestoreGraphicsAPIState(); + + m_host_display->SetDisplayTexture(&m_display_texture, HostDisplayPixelFormat::RGBA8, m_display_texture.GetWidth(), + m_display_texture.GetHeight(), left, top, width, height); +} + std::unique_ptr GPU::CreateHardwareVulkanRenderer() { return std::make_unique(); diff --git a/src/core/gpu_hw_vulkan.h b/src/core/gpu_hw_vulkan.h index 50afcfbef..7a7261c77 100644 --- a/src/core/gpu_hw_vulkan.h +++ b/src/core/gpu_hw_vulkan.h @@ -70,6 +70,10 @@ private: bool BlitVRAMReplacementTexture(const TextureReplacementTexture* tex, u32 dst_x, u32 dst_y, u32 width, u32 height); + void DownsampleFramebuffer(Vulkan::Texture& source, u32 left, u32 top, u32 width, u32 height); + void DownsampleFramebufferBoxFilter(Vulkan::Texture& source, u32 left, u32 top, u32 width, u32 height); + void DownsampleFramebufferAdaptive(Vulkan::Texture& source, u32 left, u32 top, u32 width, u32 height); + VkRenderPass m_current_render_pass = VK_NULL_HANDLE; VkRenderPass m_vram_render_pass = VK_NULL_HANDLE; @@ -101,11 +105,13 @@ private: VkSampler m_point_sampler = VK_NULL_HANDLE; VkSampler m_linear_sampler = VK_NULL_HANDLE; + VkSampler m_trilinear_sampler = VK_NULL_HANDLE; VkDescriptorSet m_batch_descriptor_set = VK_NULL_HANDLE; VkDescriptorSet m_vram_copy_descriptor_set = VK_NULL_HANDLE; VkDescriptorSet m_vram_read_descriptor_set = VK_NULL_HANDLE; VkDescriptorSet m_vram_write_descriptor_set = VK_NULL_HANDLE; + VkDescriptorSet m_display_descriptor_set = VK_NULL_HANDLE; Vulkan::StreamBuffer m_vertex_stream_buffer; Vulkan::StreamBuffer m_uniform_stream_buffer; @@ -133,4 +139,28 @@ private: // texture replacements Vulkan::Texture m_vram_write_replacement_texture; Vulkan::StreamBuffer m_texture_replacment_stream_buffer; + + // downsampling + Vulkan::Texture m_downsample_texture; + VkRenderPass m_downsample_render_pass = VK_NULL_HANDLE; + Vulkan::Texture m_downsample_weight_texture; + VkRenderPass m_downsample_weight_render_pass = VK_NULL_HANDLE; + VkFramebuffer m_downsample_weight_framebuffer = VK_NULL_HANDLE; + + struct SmoothMipView + { + VkImageView image_view = VK_NULL_HANDLE; + VkDescriptorSet descriptor_set = VK_NULL_HANDLE; + VkFramebuffer framebuffer = VK_NULL_HANDLE; + }; + std::vector m_downsample_mip_views; + + VkPipelineLayout m_downsample_pipeline_layout = VK_NULL_HANDLE; + VkDescriptorSetLayout m_downsample_composite_descriptor_set_layout = VK_NULL_HANDLE; + VkPipelineLayout m_downsample_composite_pipeline_layout = VK_NULL_HANDLE; + VkDescriptorSet m_downsample_composite_descriptor_set = VK_NULL_HANDLE; + VkPipeline m_downsample_first_pass_pipeline = VK_NULL_HANDLE; + VkPipeline m_downsample_mid_pass_pipeline = VK_NULL_HANDLE; + VkPipeline m_downsample_blur_pass_pipeline = VK_NULL_HANDLE; + VkPipeline m_downsample_composite_pass_pipeline = VK_NULL_HANDLE; }; diff --git a/src/core/host_interface.cpp b/src/core/host_interface.cpp index cd8490cb8..ed73b6250 100644 --- a/src/core/host_interface.cpp +++ b/src/core/host_interface.cpp @@ -479,6 +479,7 @@ void HostInterface::SetDefaultSettings(SettingsInterface& si) si.SetBoolValue("GPU", "TrueColor", false); si.SetBoolValue("GPU", "ScaledDithering", true); si.SetStringValue("GPU", "TextureFilter", Settings::GetTextureFilterName(Settings::DEFAULT_GPU_TEXTURE_FILTER)); + si.SetStringValue("GPU", "DownsampleMode", Settings::GetDownsampleModeName(Settings::DEFAULT_GPU_DOWNSAMPLE_MODE)); si.SetBoolValue("GPU", "DisableInterlacing", false); si.SetBoolValue("GPU", "ForceNTSCTimings", false); si.SetBoolValue("GPU", "WidescreenHack", false); @@ -719,6 +720,7 @@ void HostInterface::CheckForSettingsChanges(const Settings& old_settings) g_settings.gpu_disable_interlacing != old_settings.gpu_disable_interlacing || g_settings.gpu_force_ntsc_timings != old_settings.gpu_force_ntsc_timings || g_settings.gpu_24bit_chroma_smoothing != old_settings.gpu_24bit_chroma_smoothing || + g_settings.gpu_downsample_mode != old_settings.gpu_downsample_mode || g_settings.display_crop_mode != old_settings.display_crop_mode || g_settings.display_aspect_ratio != old_settings.display_aspect_ratio || g_settings.gpu_pgxp_enable != old_settings.gpu_pgxp_enable || diff --git a/src/core/settings.cpp b/src/core/settings.cpp index af8f8f863..ea637ffcc 100644 --- a/src/core/settings.cpp +++ b/src/core/settings.cpp @@ -151,6 +151,10 @@ void Settings::Load(SettingsInterface& si) ParseTextureFilterName( si.GetStringValue("GPU", "TextureFilter", GetTextureFilterName(DEFAULT_GPU_TEXTURE_FILTER)).c_str()) .value_or(DEFAULT_GPU_TEXTURE_FILTER); + gpu_downsample_mode = + ParseDownsampleModeName( + si.GetStringValue("GPU", "DownsampleMode", GetDownsampleModeName(DEFAULT_GPU_DOWNSAMPLE_MODE)).c_str()) + .value_or(DEFAULT_GPU_DOWNSAMPLE_MODE); gpu_disable_interlacing = si.GetBoolValue("GPU", "DisableInterlacing", false); gpu_force_ntsc_timings = si.GetBoolValue("GPU", "ForceNTSCTimings", false); gpu_widescreen_hack = si.GetBoolValue("GPU", "WidescreenHack", false); @@ -306,6 +310,7 @@ void Settings::Save(SettingsInterface& si) const si.SetBoolValue("GPU", "TrueColor", gpu_true_color); si.SetBoolValue("GPU", "ScaledDithering", gpu_scaled_dithering); si.SetStringValue("GPU", "TextureFilter", GetTextureFilterName(gpu_texture_filter)); + si.SetStringValue("GPU", "DownsampleMode", GetDownsampleModeName(gpu_downsample_mode)); si.SetBoolValue("GPU", "DisableInterlacing", gpu_disable_interlacing); si.SetBoolValue("GPU", "ForceNTSCTimings", gpu_force_ntsc_timings); si.SetBoolValue("GPU", "WidescreenHack", gpu_widescreen_hack); @@ -628,6 +633,35 @@ const char* Settings::GetTextureFilterDisplayName(GPUTextureFilter filter) return s_texture_filter_display_names[static_cast(filter)]; } +static constexpr auto s_downsample_mode_names = make_array("Disabled", "Box", "Adaptive"); +static constexpr auto s_downsample_mode_display_names = make_array( + TRANSLATABLE("GPUDownsampleMode", "Disabled"), TRANSLATABLE("GPUDownsampleMode", "Box (Downsample 3D/Smooth All)"), + TRANSLATABLE("GPUDownsampleMode", "Adaptive (Preserve 3D/Smooth 2D)")); + +std::optional Settings::ParseDownsampleModeName(const char* str) +{ + int index = 0; + for (const char* name : s_downsample_mode_names) + { + if (StringUtil::Strcasecmp(name, str) == 0) + return static_cast(index); + + index++; + } + + return std::nullopt; +} + +const char* Settings::GetDownsampleModeName(GPUDownsampleMode mode) +{ + return s_downsample_mode_names[static_cast(mode)]; +} + +const char* Settings::GetDownsampleModeDisplayName(GPUDownsampleMode mode) +{ + return s_downsample_mode_display_names[static_cast(mode)]; +} + static std::array s_display_crop_mode_names = {{"None", "Overscan", "Borders"}}; static std::array s_display_crop_mode_display_names = { {TRANSLATABLE("DisplayCropMode", "None"), TRANSLATABLE("DisplayCropMode", "Only Overscan Area"), diff --git a/src/core/settings.h b/src/core/settings.h index 060a68cec..c188deed8 100644 --- a/src/core/settings.h +++ b/src/core/settings.h @@ -104,6 +104,7 @@ struct Settings bool gpu_true_color = true; bool gpu_scaled_dithering = false; GPUTextureFilter gpu_texture_filter = GPUTextureFilter::Nearest; + GPUDownsampleMode gpu_downsample_mode = GPUDownsampleMode::Disabled; bool gpu_disable_interlacing = false; bool gpu_force_ntsc_timings = false; bool gpu_widescreen_hack = false; @@ -285,6 +286,10 @@ struct Settings static const char* GetTextureFilterName(GPUTextureFilter filter); static const char* GetTextureFilterDisplayName(GPUTextureFilter filter); + static std::optional ParseDownsampleModeName(const char* str); + static const char* GetDownsampleModeName(GPUDownsampleMode mode); + static const char* GetDownsampleModeDisplayName(GPUDownsampleMode mode); + static std::optional ParseDisplayCropMode(const char* str); static const char* GetDisplayCropModeName(DisplayCropMode crop_mode); static const char* GetDisplayCropModeDisplayName(DisplayCropMode crop_mode); @@ -312,6 +317,7 @@ struct Settings static constexpr GPURenderer DEFAULT_GPU_RENDERER = GPURenderer::HardwareOpenGL; #endif static constexpr GPUTextureFilter DEFAULT_GPU_TEXTURE_FILTER = GPUTextureFilter::Nearest; + static constexpr GPUDownsampleMode DEFAULT_GPU_DOWNSAMPLE_MODE = GPUDownsampleMode::Disabled; static constexpr ConsoleRegion DEFAULT_CONSOLE_REGION = ConsoleRegion::Auto; static constexpr float DEFAULT_GPU_PGXP_DEPTH_THRESHOLD = 300.0f; diff --git a/src/core/shadergen.cpp b/src/core/shadergen.cpp index aa121869a..b5b778bd4 100644 --- a/src/core/shadergen.cpp +++ b/src/core/shadergen.cpp @@ -164,6 +164,9 @@ void ShaderGen::WriteHeader(std::stringstream& ss) ss << "#define VECTOR_COMP_EQ(a, b) equal((a), (b))\n"; ss << "#define VECTOR_COMP_NEQ(a, b) notEqual((a), (b))\n"; ss << "#define SAMPLE_TEXTURE(name, coords) texture(name, coords)\n"; + ss << "#define SAMPLE_TEXTURE_OFFSET(name, coords, offset) textureOffset(name, coords, offset)\n"; + ss << "#define SAMPLE_TEXTURE_LEVEL(name, coords, level) textureLod(name, coords, level)\n"; + ss << "#define SAMPLE_TEXTURE_LEVEL_OFFSET(name, coords, level, offset) textureLod(name, coords, level, offset)\n"; ss << "#define LOAD_TEXTURE(name, coords, mip) texelFetch(name, coords, mip)\n"; ss << "#define LOAD_TEXTURE_MS(name, coords, sample) texelFetch(name, coords, int(sample))\n"; ss << "#define LOAD_TEXTURE_OFFSET(name, coords, mip, offset) texelFetchOffset(name, coords, mip, offset)\n"; @@ -202,6 +205,10 @@ void ShaderGen::WriteHeader(std::stringstream& ss) ss << "#define VECTOR_COMP_EQ(a, b) ((a) == (b))\n"; ss << "#define VECTOR_COMP_NEQ(a, b) ((a) != (b))\n"; ss << "#define SAMPLE_TEXTURE(name, coords) name.Sample(name##_ss, coords)\n"; + ss << "#define SAMPLE_TEXTURE_OFFSET(name, coords, offset) name.Sample(name##_ss, coords, offset)\n"; + ss << "#define SAMPLE_TEXTURE_LEVEL(name, coords, level) name.SampleLevel(name##_ss, coords, level)\n"; + ss << "#define SAMPLE_TEXTURE_LEVEL_OFFSET(name, coords, level, offset) name.SampleLevel(name##_ss, coords, level, " + "offset)\n"; ss << "#define LOAD_TEXTURE(name, coords, mip) name.Load(int3(coords, mip))\n"; ss << "#define LOAD_TEXTURE_MS(name, coords, sample) name.Load(coords, sample)\n"; ss << "#define LOAD_TEXTURE_OFFSET(name, coords, mip, offset) name.Load(int3(coords, mip), offset)\n"; @@ -547,6 +554,26 @@ std::string ShaderGen::GenerateScreenQuadVertexShader() return ss.str(); } +std::string ShaderGen::GenerateUVQuadVertexShader() +{ + std::stringstream ss; + WriteHeader(ss); + DeclareUniformBuffer(ss, {"float2 u_uv_min", "float2 u_uv_max"}, true); + DeclareVertexEntryPoint(ss, {}, 0, 1, {}, true); + ss << R"( +{ + v_tex0 = float2(float((v_id << 1) & 2u), float(v_id & 2u)); + v_pos = float4(v_tex0 * float2(2.0f, -2.0f) + float2(-1.0f, 1.0f), 0.0f, 1.0f); + v_tex0 = u_uv_min + (u_uv_max - u_uv_min) * v_tex0; + #if API_OPENGL || API_OPENGL_ES || API_VULKAN + v_pos.y = -v_pos.y; + #endif +} +)"; + + return ss.str(); +} + std::string ShaderGen::GenerateFillFragmentShader() { std::stringstream ss; diff --git a/src/core/shadergen.h b/src/core/shadergen.h index fcb3da97b..d93a5c73b 100644 --- a/src/core/shadergen.h +++ b/src/core/shadergen.h @@ -13,6 +13,7 @@ public: static bool UseGLSLBindingLayout(); std::string GenerateScreenQuadVertexShader(); + std::string GenerateUVQuadVertexShader(); std::string GenerateFillFragmentShader(); std::string GenerateCopyFragmentShader(); diff --git a/src/core/types.h b/src/core/types.h index 7a3d37152..0a0112756 100644 --- a/src/core/types.h +++ b/src/core/types.h @@ -75,6 +75,14 @@ enum class GPUTextureFilter : u8 Count }; +enum GPUDownsampleMode : u8 +{ + Disabled, + Box, + Adaptive, + Count +}; + enum class DisplayCropMode : u8 { None, diff --git a/src/duckstation-libretro/libretro_host_interface.cpp b/src/duckstation-libretro/libretro_host_interface.cpp index 60de1e0aa..bb5f647ec 100644 --- a/src/duckstation-libretro/libretro_host_interface.cpp +++ b/src/duckstation-libretro/libretro_host_interface.cpp @@ -489,7 +489,7 @@ void LibretroHostInterface::OnSystemDestroyed() m_using_hardware_renderer = false; } -static std::array s_option_definitions = {{ +static std::array s_option_definitions = {{ {"duckstation_Console.Region", "Console Region", "Determines which region/hardware to emulate. Auto-Detect will use the region of the disc inserted.", @@ -701,12 +701,6 @@ static std::array s_option_definitions = {{ "Very slow, and incompatible with the recompiler.", {{"true", "Enabled"}, {"false", "Disabled"}}, "false"}, - {"duckstation_Display.CropMode", - "Crop Mode", - "Changes how much of the image is cropped. Some games display garbage in the overscan area which is typically " - "hidden.", - {{"None", "None"}, {"Overscan", "Only Overscan Area"}, {"Borders", "All Borders"}}, - "Overscan"}, {"duckstation_Display.AspectRatio", "Aspect Ratio", "Sets the core-provided aspect ratio.", @@ -724,6 +718,20 @@ static std::array s_option_definitions = {{ {"1:1", "1:1"}, {"PAR 1:1", "PAR 1:1"}}, "Auto"}, + {"duckstation_Display.CropMode", + "Crop Mode", + "Changes how much of the image is cropped. Some games display garbage in the overscan area which is typically " + "hidden.", + {{"None", "None"}, {"Overscan", "Only Overscan Area"}, {"Borders", "All Borders"}}, + "Overscan"}, + {"duckstation_GPU.DownsampleMode", + "Downsampling", + "Downsamples the rendered image prior to displaying it. Can improve overall image quality in mixed 2D/3D games, but " + "should be disabled for pure 3D games. Only applies to the hardware renderers.", + {{"Disabled", "Disabled"}, + {"Box", "Box (Downsample 3D/Smooth All)"}, + {"Adaptive", "Adaptive (Preserve 3D/Smooth 2D)"}}, + "Disabled"}, {"duckstation_Main.LoadDevicesFromSaveStates", "Load Devices From Save States", "Sets whether the contents of devices and memory cards will be loaded when a save state is loaded.", diff --git a/src/duckstation-qt/displaysettingswidget.cpp b/src/duckstation-qt/displaysettingswidget.cpp index 63f2caeb6..401a3207f 100644 --- a/src/duckstation-qt/displaysettingswidget.cpp +++ b/src/duckstation-qt/displaysettingswidget.cpp @@ -28,6 +28,9 @@ DisplaySettingsWidget::DisplaySettingsWidget(QtHostInterface* host_interface, QW SettingWidgetBinder::BindWidgetToEnumSetting(m_host_interface, m_ui.displayCropMode, "Display", "CropMode", &Settings::ParseDisplayCropMode, &Settings::GetDisplayCropModeName, Settings::DEFAULT_DISPLAY_CROP_MODE); + SettingWidgetBinder::BindWidgetToEnumSetting(m_host_interface, m_ui.gpuDownsampleMode, "GPU", "DownsampleMode", + &Settings::ParseDownsampleModeName, &Settings::GetDownsampleModeName, + Settings::DEFAULT_GPU_DOWNSAMPLE_MODE); SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.displayLinearFiltering, "Display", "LinearFiltering"); SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.displayIntegerScaling, "Display", @@ -73,6 +76,10 @@ DisplaySettingsWidget::DisplaySettingsWidget(QtHostInterface* host_interface, QW "Some games display content in the overscan area, or use it for screen effects.
May " "not display correctly with the \"All Borders\" setting. \"Only Overscan\" offers a good " "compromise between stability and hiding black borders.")); + dialog->registerWidgetHelp( + m_ui.gpuDownsampleMode, tr("Downsampling"), tr("Disabled"), + tr("Downsamples the rendered image prior to displaying it. Can improve overall image quality in mixed 2D/3D games, " + "but should be disabled for pure 3D games. Only applies to the hardware renderers.")); dialog->registerWidgetHelp(m_ui.displayLinearFiltering, tr("Linear Upscaling"), tr("Checked"), tr("Uses bilinear texture filtering when displaying the console's framebuffer to the " "screen.
Disabling filtering " @@ -139,6 +146,12 @@ void DisplaySettingsWidget::setupAdditionalUi() m_ui.displayCropMode->addItem( qApp->translate("DisplayCropMode", Settings::GetDisplayCropModeDisplayName(static_cast(i)))); } + + for (u32 i = 0; i < static_cast(GPUDownsampleMode::Count); i++) + { + m_ui.gpuDownsampleMode->addItem( + qApp->translate("GPUDownsampleMode", Settings::GetDownsampleModeDisplayName(static_cast(i)))); + } } void DisplaySettingsWidget::populateGPUAdaptersAndResolutions() diff --git a/src/duckstation-qt/displaysettingswidget.ui b/src/duckstation-qt/displaysettingswidget.ui index 0e6435988..e8c4a6b00 100644 --- a/src/duckstation-qt/displaysettingswidget.ui +++ b/src/duckstation-qt/displaysettingswidget.ui @@ -116,7 +116,17 @@ - + + + + Downsampling: + + + + + + + diff --git a/src/duckstation-sdl/sdl_host_interface.cpp b/src/duckstation-sdl/sdl_host_interface.cpp index b0d0b3feb..018891139 100644 --- a/src/duckstation-sdl/sdl_host_interface.cpp +++ b/src/duckstation-sdl/sdl_host_interface.cpp @@ -1080,6 +1080,21 @@ void SDLHostInterface::DrawQuickSettingsMenu() settings_changed |= ImGui::MenuItem("Display Linear Filtering", nullptr, &m_settings_copy.display_linear_filtering); settings_changed |= ImGui::MenuItem("Display Integer Scaling", nullptr, &m_settings_copy.display_integer_scaling); + if (ImGui::BeginMenu("Aspect Ratio")) + { + for (u32 i = 0; i < static_cast(DisplayAspectRatio::Count); i++) + { + if (ImGui::MenuItem(Settings::GetDisplayAspectRatioName(static_cast(i)), nullptr, + m_settings_copy.display_aspect_ratio == static_cast(i))) + { + m_settings_copy.display_aspect_ratio = static_cast(i); + settings_changed = true; + } + } + + ImGui::EndMenu(); + } + if (ImGui::BeginMenu("Crop Mode")) { for (u32 i = 0; i < static_cast(DisplayCropMode::Count); i++) @@ -1095,14 +1110,14 @@ void SDLHostInterface::DrawQuickSettingsMenu() ImGui::EndMenu(); } - if (ImGui::BeginMenu("Aspect Ratio")) + if (ImGui::BeginMenu("Downsample Mode")) { - for (u32 i = 0; i < static_cast(DisplayAspectRatio::Count); i++) + for (u32 i = 0; i < static_cast(GPUDownsampleMode::Count); i++) { - if (ImGui::MenuItem(Settings::GetDisplayAspectRatioName(static_cast(i)), nullptr, - m_settings_copy.display_aspect_ratio == static_cast(i))) + if (ImGui::MenuItem(Settings::GetDownsampleModeDisplayName(static_cast(i)), nullptr, + m_settings_copy.gpu_downsample_mode == static_cast(i))) { - m_settings_copy.display_aspect_ratio = static_cast(i); + m_settings_copy.gpu_downsample_mode = static_cast(i); settings_changed = true; } } @@ -1541,6 +1556,21 @@ void SDLHostInterface::DrawSettingsWindow() settings_changed = true; } + ImGui::Text("Downsample Mode:"); + ImGui::SameLine(indent); + int gpu_downsample_mode = static_cast(m_settings_copy.gpu_downsample_mode); + if (ImGui::Combo( + "##gpu_downsample_mode", &gpu_downsample_mode, + [](void*, int index, const char** out_text) { + *out_text = Settings::GetDownsampleModeDisplayName(static_cast(index)); + return true; + }, + nullptr, static_cast(GPUDownsampleMode::Count))) + { + m_settings_copy.gpu_downsample_mode = static_cast(gpu_downsample_mode); + settings_changed = true; + } + settings_changed |= ImGui::Checkbox("Use Debug Device", &m_settings_copy.gpu_use_debug_device); settings_changed |= ImGui::Checkbox("Linear Filtering", &m_settings_copy.display_linear_filtering); settings_changed |= ImGui::Checkbox("Integer Scaling", &m_settings_copy.display_integer_scaling);