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);