GPU: Add adaptive and box downsampling modes

This commit is contained in:
Connor McLaughlin 2020-12-30 16:26:20 +10:00
parent 5236583544
commit 3cb2cd8235
23 changed files with 1180 additions and 69 deletions

View file

@ -450,4 +450,14 @@
<item>light</item>
<item>dark</item>
</string-array>
<string-array name="settings_downsample_mode_entries">
<item>Disabled</item>
<item>Box (Downsample 3D/Smooth All)</item>
<item>Adaptive (Preserve 3D/Smooth 2D)</item>
</string-array>
<string-array name="settings_downsample_mode_values">
<item>Disabled</item>
<item>Box</item>
<item>Adaptive</item>
</string-array>
</resources>

View file

@ -182,4 +182,5 @@
<string name="settings_summary_use_analog_sticks_for_dpad">Allows you to use the analog sticks to control the d-pad in digital mode, as well as the buttons.</string>
<string name="settings_disable_all_enhancements">Disable All Enhancements</string>
<string name="settings_summary_disable_all_enhancements">Temporarily disables all enhancements, which can be useful when debugging issues.</string>
<string name="settings_downsample_mode">Downsampling</string>
</resources>

View file

@ -34,6 +34,15 @@
app:useSimpleSummaryProvider="true"
app:iconSpaceReserved="false" />
<ListPreference
app:key="GPU/DownsampleMode"
app:title="@string/settings_downsample_mode"
app:entries="@array/settings_downsample_mode_entries"
app:entryValues="@array/settings_downsample_mode_values"
app:defaultValue="Disabled"
app:useSimpleSummaryProvider="true"
app:iconSpaceReserved="false" />
<SwitchPreferenceCompat
app:key="Display/LinearFiltering"
app:title="@string/settings_linear_upscaling"

View file

@ -55,6 +55,7 @@ bool GPU_HW::Initialize(HostDisplay* host_display)
m_texture_filtering = g_settings.gpu_texture_filter;
m_using_uv_limits = ShouldUseUVLimits();
m_chroma_smoothing = g_settings.gpu_24bit_chroma_smoothing;
m_downsample_mode = GetDownsampleMode(m_resolution_scale);
if (m_multisamples != g_settings.gpu_multisamples)
{
@ -70,10 +71,20 @@ bool GPU_HW::Initialize(HostDisplay* host_display)
if (!m_supports_dual_source_blend && TextureFilterRequiresDualSourceBlend(m_texture_filtering))
{
g_host_interface->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<u32>(g_settings.gpu_resolution_scale, 1, m_max_resolution_scale);
{
scale = std::clamp<u32>(g_settings.gpu_resolution_scale, 1, m_max_resolution_scale);
}
else
{
// auto scaling
const s32 height = (m_crtc_state.display_height != 0) ? static_cast<s32>(m_crtc_state.display_height) : 480;
const s32 preferred_scale =
static_cast<s32>(std::ceil(static_cast<float>(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<s32>(m_crtc_state.display_height) : 480;
const s32 preferred_scale =
static_cast<s32>(std::ceil(static_cast<float>(m_host_display->GetWindowHeight()) / height));
Log_InfoPrintf("Height = %d, preferred scale = %d", height, preferred_scale);
scale = static_cast<u32>(std::clamp<s32>(preferred_scale, 1, m_max_resolution_scale));
}
return static_cast<u32>(std::clamp<s32>(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<u32, u32> 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<float>(tex_width >> level);
const float rcp_height = 1.0f / static_cast<float>(tex_height >> level);
SmoothingUBOData data;
data.min_uv[0] = static_cast<float>(left >> level) * rcp_width;
data.min_uv[1] = static_cast<float>(top >> level) * rcp_height;
data.max_uv[0] = static_cast<float>((left + width) >> level) * rcp_width;
data.max_uv[1] = static_cast<float>((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;

View file

@ -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<u16, VRAM_WIDTH * VRAM_HEIGHT> 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<u8, bool, 0, 1> m_supports_per_sample_shading;
BitField<u8, bool, 1, 1> m_supports_dual_source_blend;
BitField<u8, bool, 2, 1> m_supports_adaptive_downsampling;
BitField<u8, bool, 3, 1> m_per_sample_shading;
BitField<u8, bool, 4, 1> m_scaled_dithering;
BitField<u8, bool, 5, 1> 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;

View file

@ -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<u16>(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> GPU::CreateHardwareD3D11Renderer()
{
return std::make_unique<GPU_HW_D3D11>();

View file

@ -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<ID3D11Device> m_device;
ComPtr<ID3D11DeviceContext> m_context;
@ -105,6 +109,7 @@ private:
ComPtr<ID3D11SamplerState> m_point_sampler_state;
ComPtr<ID3D11SamplerState> m_linear_sampler_state;
ComPtr<ID3D11SamplerState> m_trilinear_sampler_state;
std::array<ComPtr<ID3D11BlendState>, 5> m_batch_blend_states; // [transparency_mode]
ComPtr<ID3D11InputLayout> m_batch_input_layout;
@ -113,6 +118,7 @@ private:
m_batch_pixel_shaders; // [render_mode][texture_mode][dithering][interlacing]
ComPtr<ID3D11VertexShader> m_screen_quad_vertex_shader;
ComPtr<ID3D11VertexShader> m_uv_quad_vertex_shader;
ComPtr<ID3D11PixelShader> m_copy_pixel_shader;
ComPtr<ID3D11PixelShader> m_vram_fill_pixel_shader;
ComPtr<ID3D11PixelShader> m_vram_interlaced_fill_pixel_shader;
@ -123,4 +129,13 @@ private:
std::array<std::array<ComPtr<ID3D11PixelShader>, 3>, 2> m_display_pixel_shaders; // [depth_24][interlaced]
D3D11::Texture m_vram_replacement_texture;
// downsampling
ComPtr<ID3D11PixelShader> m_downsample_first_pass_pixel_shader;
ComPtr<ID3D11PixelShader> m_downsample_mid_pass_pixel_shader;
ComPtr<ID3D11PixelShader> m_downsample_blur_pass_pixel_shader;
ComPtr<ID3D11PixelShader> m_downsample_composite_pixel_shader;
D3D11::Texture m_downsample_texture;
D3D11::Texture m_downsample_weight_texture;
std::vector<std::pair<ComPtr<ID3D11ShaderResourceView>, ComPtr<ID3D11RenderTargetView>>> m_downsample_mip_views;
};

View file

@ -273,6 +273,9 @@ void GPU_HW_OpenGL::SetCapabilities(HostDisplay* host_display)
m_max_resolution_scale = std::min<int>(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<void*>(static_cast<uintptr_t>(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<s32>(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<void*>(static_cast<uintptr_t>(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<s32>(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<void*>(static_cast<uintptr_t>(m_display_texture.GetGLId())),
HostDisplayPixelFormat::RGBA8, m_display_texture.GetWidth(),
m_display_texture.GetHeight(), 0, scaled_display_height, scaled_display_width,
-static_cast<s32>(scaled_display_height));
if (IsUsingDownsampling())
{
DownsampleFramebuffer(m_display_texture, 0, 0, scaled_display_width, scaled_display_height);
}
else
{
m_host_display->SetDisplayTexture(reinterpret_cast<void*>(static_cast<uintptr_t>(m_display_texture.GetGLId())),
HostDisplayPixelFormat::RGBA8, m_display_texture.GetWidth(),
m_display_texture.GetHeight(), 0, scaled_display_height, scaled_display_width,
-static_cast<s32>(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<void*>(static_cast<uintptr_t>(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<s32>(ds_height));
}
std::unique_ptr<GPU> GPU::CreateHardwareOpenGLRenderer()
{
return std::make_unique<GPU_HW_OpenGL>();

View file

@ -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;
};

View file

@ -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();
}

View file

@ -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; }

View file

@ -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<s32>(left), static_cast<s32>(top), 0},
{VK_IMAGE_ASPECT_COLOR_BIT, 0u, 0u, 1u},
{static_cast<s32>(left), static_cast<s32>(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, &copy);
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> GPU::CreateHardwareVulkanRenderer()
{
return std::make_unique<GPU_HW_Vulkan>();

View file

@ -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<SmoothMipView> 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;
};

View file

@ -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 ||

View file

@ -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<int>(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<GPUDownsampleMode> 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<GPUDownsampleMode>(index);
index++;
}
return std::nullopt;
}
const char* Settings::GetDownsampleModeName(GPUDownsampleMode mode)
{
return s_downsample_mode_names[static_cast<int>(mode)];
}
const char* Settings::GetDownsampleModeDisplayName(GPUDownsampleMode mode)
{
return s_downsample_mode_display_names[static_cast<int>(mode)];
}
static std::array<const char*, 3> s_display_crop_mode_names = {{"None", "Overscan", "Borders"}};
static std::array<const char*, 3> s_display_crop_mode_display_names = {
{TRANSLATABLE("DisplayCropMode", "None"), TRANSLATABLE("DisplayCropMode", "Only Overscan Area"),

View file

@ -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<GPUDownsampleMode> ParseDownsampleModeName(const char* str);
static const char* GetDownsampleModeName(GPUDownsampleMode mode);
static const char* GetDownsampleModeDisplayName(GPUDownsampleMode mode);
static std::optional<DisplayCropMode> 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;

View file

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

View file

@ -13,6 +13,7 @@ public:
static bool UseGLSLBindingLayout();
std::string GenerateScreenQuadVertexShader();
std::string GenerateUVQuadVertexShader();
std::string GenerateFillFragmentShader();
std::string GenerateCopyFragmentShader();

View file

@ -75,6 +75,14 @@ enum class GPUTextureFilter : u8
Count
};
enum GPUDownsampleMode : u8
{
Disabled,
Box,
Adaptive,
Count
};
enum class DisplayCropMode : u8
{
None,

View file

@ -489,7 +489,7 @@ void LibretroHostInterface::OnSystemDestroyed()
m_using_hardware_renderer = false;
}
static std::array<retro_core_option_definition, 51> s_option_definitions = {{
static std::array<retro_core_option_definition, 52> 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<retro_core_option_definition, 51> 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<retro_core_option_definition, 51> 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.",

View file

@ -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. <br>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. <br>Disabling filtering "
@ -139,6 +146,12 @@ void DisplaySettingsWidget::setupAdditionalUi()
m_ui.displayCropMode->addItem(
qApp->translate("DisplayCropMode", Settings::GetDisplayCropModeDisplayName(static_cast<DisplayCropMode>(i))));
}
for (u32 i = 0; i < static_cast<u32>(GPUDownsampleMode::Count); i++)
{
m_ui.gpuDownsampleMode->addItem(
qApp->translate("GPUDownsampleMode", Settings::GetDownsampleModeDisplayName(static_cast<GPUDownsampleMode>(i))));
}
}
void DisplaySettingsWidget::populateGPUAdaptersAndResolutions()

View file

@ -116,7 +116,17 @@
<item row="1" column="1">
<widget class="QComboBox" name="displayCropMode"/>
</item>
<item row="2" column="0" colspan="2">
<item row="2" column="0">
<widget class="QLabel" name="label_6">
<property name="text">
<string>Downsampling:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QComboBox" name="gpuDownsampleMode"/>
</item>
<item row="3" column="0" colspan="2">
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
<widget class="QCheckBox" name="displayLinearFiltering">

View file

@ -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<u32>(DisplayAspectRatio::Count); i++)
{
if (ImGui::MenuItem(Settings::GetDisplayAspectRatioName(static_cast<DisplayAspectRatio>(i)), nullptr,
m_settings_copy.display_aspect_ratio == static_cast<DisplayAspectRatio>(i)))
{
m_settings_copy.display_aspect_ratio = static_cast<DisplayAspectRatio>(i);
settings_changed = true;
}
}
ImGui::EndMenu();
}
if (ImGui::BeginMenu("Crop Mode"))
{
for (u32 i = 0; i < static_cast<u32>(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<u32>(DisplayAspectRatio::Count); i++)
for (u32 i = 0; i < static_cast<u32>(GPUDownsampleMode::Count); i++)
{
if (ImGui::MenuItem(Settings::GetDisplayAspectRatioName(static_cast<DisplayAspectRatio>(i)), nullptr,
m_settings_copy.display_aspect_ratio == static_cast<DisplayAspectRatio>(i)))
if (ImGui::MenuItem(Settings::GetDownsampleModeDisplayName(static_cast<GPUDownsampleMode>(i)), nullptr,
m_settings_copy.gpu_downsample_mode == static_cast<GPUDownsampleMode>(i)))
{
m_settings_copy.display_aspect_ratio = static_cast<DisplayAspectRatio>(i);
m_settings_copy.gpu_downsample_mode = static_cast<GPUDownsampleMode>(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<int>(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<GPUDownsampleMode>(index));
return true;
},
nullptr, static_cast<int>(GPUDownsampleMode::Count)))
{
m_settings_copy.gpu_downsample_mode = static_cast<GPUDownsampleMode>(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);