GPU: Rewrite deinterlacing and add adaptive/blend modes

This commit is contained in:
Stenzek 2024-03-16 02:02:03 +10:00
parent 8b2b4ce8d9
commit 1ab7850ed0
No known key found for this signature in database
19 changed files with 970 additions and 385 deletions

View File

@ -3941,6 +3941,14 @@ void FullscreenUI::DrawDisplaySettingsPage()
&Settings::GetDisplayAspectRatioName, &Settings::GetDisplayAspectRatioDisplayName,
DisplayAspectRatio::Count);
DrawEnumSetting(
bsi, FSUI_CSTR("Deinterlacing Mode"),
FSUI_CSTR(
"Determines which algorithm is used to convert interlaced frames to progressive for display on your system."),
"Display", "DeinterlacingMode", Settings::DEFAULT_DISPLAY_DEINTERLACING_MODE,
&Settings::ParseDisplayDeinterlacingMode, &Settings::GetDisplayDeinterlacingModeName,
&Settings::GetDisplayDeinterlacingModeDisplayName, DisplayDeinterlacingMode::Count);
DrawEnumSetting(bsi, FSUI_CSTR("Crop Mode"),
FSUI_CSTR("Determines how much of the area typically not visible on a consumer TV set to crop/hide."),
"Display", "CropMode", Settings::DEFAULT_DISPLAY_CROP_MODE, &Settings::ParseDisplayCropMode,

View File

@ -56,6 +56,8 @@ GPU::GPU()
GPU::~GPU()
{
JoinScreenshotThreads();
DestroyDeinterlaceTextures();
g_gpu_device->RecycleTexture(std::move(m_chroma_smoothing_texture));
if (g_gpu_device)
g_gpu_device->SetGPUTimingEnabled(false);
@ -78,7 +80,7 @@ bool GPU::Initialize()
m_console_is_pal = System::IsPALRegion();
UpdateCRTCConfig();
if (!CompileDisplayPipeline())
if (!CompileDisplayPipelines(true, true, g_settings.gpu_24bit_chroma_smoothing))
{
Host::ReportErrorAsync("Error", "Failed to compile base GPU pipelines.");
return false;
@ -107,10 +109,20 @@ void GPU::UpdateSettings(const Settings& old_settings)
// Crop mode calls this, so recalculate the display area
UpdateCRTCDisplayParameters();
if (g_settings.display_scaling != old_settings.display_scaling)
if (g_settings.display_scaling != old_settings.display_scaling ||
g_settings.display_deinterlacing_mode != old_settings.display_deinterlacing_mode ||
g_settings.gpu_24bit_chroma_smoothing != old_settings.gpu_24bit_chroma_smoothing)
{
if (!CompileDisplayPipeline())
// Toss buffers on mode change.
if (g_settings.display_deinterlacing_mode != old_settings.display_deinterlacing_mode)
DestroyDeinterlaceTextures();
if (!CompileDisplayPipelines(g_settings.display_scaling != old_settings.display_scaling,
g_settings.display_deinterlacing_mode != old_settings.display_deinterlacing_mode,
g_settings.gpu_24bit_chroma_smoothing != old_settings.gpu_24bit_chroma_smoothing))
{
Panic("Failed to compile display pipeline on settings change.");
}
}
g_gpu_device->SetGPUTimingEnabled(g_settings.display_show_gpu_usage);
@ -1359,6 +1371,10 @@ void GPU::HandleGetGPUInfoCommand(u32 value)
void GPU::ClearDisplay()
{
ClearDisplayTexture();
// Just recycle the textures, it'll get re-fetched.
DestroyDeinterlaceTextures();
}
void GPU::UpdateDisplay()
@ -1587,56 +1603,183 @@ void GPU::SetTextureWindow(u32 value)
m_draw_mode.texture_window_changed = true;
}
bool GPU::CompileDisplayPipeline()
bool GPU::CompileDisplayPipelines(bool display, bool deinterlace, bool chroma_smoothing)
{
GPUShaderGen shadergen(g_gpu_device->GetRenderAPI(), g_gpu_device->GetFeatures().dual_source_blend,
g_gpu_device->GetFeatures().framebuffer_fetch);
GPUPipeline::GraphicsConfig plconfig;
plconfig.layout = GPUPipeline::Layout::SingleTextureAndPushConstants;
plconfig.input_layout.vertex_stride = 0;
plconfig.primitive = GPUPipeline::Primitive::Triangles;
plconfig.rasterization = GPUPipeline::RasterizationState::GetNoCullState();
plconfig.depth = GPUPipeline::DepthState::GetNoTestsState();
plconfig.blend = GPUPipeline::BlendState::GetNoBlendingState();
plconfig.SetTargetFormats(g_gpu_device->HasSurface() ? g_gpu_device->GetWindowFormat() : GPUTexture::Format::RGBA8);
plconfig.depth_format = GPUTexture::Format::Unknown;
plconfig.samples = 1;
plconfig.per_sample_shading = false;
plconfig.geometry_shader = nullptr;
std::string vs = shadergen.GenerateDisplayVertexShader();
std::string fs;
switch (g_settings.display_scaling)
if (display)
{
case DisplayScalingMode::BilinearSharp:
fs = shadergen.GenerateDisplaySharpBilinearFragmentShader();
break;
plconfig.layout = GPUPipeline::Layout::SingleTextureAndPushConstants;
plconfig.SetTargetFormats(g_gpu_device->HasSurface() ? g_gpu_device->GetWindowFormat() : GPUTexture::Format::RGBA8);
case DisplayScalingMode::BilinearSmooth:
fs = shadergen.GenerateDisplayFragmentShader(true);
break;
std::string vs = shadergen.GenerateDisplayVertexShader();
std::string fs;
switch (g_settings.display_scaling)
{
case DisplayScalingMode::BilinearSharp:
fs = shadergen.GenerateDisplaySharpBilinearFragmentShader();
break;
case DisplayScalingMode::Nearest:
case DisplayScalingMode::NearestInteger:
default:
fs = shadergen.GenerateDisplayFragmentShader(false);
break;
case DisplayScalingMode::BilinearSmooth:
fs = shadergen.GenerateDisplayFragmentShader(true);
break;
case DisplayScalingMode::Nearest:
case DisplayScalingMode::NearestInteger:
default:
fs = shadergen.GenerateDisplayFragmentShader(false);
break;
}
std::unique_ptr<GPUShader> vso = g_gpu_device->CreateShader(GPUShaderStage::Vertex, vs);
std::unique_ptr<GPUShader> fso = g_gpu_device->CreateShader(GPUShaderStage::Fragment, fs);
if (!vso || !fso)
return false;
GL_OBJECT_NAME(vso, "Display Vertex Shader");
GL_OBJECT_NAME_FMT(fso, "Display Fragment Shader [{}]",
Settings::GetDisplayScalingName(g_settings.display_scaling));
plconfig.vertex_shader = vso.get();
plconfig.fragment_shader = fso.get();
if (!(m_display_pipeline = g_gpu_device->CreatePipeline(plconfig)))
return false;
GL_OBJECT_NAME_FMT(m_display_pipeline, "Display Pipeline [{}]",
Settings::GetDisplayScalingName(g_settings.display_scaling));
}
std::unique_ptr<GPUShader> vso = g_gpu_device->CreateShader(GPUShaderStage::Vertex, vs);
std::unique_ptr<GPUShader> fso = g_gpu_device->CreateShader(GPUShaderStage::Fragment, fs);
if (!vso || !fso)
return false;
GL_OBJECT_NAME(vso, "Display Vertex Shader");
GL_OBJECT_NAME_FMT(fso, "Display Fragment Shader [{}]", Settings::GetDisplayScalingName(g_settings.display_scaling));
if (deinterlace)
{
plconfig.SetTargetFormats(GPUTexture::Format::RGBA8);
plconfig.vertex_shader = vso.get();
plconfig.fragment_shader = fso.get();
plconfig.geometry_shader = nullptr;
if (!(m_display_pipeline = g_gpu_device->CreatePipeline(plconfig)))
return false;
GL_OBJECT_NAME_FMT(m_display_pipeline, "Display Pipeline [{}]",
Settings::GetDisplayScalingName(g_settings.display_scaling));
std::unique_ptr<GPUShader> vso =
g_gpu_device->CreateShader(GPUShaderStage::Vertex, shadergen.GenerateScreenQuadVertexShader());
if (!vso)
return false;
GL_OBJECT_NAME(vso, "Deinterlace Vertex Shader");
std::unique_ptr<GPUShader> fso;
if (!(fso = g_gpu_device->CreateShader(GPUShaderStage::Fragment,
shadergen.GenerateInterleavedFieldExtractFragmentShader())))
{
return false;
}
GL_OBJECT_NAME(fso, "Deinterlace Field Extract Fragment Shader");
plconfig.layout = GPUPipeline::Layout::SingleTextureAndPushConstants;
plconfig.vertex_shader = vso.get();
plconfig.fragment_shader = fso.get();
if (!(m_deinterlace_extract_pipeline = g_gpu_device->CreatePipeline(plconfig)))
return false;
GL_OBJECT_NAME(m_deinterlace_extract_pipeline, "Deinterlace Field Extract Pipeline");
switch (g_settings.display_deinterlacing_mode)
{
case DisplayDeinterlacingMode::Disabled:
break;
case DisplayDeinterlacingMode::Weave:
{
if (!(fso = g_gpu_device->CreateShader(GPUShaderStage::Fragment,
shadergen.GenerateDeinterlaceWeaveFragmentShader())))
{
return false;
}
GL_OBJECT_NAME(fso, "Weave Deinterlace Fragment Shader");
plconfig.layout = GPUPipeline::Layout::SingleTextureAndPushConstants;
plconfig.vertex_shader = vso.get();
plconfig.fragment_shader = fso.get();
if (!(m_deinterlace_pipeline = g_gpu_device->CreatePipeline(plconfig)))
return false;
GL_OBJECT_NAME(m_deinterlace_pipeline, "Weave Deinterlace Pipeline");
}
break;
case DisplayDeinterlacingMode::Blend:
{
if (!(fso = g_gpu_device->CreateShader(GPUShaderStage::Fragment,
shadergen.GenerateDeinterlaceBlendFragmentShader())))
{
return false;
}
GL_OBJECT_NAME(fso, "Blend Deinterlace Fragment Shader");
plconfig.layout = GPUPipeline::Layout::MultiTextureAndPushConstants;
plconfig.vertex_shader = vso.get();
plconfig.fragment_shader = fso.get();
if (!(m_deinterlace_pipeline = g_gpu_device->CreatePipeline(plconfig)))
return false;
GL_OBJECT_NAME(m_deinterlace_pipeline, "Blend Deinterlace Pipeline");
}
break;
case DisplayDeinterlacingMode::Adaptive:
{
fso =
g_gpu_device->CreateShader(GPUShaderStage::Fragment, shadergen.GenerateFastMADReconstructFragmentShader());
if (!fso)
return false;
GL_OBJECT_NAME(fso, "FastMAD Reconstruct Fragment Shader");
plconfig.layout = GPUPipeline::Layout::MultiTextureAndPushConstants;
plconfig.fragment_shader = fso.get();
if (!(m_deinterlace_pipeline = g_gpu_device->CreatePipeline(plconfig)))
return false;
GL_OBJECT_NAME(m_deinterlace_pipeline, "FastMAD Reconstruct Pipeline");
}
break;
default:
UnreachableCode();
}
}
if (chroma_smoothing)
{
m_chroma_smoothing_pipeline.reset();
g_gpu_device->RecycleTexture(std::move(m_chroma_smoothing_texture));
if (g_settings.gpu_24bit_chroma_smoothing)
{
plconfig.layout = GPUPipeline::Layout::SingleTextureAndPushConstants;
plconfig.SetTargetFormats(GPUTexture::Format::RGBA8);
std::unique_ptr<GPUShader> vso =
g_gpu_device->CreateShader(GPUShaderStage::Vertex, shadergen.GenerateScreenQuadVertexShader());
std::unique_ptr<GPUShader> fso =
g_gpu_device->CreateShader(GPUShaderStage::Fragment, shadergen.GenerateChromaSmoothingFragmentShader());
if (!vso || !fso)
return false;
GL_OBJECT_NAME(vso, "Chroma Smoothing Vertex Shader");
GL_OBJECT_NAME(fso, "Chroma Smoothing Fragment Shader");
plconfig.vertex_shader = vso.get();
plconfig.fragment_shader = fso.get();
if (!(m_chroma_smoothing_pipeline = g_gpu_device->CreatePipeline(plconfig)))
return false;
GL_OBJECT_NAME(m_chroma_smoothing_pipeline, "Chroma Smoothing Pipeline");
}
}
return true;
}
@ -1804,6 +1947,226 @@ bool GPU::RenderDisplay(GPUTexture* target, const Common::Rectangle<s32>& draw_r
}
}
void GPU::DestroyDeinterlaceTextures()
{
for (std::unique_ptr<GPUTexture>& tex : m_deinterlace_buffers)
g_gpu_device->RecycleTexture(std::move(tex));
g_gpu_device->RecycleTexture(std::move(m_deinterlace_texture));
m_current_deinterlace_buffer = 0;
}
bool GPU::Deinterlace(GPUTexture* src, u32 x, u32 y, u32 width, u32 height, u32 field, u32 line_skip)
{
switch (g_settings.display_deinterlacing_mode)
{
case DisplayDeinterlacingMode::Disabled:
{
if (line_skip == 0)
{
SetDisplayTexture(src, x, y, width, height);
return true;
}
// Still have to extract the field.
if (!DeinterlaceExtractField(0, src, x, y, width, height, line_skip)) [[unlikely]]
return false;
SetDisplayTexture(m_deinterlace_buffers[0].get(), 0, 0, width, height);
return true;
}
case DisplayDeinterlacingMode::Weave:
{
GL_SCOPE_FMT("DeinterlaceWeave({{{},{}}}, {}x{}, field={}, line_skip={})", x, y, width, height, field, line_skip);
const u32 full_height = height * 2;
if (!DeinterlaceSetTargetSize(width, full_height, true)) [[unlikely]]
{
ClearDisplayTexture();
return false;
}
src->MakeReadyForSampling();
g_gpu_device->SetRenderTarget(m_deinterlace_texture.get());
g_gpu_device->SetPipeline(m_deinterlace_pipeline.get());
g_gpu_device->SetTextureSampler(0, src, g_gpu_device->GetNearestSampler());
const u32 uniforms[] = {x, y, field, line_skip};
g_gpu_device->PushUniformBuffer(uniforms, sizeof(uniforms));
g_gpu_device->SetViewportAndScissor(0, 0, width, full_height);
g_gpu_device->Draw(3, 0);
m_deinterlace_texture->MakeReadyForSampling();
SetDisplayTexture(m_deinterlace_texture.get(), 0, 0, width, full_height);
return true;
}
case DisplayDeinterlacingMode::Blend:
{
constexpr u32 NUM_BLEND_BUFFERS = 2;
GL_SCOPE_FMT("DeinterlaceBlend({{{},{}}}, {}x{}, field={}, line_skip={})", x, y, width, height, field, line_skip);
const u32 this_buffer = m_current_deinterlace_buffer;
m_current_deinterlace_buffer = (m_current_deinterlace_buffer + 1u) % NUM_BLEND_BUFFERS;
GL_INS_FMT("Current buffer: {}", this_buffer);
if (!DeinterlaceExtractField(this_buffer, src, x, y, width, height, line_skip) ||
!DeinterlaceSetTargetSize(width, height, false)) [[unlikely]]
{
ClearDisplayTexture();
return false;
}
// TODO: could be implemented with alpha blending instead..
g_gpu_device->InvalidateRenderTarget(m_deinterlace_texture.get());
g_gpu_device->SetRenderTarget(m_deinterlace_texture.get());
g_gpu_device->SetPipeline(m_deinterlace_pipeline.get());
g_gpu_device->SetTextureSampler(0, m_deinterlace_buffers[this_buffer].get(), g_gpu_device->GetNearestSampler());
g_gpu_device->SetTextureSampler(1, m_deinterlace_buffers[(this_buffer - 1) % NUM_BLEND_BUFFERS].get(),
g_gpu_device->GetNearestSampler());
g_gpu_device->SetViewportAndScissor(0, 0, width, height);
g_gpu_device->Draw(3, 0);
m_deinterlace_texture->MakeReadyForSampling();
SetDisplayTexture(m_deinterlace_texture.get(), 0, 0, width, height);
return true;
}
case DisplayDeinterlacingMode::Adaptive:
{
GL_SCOPE_FMT("DeinterlaceAdaptive({{{},{}}}, {}x{}, field={}, line_skip={})", x, y, width, height, field,
line_skip);
const u32 full_height = height * 2;
const u32 this_buffer = m_current_deinterlace_buffer;
m_current_deinterlace_buffer = (m_current_deinterlace_buffer + 1u) % DEINTERLACE_BUFFER_COUNT;
GL_INS_FMT("Current buffer: {}", this_buffer);
if (!DeinterlaceExtractField(this_buffer, src, x, y, width, height, line_skip) ||
!DeinterlaceSetTargetSize(width, full_height, false)) [[unlikely]]
{
ClearDisplayTexture();
return false;
}
g_gpu_device->SetRenderTarget(m_deinterlace_texture.get());
g_gpu_device->SetPipeline(m_deinterlace_pipeline.get());
g_gpu_device->SetTextureSampler(0, m_deinterlace_buffers[this_buffer].get(), g_gpu_device->GetNearestSampler());
g_gpu_device->SetTextureSampler(1, m_deinterlace_buffers[(this_buffer - 1) % DEINTERLACE_BUFFER_COUNT].get(),
g_gpu_device->GetNearestSampler());
g_gpu_device->SetTextureSampler(2, m_deinterlace_buffers[(this_buffer - 2) % DEINTERLACE_BUFFER_COUNT].get(),
g_gpu_device->GetNearestSampler());
g_gpu_device->SetTextureSampler(3, m_deinterlace_buffers[(this_buffer - 3) % DEINTERLACE_BUFFER_COUNT].get(),
g_gpu_device->GetNearestSampler());
const u32 uniforms[] = {field, full_height};
g_gpu_device->PushUniformBuffer(uniforms, sizeof(uniforms));
g_gpu_device->SetViewportAndScissor(0, 0, width, full_height);
g_gpu_device->Draw(3, 0);
m_deinterlace_texture->MakeReadyForSampling();
SetDisplayTexture(m_deinterlace_texture.get(), 0, 0, width, full_height);
return true;
}
default:
UnreachableCode();
}
}
bool GPU::DeinterlaceExtractField(u32 dst_bufidx, GPUTexture* src, u32 x, u32 y, u32 width, u32 height, u32 line_skip)
{
if (!m_deinterlace_buffers[dst_bufidx] || m_deinterlace_buffers[dst_bufidx]->GetWidth() != width ||
m_deinterlace_buffers[dst_bufidx]->GetHeight() != height)
{
if (!g_gpu_device->ResizeTexture(&m_deinterlace_buffers[dst_bufidx], width, height, GPUTexture::Type::RenderTarget,
GPUTexture::Format::RGBA8, false)) [[unlikely]]
{
return false;
}
GL_OBJECT_NAME_FMT(m_deinterlace_buffers[dst_bufidx], "Blend Deinterlace Buffer {}", dst_bufidx);
}
GPUTexture* dst = m_deinterlace_buffers[dst_bufidx].get();
g_gpu_device->InvalidateRenderTarget(dst);
// If we're not skipping lines, then we can simply copy the texture.
if (line_skip == 0 && src->GetFormat() == dst->GetFormat())
{
GL_INS_FMT("DeinterlaceExtractField({{{},{}}} {}x{} line_skip={}) => copy direct", x, y, width, height, line_skip);
g_gpu_device->CopyTextureRegion(dst, 0, 0, 0, 0, src, x, y, 0, 0, width, height);
}
else
{
GL_SCOPE_FMT("DeinterlaceExtractField({{{},{}}} {}x{} line_skip={}) => shader copy", x, y, width, height,
line_skip);
// Otherwise, we need to extract every other line from the texture.
src->MakeReadyForSampling();
g_gpu_device->SetRenderTarget(dst);
g_gpu_device->SetPipeline(m_deinterlace_extract_pipeline.get());
g_gpu_device->SetTextureSampler(0, src, g_gpu_device->GetNearestSampler());
const u32 uniforms[] = {x, y, line_skip};
g_gpu_device->PushUniformBuffer(uniforms, sizeof(uniforms));
g_gpu_device->SetViewportAndScissor(0, 0, width, height);
g_gpu_device->Draw(3, 0);
GL_POP();
}
dst->MakeReadyForSampling();
return true;
}
bool GPU::DeinterlaceSetTargetSize(u32 width, u32 height, bool preserve)
{
if (!m_deinterlace_texture || m_deinterlace_texture->GetWidth() != width ||
m_deinterlace_texture->GetHeight() != height)
{
if (!g_gpu_device->ResizeTexture(&m_deinterlace_texture, width, height, GPUTexture::Type::RenderTarget,
GPUTexture::Format::RGBA8, preserve)) [[unlikely]]
{
return false;
}
GL_OBJECT_NAME(m_deinterlace_texture, "Deinterlace target texture");
}
return true;
}
bool GPU::ApplyChromaSmoothing(GPUTexture* src, u32 x, u32 y, u32 width, u32 height)
{
if (!m_chroma_smoothing_texture || m_chroma_smoothing_texture->GetWidth() != width ||
m_chroma_smoothing_texture->GetHeight() != height)
{
if (!g_gpu_device->ResizeTexture(&m_chroma_smoothing_texture, width, height, GPUTexture::Type::RenderTarget,
GPUTexture::Format::RGBA8, false))
{
ClearDisplayTexture();
return false;
}
GL_OBJECT_NAME(m_chroma_smoothing_texture, "Chroma smoothing texture");
}
GL_SCOPE_FMT("ApplyChromaSmoothing({{{},{}}}, {}x{})", x, y, width, height);
src->MakeReadyForSampling();
g_gpu_device->InvalidateRenderTarget(m_chroma_smoothing_texture.get());
g_gpu_device->SetRenderTarget(m_chroma_smoothing_texture.get());
g_gpu_device->SetPipeline(m_chroma_smoothing_pipeline.get());
g_gpu_device->SetTextureSampler(0, src, g_gpu_device->GetNearestSampler());
const u32 uniforms[] = {x, y, width - 1, height - 1};
g_gpu_device->PushUniformBuffer(uniforms, sizeof(uniforms));
g_gpu_device->SetViewportAndScissor(0, 0, width, height);
g_gpu_device->Draw(3, 0);
m_chroma_smoothing_texture->MakeReadyForSampling();
SetDisplayTexture(m_chroma_smoothing_texture.get(), 0, 0, width, height);
return true;
}
Common::Rectangle<float> GPU::CalculateDrawRect(s32 window_width, s32 window_height, float* out_left_padding,
float* out_top_padding, float* out_scale, float* out_x_scale,
bool apply_aspect_ratio /* = true */) const

View File

@ -61,6 +61,7 @@ public:
DOT_TIMER_INDEX = 0,
HBLANK_TIMER_INDEX = 1,
MAX_RESOLUTION_SCALE = 32,
DEINTERLACE_BUFFER_COUNT = 4,
};
enum : u16
@ -239,6 +240,7 @@ protected:
bool remove_alpha);
void SoftReset();
void ClearDisplay();
// Sets dots per scanline
void UpdateCRTCConfig();
@ -313,7 +315,6 @@ protected:
virtual void UpdateVRAM(u32 x, u32 y, u32 width, u32 height, const void* data, bool set_mask, bool check_mask);
virtual void CopyVRAM(u32 src_x, u32 src_y, u32 dst_x, u32 dst_y, u32 width, u32 height);
virtual void DispatchRenderCommand();
virtual void ClearDisplay();
virtual void UpdateDisplay();
virtual void DrawRendererStats();
@ -578,6 +579,12 @@ protected:
bool RenderDisplay(GPUTexture* target, const Common::Rectangle<s32>& draw_rect, bool postfx);
bool Deinterlace(GPUTexture* src, u32 x, u32 y, u32 width, u32 height, u32 field, u32 line_skip);
bool DeinterlaceExtractField(u32 dst_bufidx, GPUTexture* src, u32 x, u32 y, u32 width, u32 height, u32 line_skip);
bool DeinterlaceSetTargetSize(u32 width, u32 height, bool preserve);
void DestroyDeinterlaceTextures();
bool ApplyChromaSmoothing(GPUTexture* src, u32 x, u32 y, u32 width, u32 height);
s32 m_display_width = 0;
s32 m_display_height = 0;
s32 m_display_active_left = 0;
@ -586,6 +593,15 @@ protected:
s32 m_display_active_height = 0;
float m_display_aspect_ratio = 1.0f;
u32 m_current_deinterlace_buffer = 0;
std::unique_ptr<GPUPipeline> m_deinterlace_pipeline;
std::unique_ptr<GPUPipeline> m_deinterlace_extract_pipeline;
std::array<std::unique_ptr<GPUTexture>, DEINTERLACE_BUFFER_COUNT> m_deinterlace_buffers;
std::unique_ptr<GPUTexture> m_deinterlace_texture;
std::unique_ptr<GPUPipeline> m_chroma_smoothing_pipeline;
std::unique_ptr<GPUTexture> m_chroma_smoothing_texture;
std::unique_ptr<GPUPipeline> m_display_pipeline;
GPUTexture* m_display_texture = nullptr;
s32 m_display_texture_view_x = 0;
@ -619,7 +635,7 @@ protected:
Stats m_stats = {};
private:
bool CompileDisplayPipeline();
bool CompileDisplayPipelines(bool display, bool deinterlace, bool chroma_smoothing);
using GP0CommandHandler = bool (GPU::*)();
using GP0CommandHandlerTable = std::array<GP0CommandHandler, 256>;

View File

@ -203,7 +203,6 @@ bool GPU_HW::Initialize()
m_line_detect_mode = (m_resolution_scale > 1) ? g_settings.gpu_line_detect_mode : GPULineDetectMode::Disabled;
m_clamp_uvs = ShouldClampUVs();
m_compute_uv_range = m_clamp_uvs;
m_chroma_smoothing = g_settings.gpu_24bit_chroma_smoothing;
m_downsample_mode = GetDownsampleMode(m_resolution_scale);
m_wireframe_mode = g_settings.gpu_wireframe_mode;
m_disable_color_perspective = features.noperspective_interpolation && ShouldDisableColorPerspective();
@ -336,7 +335,7 @@ void GPU_HW::UpdateSettings(const Settings& old_settings)
m_true_color != g_settings.gpu_true_color || m_debanding != g_settings.gpu_debanding ||
m_per_sample_shading != per_sample_shading || m_scaled_dithering != g_settings.gpu_scaled_dithering ||
m_texture_filtering != g_settings.gpu_texture_filter || m_clamp_uvs != clamp_uvs ||
m_chroma_smoothing != g_settings.gpu_24bit_chroma_smoothing || m_downsample_mode != downsample_mode ||
m_downsample_mode != downsample_mode ||
(m_downsample_mode == GPUDownsampleMode::Box &&
g_settings.gpu_downsample_scale != old_settings.gpu_downsample_scale) ||
m_wireframe_mode != wireframe_mode || m_pgxp_depth_buffer != g_settings.UsingPGXPDepthBuffer() ||
@ -389,7 +388,6 @@ void GPU_HW::UpdateSettings(const Settings& old_settings)
m_line_detect_mode = (m_resolution_scale > 1) ? g_settings.gpu_line_detect_mode : GPULineDetectMode::Disabled;
m_clamp_uvs = clamp_uvs;
m_compute_uv_range = m_clamp_uvs;
m_chroma_smoothing = g_settings.gpu_24bit_chroma_smoothing;
m_downsample_mode = downsample_mode;
m_wireframe_mode = wireframe_mode;
m_disable_color_perspective = disable_color_perspective;
@ -701,10 +699,6 @@ void GPU_HW::ClearFramebuffer()
g_gpu_device->ClearRenderTarget(m_vram_texture.get(), 0);
g_gpu_device->ClearDepth(m_vram_depth_texture.get(), m_pgxp_depth_buffer ? 1.0f : 0.0f);
ClearVRAMDirtyRectangle();
if (m_display_private_texture)
g_gpu_device->ClearRenderTarget(m_display_private_texture.get(), 0);
m_last_depth_z = 1.0f;
}
@ -719,11 +713,11 @@ void GPU_HW::DestroyBuffers()
m_vram_upload_buffer.reset();
m_vram_readback_download_texture.reset();
g_gpu_device->RecycleTexture(std::move(m_downsample_texture));
g_gpu_device->RecycleTexture(std::move(m_vram_extract_texture));
g_gpu_device->RecycleTexture(std::move(m_vram_read_texture));
g_gpu_device->RecycleTexture(std::move(m_vram_depth_texture));
g_gpu_device->RecycleTexture(std::move(m_vram_texture));
g_gpu_device->RecycleTexture(std::move(m_vram_readback_texture));
g_gpu_device->RecycleTexture(std::move(m_display_private_texture));
}
bool GPU_HW::CompilePipelines()
@ -1124,22 +1118,17 @@ bool GPU_HW::CompilePipelines()
{
for (u8 depth_24 = 0; depth_24 < 2; depth_24++)
{
for (u8 interlace_mode = 0; interlace_mode < 3; interlace_mode++)
{
std::unique_ptr<GPUShader> fs = g_gpu_device->CreateShader(
GPUShaderStage::Fragment,
shadergen.GenerateDisplayFragmentShader(
ConvertToBoolUnchecked(depth_24), static_cast<InterlacedRenderMode>(interlace_mode), m_chroma_smoothing));
if (!fs)
return false;
std::unique_ptr<GPUShader> fs = g_gpu_device->CreateShader(
GPUShaderStage::Fragment, shadergen.GenerateVRAMExtractFragmentShader(ConvertToBoolUnchecked(depth_24)));
if (!fs)
return false;
plconfig.fragment_shader = fs.get();
plconfig.fragment_shader = fs.get();
if (!(m_display_pipelines[depth_24][interlace_mode] = g_gpu_device->CreatePipeline(plconfig)))
return false;
if (!(m_vram_extract_pipeline[depth_24] = g_gpu_device->CreatePipeline(plconfig)))
return false;
progress.Increment();
}
progress.Increment();
}
}
@ -1242,6 +1231,9 @@ void GPU_HW::DestroyPipelines()
for (std::unique_ptr<GPUPipeline>& p : m_vram_copy_pipelines)
destroy(p);
for (std::unique_ptr<GPUPipeline>& p : m_vram_extract_pipeline)
destroy(p);
destroy(m_vram_readback_pipeline);
destroy(m_vram_update_depth_pipeline);
destroy(m_vram_write_replacement_pipeline);
@ -1251,8 +1243,6 @@ void GPU_HW::DestroyPipelines()
destroy(m_downsample_blur_pass_pipeline);
destroy(m_downsample_composite_pass_pipeline);
m_downsample_composite_sampler.reset();
m_display_pipelines.enumerate(destroy);
}
GPU_HW::BatchRenderMode GPU_HW::BatchConfig::GetRenderMode() const
@ -1407,14 +1397,6 @@ ALWAYS_INLINE_RELEASE void GPU_HW::DrawBatchVertices(BatchRenderMode render_mode
g_gpu_device->DrawIndexed(num_indices, base_index, base_vertex);
}
void GPU_HW::ClearDisplay()
{
ClearDisplayTexture();
if (m_display_private_texture)
g_gpu_device->ClearRenderTarget(m_display_private_texture.get(), 0xFF000000u);
}
ALWAYS_INLINE_RELEASE void GPU_HW::HandleFlippedQuadTextureCoordinates(BatchVertex* vertices)
{
// Taken from beetle-psx gpu_polygon.cpp
@ -2417,19 +2399,6 @@ ALWAYS_INLINE bool GPU_HW::IsFlushed() const
return (m_batch_index_count == 0);
}
GPU_HW::InterlacedRenderMode GPU_HW::GetInterlacedRenderMode() const
{
if (IsInterlacedDisplayEnabled())
{
return m_GPUSTAT.vertical_resolution ? InterlacedRenderMode::InterleavedFields :
InterlacedRenderMode::SeparateFields;
}
else
{
return InterlacedRenderMode::None;
}
}
ALWAYS_INLINE_RELEASE bool GPU_HW::NeedsTwoPassRendering() const
{
// We need two-pass rendering when using BG-FG blending and texturing, as the transparency can be enabled
@ -3104,6 +3073,8 @@ void GPU_HW::UpdateDisplay()
{
FlushRender();
GL_SCOPE("UpdateDisplay()");
if (g_settings.debugging.show_vram)
{
if (IsUsingMultisampling())
@ -3119,89 +3090,106 @@ void GPU_HW::UpdateDisplay()
SetDisplayParameters(VRAM_WIDTH, VRAM_HEIGHT, 0, 0, VRAM_WIDTH, VRAM_HEIGHT,
static_cast<float>(VRAM_WIDTH) / static_cast<float>(VRAM_HEIGHT));
return;
}
SetDisplayParameters(m_crtc_state.display_width, m_crtc_state.display_height, m_crtc_state.display_origin_left,
m_crtc_state.display_origin_top, m_crtc_state.display_vram_width,
m_crtc_state.display_vram_height, ComputeDisplayAspectRatio());
const bool interlaced = IsInterlacedDisplayEnabled();
const u32 interlaced_field = GetInterlacedDisplayField();
const u32 resolution_scale = m_GPUSTAT.display_area_color_depth_24 ? 1 : m_resolution_scale;
const u32 scaled_vram_offset_x = m_crtc_state.display_vram_left * resolution_scale;
const u32 scaled_vram_offset_y = (m_crtc_state.display_vram_top * resolution_scale) +
((interlaced && m_GPUSTAT.vertical_resolution) ? interlaced_field : 0);
const u32 scaled_display_width = m_crtc_state.display_vram_width * resolution_scale;
const u32 scaled_display_height = m_crtc_state.display_vram_height * resolution_scale;
const u32 read_height = interlaced ? (scaled_display_height / 2u) : scaled_display_height;
const u32 line_skip = m_GPUSTAT.vertical_resolution;
bool drew_anything = false;
if (IsDisplayDisabled())
{
ClearDisplayTexture();
return;
}
else if (!m_GPUSTAT.display_area_color_depth_24 && !IsUsingMultisampling() &&
(scaled_vram_offset_x + scaled_display_width) <= m_vram_texture->GetWidth() &&
(scaled_vram_offset_y + scaled_display_height) <= m_vram_texture->GetHeight())
{
// Fast path if no copies are needed.
if (interlaced)
{
GL_INS("Deinterlace fast path");
drew_anything = true;
Deinterlace(m_vram_texture.get(), scaled_vram_offset_x, scaled_vram_offset_y, scaled_display_width, read_height,
interlaced_field, line_skip);
}
else
{
GL_INS("Direct display");
SetDisplayTexture(m_vram_texture.get(), scaled_vram_offset_x, scaled_vram_offset_y, scaled_display_width,
scaled_display_height);
}
}
else
{
// TODO: use a dynamically sized texture
SetDisplayParameters(m_crtc_state.display_width, m_crtc_state.display_height, m_crtc_state.display_origin_left,
m_crtc_state.display_origin_top, m_crtc_state.display_vram_width,
m_crtc_state.display_vram_height, ComputeDisplayAspectRatio());
const u32 resolution_scale = m_GPUSTAT.display_area_color_depth_24 ? 1 : m_resolution_scale;
const u32 vram_offset_x = m_crtc_state.display_vram_left;
const u32 vram_offset_y = m_crtc_state.display_vram_top;
const u32 scaled_vram_offset_x = vram_offset_x * resolution_scale;
const u32 scaled_vram_offset_y = vram_offset_y * resolution_scale;
const u32 display_width = m_crtc_state.display_vram_width;
const u32 display_height = m_crtc_state.display_vram_height;
const u32 scaled_display_width = display_width * resolution_scale;
const u32 scaled_display_height = display_height * resolution_scale;
const InterlacedRenderMode interlaced = GetInterlacedRenderMode();
if (IsDisplayDisabled())
if (!m_vram_extract_texture || m_vram_extract_texture->GetWidth() != scaled_display_width ||
m_vram_extract_texture->GetHeight() != read_height)
{
ClearDisplayTexture();
}
else if (!m_GPUSTAT.display_area_color_depth_24 && interlaced == InterlacedRenderMode::None &&
!IsUsingMultisampling() && (scaled_vram_offset_x + scaled_display_width) <= m_vram_texture->GetWidth() &&
(scaled_vram_offset_y + scaled_display_height) <= m_vram_texture->GetHeight())
{
if (IsUsingDownsampling())
if (!g_gpu_device->ResizeTexture(&m_vram_extract_texture, scaled_display_width, read_height,
GPUTexture::Type::RenderTarget, GPUTexture::Format::RGBA8)) [[unlikely]]
{
DownsampleFramebuffer(m_vram_texture.get(), scaled_vram_offset_x, scaled_vram_offset_y, scaled_display_width,
scaled_display_height);
ClearDisplayTexture();
return;
}
else
}
g_gpu_device->InvalidateRenderTarget(m_vram_extract_texture.get());
g_gpu_device->SetRenderTarget(m_vram_extract_texture.get());
g_gpu_device->SetPipeline(m_vram_extract_pipeline[BoolToUInt8(m_GPUSTAT.display_area_color_depth_24)].get());
g_gpu_device->SetTextureSampler(0, m_vram_texture.get(), g_gpu_device->GetNearestSampler());
const u32 reinterpret_start_x = m_crtc_state.regs.X * resolution_scale;
const u32 skip_x = (m_crtc_state.display_vram_left - m_crtc_state.regs.X) * resolution_scale;
GL_INS_FMT("Convert 16bpp to 24bpp, skip_x = {}, line_skip = {}", skip_x, line_skip);
const u32 uniforms[4] = {reinterpret_start_x, scaled_vram_offset_y, skip_x, line_skip};
g_gpu_device->PushUniformBuffer(uniforms, sizeof(uniforms));
g_gpu_device->SetViewportAndScissor(0, 0, scaled_display_width, read_height);
g_gpu_device->Draw(3, 0);
m_vram_extract_texture->MakeReadyForSampling();
drew_anything = true;
if (g_settings.gpu_24bit_chroma_smoothing)
{
if (ApplyChromaSmoothing(m_vram_extract_texture.get(), 0, 0, scaled_display_width, read_height))
{
SetDisplayTexture(m_vram_texture.get(), scaled_vram_offset_x, scaled_vram_offset_y, scaled_display_width,
scaled_display_height);
if (interlaced)
Deinterlace(m_display_texture, 0, 0, scaled_display_width, read_height, interlaced_field, 0);
}
}
else
{
if (!m_display_private_texture || m_display_private_texture->GetWidth() != scaled_display_width ||
m_display_private_texture->GetHeight() != scaled_display_height)
{
g_gpu_device->RecycleTexture(std::move(m_display_private_texture));
if (!(m_display_private_texture = g_gpu_device->FetchTexture(
scaled_display_width, scaled_display_height, 1, 1, 1, GPUTexture::Type::RenderTarget, VRAM_RT_FORMAT)))
{
Log_ErrorFmt("Failed to create {}x{} display texture", scaled_display_width, scaled_display_height);
ClearDisplayTexture();
return;
}
GL_OBJECT_NAME(m_display_private_texture, "Display Texture");
}
// TODO: discard vs load for interlaced
if (interlaced == InterlacedRenderMode::None)
g_gpu_device->InvalidateRenderTarget(m_display_private_texture.get());
g_gpu_device->SetRenderTarget(m_display_private_texture.get());
g_gpu_device->SetPipeline(
m_display_pipelines[BoolToUInt8(m_GPUSTAT.display_area_color_depth_24)][static_cast<u8>(interlaced)].get());
g_gpu_device->SetTextureSampler(0, m_vram_texture.get(), g_gpu_device->GetNearestSampler());
const u32 reinterpret_field_offset = (interlaced != InterlacedRenderMode::None) ? GetInterlacedDisplayField() : 0;
const u32 reinterpret_start_x = m_crtc_state.regs.X * resolution_scale;
const u32 reinterpret_crop_left = (m_crtc_state.display_vram_left - m_crtc_state.regs.X) * resolution_scale;
const u32 uniforms[4] = {reinterpret_start_x, scaled_vram_offset_y + reinterpret_field_offset,
reinterpret_crop_left, reinterpret_field_offset};
g_gpu_device->PushUniformBuffer(uniforms, sizeof(uniforms));
g_gpu_device->SetViewportAndScissor(0, 0, scaled_display_width, scaled_display_height);
g_gpu_device->Draw(3, 0);
if (IsUsingDownsampling())
DownsampleFramebuffer(m_display_private_texture.get(), 0, 0, scaled_display_width, scaled_display_height);
if (interlaced)
Deinterlace(m_vram_extract_texture.get(), 0, 0, scaled_display_width, read_height, interlaced_field, 0);
else
SetDisplayTexture(m_display_private_texture.get(), 0, 0, scaled_display_width, scaled_display_height);
RestoreDeviceContext();
SetDisplayTexture(m_vram_extract_texture.get(), 0, 0, scaled_display_width, read_height);
}
}
if (m_downsample_mode != GPUDownsampleMode::Disabled)
{
DebugAssert(m_display_texture);
DownsampleFramebuffer(m_display_texture, m_display_texture_view_x, m_display_texture_view_y,
m_display_texture_view_width, m_display_texture_view_height);
}
if (drew_anything)
RestoreDeviceContext();
}
void GPU_HW::DownsampleFramebuffer(GPUTexture* source, u32 left, u32 top, u32 width, u32 height)

View File

@ -32,13 +32,6 @@ public:
OnlyTransparent
};
enum class InterlacedRenderMode : u8
{
None,
InterleavedFields,
SeparateFields
};
GPU_HW();
~GPU_HW() override;
@ -56,7 +49,6 @@ public:
std::tuple<u32, u32> GetEffectiveDisplayResolution(bool scaled = true) override final;
std::tuple<u32, u32> GetFullDisplayResolution(bool scaled = true) override final;
void ClearDisplay() override;
void UpdateDisplay() override;
private:
@ -162,9 +154,6 @@ private:
/// Returns the value to be written to the depth buffer for the current operation for mask bit emulation.
float GetCurrentNormalizedVertexDepth() const;
/// Returns the interlaced mode to use when scanning out/displaying.
InterlacedRenderMode GetInterlacedRenderMode() const;
/// Returns if the draw needs to be broken into opaque/transparent passes.
bool NeedsTwoPassRendering() const;
@ -212,7 +201,6 @@ private:
std::unique_ptr<GPUTexture> m_vram_readback_texture;
std::unique_ptr<GPUDownloadTexture> m_vram_readback_download_texture;
std::unique_ptr<GPUTexture> m_vram_replacement_texture;
std::unique_ptr<GPUTexture> m_display_private_texture; // TODO: Move to base.
std::unique_ptr<GPUTextureBuffer> m_vram_upload_buffer;
std::unique_ptr<GPUTexture> m_vram_write_texture;
@ -237,7 +225,6 @@ private:
bool m_supports_framebuffer_fetch : 1 = false;
bool m_per_sample_shading : 1 = false;
bool m_scaled_dithering : 1 = false;
bool m_chroma_smoothing : 1 = false;
bool m_disable_color_perspective : 1 = false;
GPUTextureFilter m_texture_filtering = GPUTextureFilter::Nearest;
@ -275,12 +262,11 @@ private:
std::unique_ptr<GPUPipeline> m_vram_readback_pipeline;
std::unique_ptr<GPUPipeline> m_vram_update_depth_pipeline;
// [depth_24][interlace_mode]
DimensionalArray<std::unique_ptr<GPUPipeline>, 3, 2> m_display_pipelines{};
std::unique_ptr<GPUPipeline> m_vram_write_replacement_pipeline;
std::array<std::unique_ptr<GPUPipeline>, 2> m_vram_extract_pipeline; // [24bit]
std::unique_ptr<GPUTexture> m_vram_extract_texture;
std::unique_ptr<GPUTexture> m_downsample_texture;
std::unique_ptr<GPUPipeline> m_downsample_first_pass_pipeline;
std::unique_ptr<GPUPipeline> m_downsample_mid_pass_pipeline;

View File

@ -1019,36 +1019,18 @@ float3 ApplyDebanding(float2 frag_coord)
return ss.str();
}
std::string GPU_HW_ShaderGen::GenerateDisplayFragmentShader(bool depth_24bit,
GPU_HW::InterlacedRenderMode interlace_mode,
bool smooth_chroma)
std::string GPU_HW_ShaderGen::GenerateVRAMExtractFragmentShader(bool depth_24bit)
{
std::stringstream ss;
WriteHeader(ss);
DefineMacro(ss, "DEPTH_24BIT", depth_24bit);
DefineMacro(ss, "INTERLACED", interlace_mode != GPU_HW::InterlacedRenderMode::None);
DefineMacro(ss, "INTERLEAVED", interlace_mode == GPU_HW::InterlacedRenderMode::InterleavedFields);
DefineMacro(ss, "SMOOTH_CHROMA", smooth_chroma);
DefineMacro(ss, "MULTISAMPLED", UsingMSAA());
WriteCommonFunctions(ss);
DeclareUniformBuffer(ss, {"uint2 u_vram_offset", "uint u_crop_left", "uint u_field_offset"}, true);
DeclareUniformBuffer(ss, {"uint2 u_vram_offset", "uint u_skip_x", "uint u_line_skip"}, true);
DeclareTexture(ss, "samp0", 0, UsingMSAA());
ss << R"(
float3 RGBToYUV(float3 rgb)
{
return float3(dot(rgb.rgb, float3(0.299f, 0.587f, 0.114f)),
dot(rgb.rgb, float3(-0.14713f, -0.28886f, 0.436f)),
dot(rgb.rgb, float3(0.615f, -0.51499f, -0.10001f)));
}
float3 YUVToRGB(float3 yuv)
{
return float3(dot(yuv, float3(1.0f, 0.0f, 1.13983f)),
dot(yuv, float3(1.0f, -0.39465f, -0.58060f)),
dot(yuv, float3(1.0f, 2.03211f, 0.0f)));
}
float4 LoadVRAM(int2 coords)
{
#if MULTISAMPLING
@ -1079,61 +1061,15 @@ float3 SampleVRAM24(uint2 icoords)
return float3(float(s1s0 & 0xFFu) / 255.0, float((s1s0 >> 8u) & 0xFFu) / 255.0,
float((s1s0 >> 16u) & 0xFFu) / 255.0);
}
float3 SampleVRAMAverage2x2(uint2 icoords)
{
float3 value = SampleVRAM24(icoords);
value += SampleVRAM24(icoords + uint2(0, 1));
value += SampleVRAM24(icoords + uint2(1, 0));
value += SampleVRAM24(icoords + uint2(1, 1));
return value * 0.25;
}
float3 SampleVRAM24Smoothed(uint2 icoords)
{
int2 base = int2(icoords) - 1;
uint2 low = uint2(max(base & ~1, int2(0, 0)));
uint2 high = low + 2u;
float2 coeff = vec2(base & 1) * 0.5 + 0.25;
float3 p = SampleVRAM24(icoords);
float3 p00 = SampleVRAMAverage2x2(low);
float3 p01 = SampleVRAMAverage2x2(uint2(low.x, high.y));
float3 p10 = SampleVRAMAverage2x2(uint2(high.x, low.y));
float3 p11 = SampleVRAMAverage2x2(high);
float3 s = lerp(lerp(p00, p10, coeff.x),
lerp(p01, p11, coeff.x),
coeff.y);
float y = RGBToYUV(p).x;
float2 uv = RGBToYUV(s).yz;
return YUVToRGB(float3(y, uv));
}
)";
DeclareFragmentEntryPoint(ss, 0, 1, {}, true, 1);
ss << R"(
{
uint2 icoords = uint2(v_pos.xy) + uint2(u_crop_left, 0u);
#if INTERLACED
if ((icoords.y & 1u) != u_field_offset)
discard;
#if !INTERLEAVED
icoords.y /= 2u;
#else
icoords.y &= ~1u;
#endif
#endif
uint2 icoords = uint2(uint(v_pos.x) + u_skip_x, uint(v_pos.y) << u_line_skip);
#if DEPTH_24BIT
#if SMOOTH_CHROMA
o_col0 = float4(SampleVRAM24Smoothed(icoords), 1.0);
#else
o_col0 = float4(SampleVRAM24(icoords), 1.0);
#endif
o_col0 = float4(SampleVRAM24(icoords), 1.0);
#else
o_col0 = float4(LoadVRAM(int2((icoords + u_vram_offset) % VRAM_SIZE)).rgb, 1.0);
#endif

View File

@ -17,8 +17,6 @@ public:
std::string GenerateBatchVertexShader(bool textured);
std::string GenerateBatchFragmentShader(GPU_HW::BatchRenderMode render_mode, GPUTransparencyMode transparency,
GPUTextureMode texture_mode, bool dithering, bool interlacing);
std::string GenerateDisplayFragmentShader(bool depth_24bit, GPU_HW::InterlacedRenderMode interlace_mode,
bool smooth_chroma);
std::string GenerateWireframeGeometryShader();
std::string GenerateWireframeFragmentShader();
std::string GenerateVRAMReadFragmentShader();
@ -26,6 +24,7 @@ public:
std::string GenerateVRAMCopyFragmentShader();
std::string GenerateVRAMFillFragmentShader(bool wrapped, bool interlaced);
std::string GenerateVRAMUpdateDepthFragmentShader();
std::string GenerateVRAMExtractFragmentShader(bool depth_24bit);
std::string GenerateAdaptiveDownsampleVertexShader();
std::string GenerateAdaptiveDownsampleMipFragmentShader(bool first_pass);

View File

@ -83,3 +83,181 @@ std::string GPUShaderGen::GenerateDisplaySharpBilinearFragmentShader()
return ss.str();
}
std::string GPUShaderGen::GenerateInterleavedFieldExtractFragmentShader()
{
std::stringstream ss;
WriteHeader(ss);
DeclareUniformBuffer(ss, {"uint2 u_src_offset", "uint u_line_skip"}, true);
DeclareTexture(ss, "samp0", 0, false);
DeclareFragmentEntryPoint(ss, 0, 1, {}, true);
ss << R"(
{
uint2 tcoord = u_src_offset + uint2(uint(v_pos.x), uint(v_pos.y) << u_line_skip);
o_col0 = LOAD_TEXTURE(samp0, int2(tcoord), 0);
}
)";
return ss.str();
}
std::string GPUShaderGen::GenerateDeinterlaceWeaveFragmentShader()
{
std::stringstream ss;
WriteHeader(ss);
DeclareUniformBuffer(ss, {"uint2 u_src_offset", "uint u_render_field", "uint u_line_skip"}, true);
DeclareTexture(ss, "samp0", 0, false);
DeclareFragmentEntryPoint(ss, 0, 1, {}, true);
ss << R"(
{
uint2 fcoord = uint2(v_pos.xy);
if ((fcoord.y & 1) != u_render_field)
discard;
uint2 tcoord = u_src_offset + uint2(fcoord.x, (fcoord.y / 2u) << u_line_skip);
o_col0 = LOAD_TEXTURE(samp0, int2(tcoord), 0);
})";
return ss.str();
}
std::string GPUShaderGen::GenerateDeinterlaceBlendFragmentShader()
{
std::stringstream ss;
WriteHeader(ss);
DeclareTexture(ss, "samp0", 0, false);
DeclareTexture(ss, "samp1", 1, false);
DeclareFragmentEntryPoint(ss, 0, 1, {}, true);
ss << R"(
{
uint2 uv = uint2(v_pos.xy);
float4 c0 = LOAD_TEXTURE(samp0, int2(uv), 0);
float4 c1 = LOAD_TEXTURE(samp1, int2(uv), 0);
o_col0 = (c0 + c1) * 0.5f;
}
)";
return ss.str();
}
std::string GPUShaderGen::GenerateFastMADReconstructFragmentShader()
{
std::stringstream ss;
WriteHeader(ss);
DeclareUniformBuffer(ss, {"uint u_current_field", "uint u_height"}, true);
DeclareTexture(ss, "samp0", 0, false);
DeclareTexture(ss, "samp1", 1, false);
DeclareTexture(ss, "samp2", 2, false);
DeclareTexture(ss, "samp3", 3, false);
ss << R"(
CONSTANT float3 SENSITIVITY = float3(0.08f, 0.08f, 0.08f);
)";
DeclareFragmentEntryPoint(ss, 0, 1, {}, true);
ss << R"(
{
int2 uv = int2(int(v_pos.x), int(v_pos.y) >> 1);
float3 cur = LOAD_TEXTURE(samp0, uv, 0).rgb;
float3 hn = LOAD_TEXTURE(samp0, uv + int2(0, -1), 0).rgb;
float3 cn = LOAD_TEXTURE(samp1, uv, 0).rgb;
float3 ln = LOAD_TEXTURE(samp0, uv + int2(0, 1), 0).rgb;
float3 ho = LOAD_TEXTURE(samp2, uv + int2(0, -1), 0).rgb;
float3 co = LOAD_TEXTURE(samp3, uv, 0).rgb;
float3 lo = LOAD_TEXTURE(samp2, uv + int2(0, 1), 0).rgb;
float3 mh = abs(hn.rgb - ho.rgb) - SENSITIVITY;
float3 mc = abs(cn.rgb - co.rgb) - SENSITIVITY;
float3 ml = abs(ln.rgb - lo.rgb) - SENSITIVITY;
float3 mmaxv = max(mh, max(mc, ml));
float mmax = max(mmaxv.r, max(mmaxv.g, mmaxv.b));
// Is pixel F [n][ x , y ] present in the Current Field f [n] ?
uint row = uint(v_pos.y);
if ((row & 1u) == u_current_field)
{
// Directly uses the pixel from the Current Field
o_col0.rgb = cur;
}
else if (row > 0 && row < u_height && mmax > 0.0f)
{
// Reconstructs the missing pixel as the average of the same pixel from the line above and the
// line below it in the Current Field.
o_col0.rgb = (hn + ln) / 2.0;
}
else
{
// Reconstructs the missing pixel as the same pixel from the Previous Field.
o_col0.rgb = cn;
}
o_col0.a = 1.0f;
}
)";
return ss.str();
}
std::string GPUShaderGen::GenerateChromaSmoothingFragmentShader()
{
std::stringstream ss;
WriteHeader(ss);
DeclareUniformBuffer(ss, {"uint2 u_sample_offset", "uint2 u_clamp_size"}, true);
DeclareTexture(ss, "samp0", 0);
ss << R"(
float3 RGBToYUV(float3 rgb)
{
return float3(dot(rgb.rgb, float3(0.299f, 0.587f, 0.114f)),
dot(rgb.rgb, float3(-0.14713f, -0.28886f, 0.436f)),
dot(rgb.rgb, float3(0.615f, -0.51499f, -0.10001f)));
}
float3 YUVToRGB(float3 yuv)
{
return float3(dot(yuv, float3(1.0f, 0.0f, 1.13983f)),
dot(yuv, float3(1.0f, -0.39465f, -0.58060f)),
dot(yuv, float3(1.0f, 2.03211f, 0.0f)));
}
float3 SampleVRAMAverage2x2(uint2 icoords)
{
float3 value = LOAD_TEXTURE(samp0, icoords, 0).rgb;
value += LOAD_TEXTURE(samp0, icoords + uint2(0, 1), 0).rgb;
value += LOAD_TEXTURE(samp0, icoords + uint2(1, 0), 0).rgb;
value += LOAD_TEXTURE(samp0, icoords + uint2(1, 1), 0).rgb;
return value * 0.25;
}
)";
DeclareFragmentEntryPoint(ss, 0, 1, {}, true, 1);
ss << R"(
{
uint2 icoords = uint2(v_pos.xy) + u_sample_offset;
int2 base = int2(icoords) - 1;
uint2 low = uint2(max(base & ~1, int2(0, 0)));
uint2 high = min(low + 2u, u_clamp_size);
float2 coeff = vec2(base & 1) * 0.5 + 0.25;
float3 p = LOAD_TEXTURE(samp0, icoords, 0);
float3 p00 = SampleVRAMAverage2x2(low);
float3 p01 = SampleVRAMAverage2x2(uint2(low.x, high.y));
float3 p10 = SampleVRAMAverage2x2(uint2(high.x, low.y));
float3 p11 = SampleVRAMAverage2x2(high);
float3 s = lerp(lerp(p00, p10, coeff.x),
lerp(p01, p11, coeff.x),
coeff.y);
float y = RGBToYUV(p).x;
float2 uv = RGBToYUV(s).yz;
o_col0 = float4(YUVToRGB(float3(y, uv)), 1.0);
}
)";
return ss.str();
}

View File

@ -15,6 +15,13 @@ public:
std::string GenerateDisplayFragmentShader(bool clamp_uv);
std::string GenerateDisplaySharpBilinearFragmentShader();
std::string GenerateInterleavedFieldExtractFragmentShader();
std::string GenerateDeinterlaceWeaveFragmentShader();
std::string GenerateDeinterlaceBlendFragmentShader();
std::string GenerateFastMADReconstructFragmentShader();
std::string GenerateChromaSmoothingFragmentShader();
private:
void WriteDisplayUniformBuffer(std::stringstream& ss);
};

View File

@ -28,7 +28,7 @@ GPU_SW::GPU_SW() = default;
GPU_SW::~GPU_SW()
{
g_gpu_device->RecycleTexture(std::move(m_private_display_texture));
g_gpu_device->RecycleTexture(std::move(m_upload_texture));
m_backend.Shutdown();
}
@ -92,18 +92,18 @@ void GPU_SW::UpdateSettings(const Settings& old_settings)
GPUTexture* GPU_SW::GetDisplayTexture(u32 width, u32 height, GPUTexture::Format format)
{
if (!m_private_display_texture || m_private_display_texture->GetWidth() != width ||
m_private_display_texture->GetHeight() != height || m_private_display_texture->GetFormat() != format)
if (!m_upload_texture || m_upload_texture->GetWidth() != width || m_upload_texture->GetHeight() != height ||
m_upload_texture->GetFormat() != format)
{
ClearDisplayTexture();
g_gpu_device->RecycleTexture(std::move(m_private_display_texture));
m_private_display_texture =
g_gpu_device->RecycleTexture(std::move(m_upload_texture));
m_upload_texture =
g_gpu_device->FetchTexture(width, height, 1, 1, 1, GPUTexture::Type::DynamicTexture, format, nullptr, 0);
if (!m_private_display_texture)
if (!m_upload_texture)
Log_ErrorPrintf("Failed to create %ux%u %u texture", width, height, static_cast<u32>(format));
}
return m_private_display_texture.get();
return m_upload_texture.get();
}
template<GPUTexture::Format out_format, typename out_type>
@ -240,35 +240,26 @@ ALWAYS_INLINE void CopyOutRow16<GPUTexture::Format::BGRA8, u32>(const u16* src_p
}
template<GPUTexture::Format display_format>
void GPU_SW::CopyOut15Bit(u32 src_x, u32 src_y, u32 width, u32 height, u32 field, bool interlaced, bool interleaved)
ALWAYS_INLINE_RELEASE bool GPU_SW::CopyOut15Bit(u32 src_x, u32 src_y, u32 width, u32 height, u32 line_skip)
{
using OutputPixelType =
std::conditional_t<display_format == GPUTexture::Format::RGBA8 || display_format == GPUTexture::Format::BGRA8, u32,
u16>;
GPUTexture* texture = GetDisplayTexture(width, height, display_format);
if (!texture)
return;
if (!texture) [[unlikely]]
return false;
u32 dst_stride = GPU_MAX_DISPLAY_WIDTH * sizeof(OutputPixelType);
u8* dst_ptr = m_display_texture_buffer.data() + (interlaced ? (field != 0 ? dst_stride : 0) : 0);
const bool mapped =
(!interlaced && texture->Map(reinterpret_cast<void**>(&dst_ptr), &dst_stride, 0, 0, width, height));
const u32 output_stride = dst_stride;
const u8 interlaced_shift = BoolToUInt8(interlaced);
const u8 interleaved_shift = BoolToUInt8(interleaved);
u32 dst_stride = width * sizeof(OutputPixelType);
u8* dst_ptr = m_upload_buffer.data();
const bool mapped = texture->Map(reinterpret_cast<void**>(&dst_ptr), &dst_stride, 0, 0, width, height);
// Fast path when not wrapping around.
if ((src_x + width) <= VRAM_WIDTH && (src_y + height) <= VRAM_HEIGHT)
{
const u32 rows = height >> interlaced_shift;
dst_stride <<= interlaced_shift;
const u16* src_ptr = &g_vram[src_y * VRAM_WIDTH + src_x];
const u32 src_step = VRAM_WIDTH << interleaved_shift;
for (u32 row = 0; row < rows; row++)
const u32 src_step = VRAM_WIDTH << line_skip;
for (u32 row = 0; row < height; row++)
{
CopyOutRow16<display_format>(src_ptr, reinterpret_cast<OutputPixelType*>(dst_ptr), width);
src_ptr += src_step;
@ -277,11 +268,9 @@ void GPU_SW::CopyOut15Bit(u32 src_x, u32 src_y, u32 width, u32 height, u32 field
}
else
{
const u32 rows = height >> interlaced_shift;
dst_stride <<= interlaced_shift;
const u32 end_x = src_x + width;
for (u32 row = 0; row < rows; row++)
const u32 y_step = (1 << line_skip);
for (u32 row = 0; row < height; row++)
{
const u16* src_row_ptr = &g_vram[(src_y % VRAM_HEIGHT) * VRAM_WIDTH];
OutputPixelType* dst_row_ptr = reinterpret_cast<OutputPixelType*>(dst_ptr);
@ -289,7 +278,7 @@ void GPU_SW::CopyOut15Bit(u32 src_x, u32 src_y, u32 width, u32 height, u32 field
for (u32 col = src_x; col < end_x; col++)
*(dst_row_ptr++) = VRAM16ToOutput<display_format, OutputPixelType>(src_row_ptr[col % VRAM_WIDTH]);
src_y += (1 << interleaved_shift);
src_y += y_step;
dst_ptr += dst_stride;
}
}
@ -297,61 +286,31 @@ void GPU_SW::CopyOut15Bit(u32 src_x, u32 src_y, u32 width, u32 height, u32 field
if (mapped)
texture->Unmap();
else
texture->Update(0, 0, width, height, m_display_texture_buffer.data(), output_stride);
texture->Update(0, 0, width, height, m_upload_buffer.data(), dst_stride);
SetDisplayTexture(texture, 0, 0, width, height);
}
void GPU_SW::CopyOut15Bit(GPUTexture::Format display_format, u32 src_x, u32 src_y, u32 width, u32 height, u32 field,
bool interlaced, bool interleaved)
{
switch (display_format)
{
case GPUTexture::Format::RGBA5551:
CopyOut15Bit<GPUTexture::Format::RGBA5551>(src_x, src_y, width, height, field, interlaced, interleaved);
break;
case GPUTexture::Format::RGB565:
CopyOut15Bit<GPUTexture::Format::RGB565>(src_x, src_y, width, height, field, interlaced, interleaved);
break;
case GPUTexture::Format::RGBA8:
CopyOut15Bit<GPUTexture::Format::RGBA8>(src_x, src_y, width, height, field, interlaced, interleaved);
break;
case GPUTexture::Format::BGRA8:
CopyOut15Bit<GPUTexture::Format::BGRA8>(src_x, src_y, width, height, field, interlaced, interleaved);
break;
default:
break;
}
return true;
}
template<GPUTexture::Format display_format>
void GPU_SW::CopyOut24Bit(u32 src_x, u32 src_y, u32 skip_x, u32 width, u32 height, u32 field, bool interlaced,
bool interleaved)
ALWAYS_INLINE_RELEASE bool GPU_SW::CopyOut24Bit(u32 src_x, u32 src_y, u32 skip_x, u32 width, u32 height, u32 line_skip)
{
using OutputPixelType =
std::conditional_t<display_format == GPUTexture::Format::RGBA8 || display_format == GPUTexture::Format::BGRA8, u32,
u16>;
GPUTexture* texture = GetDisplayTexture(width, height, display_format);
if (!texture)
return;
if (!texture) [[unlikely]]
return false;
u32 dst_stride = Common::AlignUpPow2<u32>(width * sizeof(OutputPixelType), 4);
u8* dst_ptr = m_display_texture_buffer.data() + (interlaced ? (field != 0 ? dst_stride : 0) : 0);
const bool mapped =
(!interlaced && texture->Map(reinterpret_cast<void**>(&dst_ptr), &dst_stride, 0, 0, width, height));
u8* dst_ptr = m_upload_buffer.data();
const bool mapped = texture->Map(reinterpret_cast<void**>(&dst_ptr), &dst_stride, 0, 0, width, height);
const u32 output_stride = dst_stride;
const u8 interlaced_shift = BoolToUInt8(interlaced);
const u8 interleaved_shift = BoolToUInt8(interleaved);
const u32 rows = height >> interlaced_shift;
dst_stride <<= interlaced_shift;
if ((src_x + width) <= VRAM_WIDTH && (src_y + (rows << interleaved_shift)) <= VRAM_HEIGHT)
if ((src_x + width) <= VRAM_WIDTH && (src_y + (height << line_skip)) <= VRAM_HEIGHT)
{
const u8* src_ptr = reinterpret_cast<const u8*>(&g_vram[src_y * VRAM_WIDTH + src_x]) + (skip_x * 3);
const u32 src_stride = (VRAM_WIDTH << interleaved_shift) * sizeof(u16);
for (u32 row = 0; row < rows; row++)
const u32 src_stride = (VRAM_WIDTH << line_skip) * sizeof(u16);
for (u32 row = 0; row < height; row++)
{
if constexpr (display_format == GPUTexture::Format::RGBA8)
{
@ -407,7 +366,9 @@ void GPU_SW::CopyOut24Bit(u32 src_x, u32 src_y, u32 skip_x, u32 width, u32 heigh
}
else
{
for (u32 row = 0; row < rows; row++)
const u32 y_step = (1 << line_skip);
for (u32 row = 0; row < height; row++)
{
const u16* src_row_ptr = &g_vram[(src_y % VRAM_HEIGHT) * VRAM_WIDTH];
OutputPixelType* dst_row_ptr = reinterpret_cast<OutputPixelType*>(dst_ptr);
@ -438,7 +399,7 @@ void GPU_SW::CopyOut24Bit(u32 src_x, u32 src_y, u32 skip_x, u32 width, u32 heigh
}
}
src_y += (1 << interleaved_shift);
src_y += y_step;
dst_ptr += dst_stride;
}
}
@ -446,36 +407,55 @@ void GPU_SW::CopyOut24Bit(u32 src_x, u32 src_y, u32 skip_x, u32 width, u32 heigh
if (mapped)
texture->Unmap();
else
texture->Update(0, 0, width, height, m_display_texture_buffer.data(), output_stride);
texture->Update(0, 0, width, height, m_upload_buffer.data(), dst_stride);
SetDisplayTexture(texture, 0, 0, width, height);
return true;
}
void GPU_SW::CopyOut24Bit(GPUTexture::Format display_format, u32 src_x, u32 src_y, u32 skip_x, u32 width, u32 height,
u32 field, bool interlaced, bool interleaved)
bool GPU_SW::CopyOut(u32 src_x, u32 src_y, u32 skip_x, u32 width, u32 height, u32 line_skip, bool is_24bit)
{
switch (display_format)
if (!is_24bit)
{
case GPUTexture::Format::RGBA5551:
CopyOut24Bit<GPUTexture::Format::RGBA5551>(src_x, src_y, skip_x, width, height, field, interlaced, interleaved);
break;
case GPUTexture::Format::RGB565:
CopyOut24Bit<GPUTexture::Format::RGB565>(src_x, src_y, skip_x, width, height, field, interlaced, interleaved);
break;
case GPUTexture::Format::RGBA8:
CopyOut24Bit<GPUTexture::Format::RGBA8>(src_x, src_y, skip_x, width, height, field, interlaced, interleaved);
break;
case GPUTexture::Format::BGRA8:
CopyOut24Bit<GPUTexture::Format::BGRA8>(src_x, src_y, skip_x, width, height, field, interlaced, interleaved);
break;
default:
break;
}
}
DebugAssert(skip_x == 0);
void GPU_SW::ClearDisplay()
{
std::memset(m_display_texture_buffer.data(), 0, m_display_texture_buffer.size());
switch (m_16bit_display_format)
{
case GPUTexture::Format::RGBA5551:
return CopyOut15Bit<GPUTexture::Format::RGBA5551>(src_x, src_y, width, height, line_skip);
case GPUTexture::Format::RGB565:
return CopyOut15Bit<GPUTexture::Format::RGB565>(src_x, src_y, width, height, line_skip);
case GPUTexture::Format::RGBA8:
return CopyOut15Bit<GPUTexture::Format::RGBA8>(src_x, src_y, width, height, line_skip);
case GPUTexture::Format::BGRA8:
return CopyOut15Bit<GPUTexture::Format::BGRA8>(src_x, src_y, width, height, line_skip);
default:
UnreachableCode();
}
}
else
{
switch (m_24bit_display_format)
{
case GPUTexture::Format::RGBA5551:
return CopyOut24Bit<GPUTexture::Format::RGBA5551>(src_x, src_y, skip_x, width, height, line_skip);
case GPUTexture::Format::RGB565:
return CopyOut24Bit<GPUTexture::Format::RGB565>(src_x, src_y, skip_x, width, height, line_skip);
case GPUTexture::Format::RGBA8:
return CopyOut24Bit<GPUTexture::Format::RGBA8>(src_x, src_y, skip_x, width, height, line_skip);
case GPUTexture::Format::BGRA8:
return CopyOut24Bit<GPUTexture::Format::BGRA8>(src_x, src_y, skip_x, width, height, line_skip);
default:
UnreachableCode();
}
}
}
void GPU_SW::UpdateDisplay()
@ -495,45 +475,49 @@ void GPU_SW::UpdateDisplay()
return;
}
const u32 vram_offset_y = m_crtc_state.display_vram_top;
const u32 display_width = m_crtc_state.display_vram_width;
const u32 display_height = m_crtc_state.display_vram_height;
const bool is_24bit = m_GPUSTAT.display_area_color_depth_24;
const bool interlaced = IsInterlacedDisplayEnabled();
const u32 field = GetInterlacedDisplayField();
const u32 vram_offset_x = is_24bit ? m_crtc_state.regs.X : m_crtc_state.display_vram_left;
const u32 vram_offset_y =
m_crtc_state.display_vram_top + ((interlaced && m_GPUSTAT.vertical_resolution) ? field : 0);
const u32 skip_x = is_24bit ? (m_crtc_state.display_vram_left - m_crtc_state.regs.X) : 0;
const u32 read_width = m_crtc_state.display_vram_width;
const u32 read_height = interlaced ? (m_crtc_state.display_vram_height / 2) : m_crtc_state.display_vram_height;
if (IsInterlacedDisplayEnabled())
{
const u32 field = GetInterlacedDisplayField();
if (m_GPUSTAT.display_area_color_depth_24)
const u32 line_skip = m_GPUSTAT.vertical_resolution;
if (CopyOut(vram_offset_x, vram_offset_y, skip_x, read_width, read_height, line_skip, is_24bit))
{
CopyOut24Bit(m_24bit_display_format, m_crtc_state.regs.X, vram_offset_y + field,
m_crtc_state.display_vram_left - m_crtc_state.regs.X, display_width, display_height, field, true,
m_GPUSTAT.vertical_resolution);
}
else
{
CopyOut15Bit(m_16bit_display_format, m_crtc_state.display_vram_left, vram_offset_y + field, display_width,
display_height, field, true, m_GPUSTAT.vertical_resolution);
if (is_24bit && g_settings.gpu_24bit_chroma_smoothing)
{
if (ApplyChromaSmoothing(m_upload_texture.get(), 0, 0, read_width, read_height))
Deinterlace(m_display_texture, 0, 0, read_width, read_height, field, 0);
}
else
{
Deinterlace(m_upload_texture.get(), 0, 0, read_width, read_height, field, 0);
}
}
}
else
{
if (m_GPUSTAT.display_area_color_depth_24)
if (CopyOut(vram_offset_x, vram_offset_y, skip_x, read_width, read_height, 0, is_24bit))
{
CopyOut24Bit(m_24bit_display_format, m_crtc_state.regs.X, vram_offset_y,
m_crtc_state.display_vram_left - m_crtc_state.regs.X, display_width, display_height, 0, false,
false);
}
else
{
CopyOut15Bit(m_16bit_display_format, m_crtc_state.display_vram_left, vram_offset_y, display_width,
display_height, 0, false, false);
if (is_24bit && g_settings.gpu_24bit_chroma_smoothing)
ApplyChromaSmoothing(m_upload_texture.get(), 0, 0, read_width, read_height);
else
SetDisplayTexture(m_upload_texture.get(), 0, 0, read_width, read_height);
}
}
}
else
{
CopyOut15Bit(m_16bit_display_format, 0, 0, VRAM_WIDTH, VRAM_HEIGHT, 0, false, false);
SetDisplayParameters(VRAM_WIDTH, VRAM_HEIGHT, 0, 0, VRAM_WIDTH, VRAM_HEIGHT,
static_cast<float>(VRAM_WIDTH) / static_cast<float>(VRAM_HEIGHT));
if (CopyOut(0, 0, 0, VRAM_WIDTH, VRAM_HEIGHT, 0, false))
SetDisplayTexture(m_upload_texture.get(), 0, 0, VRAM_WIDTH, VRAM_HEIGHT);
}
}

View File

@ -42,17 +42,13 @@ protected:
void CopyVRAM(u32 src_x, u32 src_y, u32 dst_x, u32 dst_y, u32 width, u32 height) override;
template<GPUTexture::Format display_format>
void CopyOut15Bit(u32 src_x, u32 src_y, u32 width, u32 height, u32 field, bool interlaced, bool interleaved);
void CopyOut15Bit(GPUTexture::Format display_format, u32 src_x, u32 src_y, u32 width, u32 height, u32 field,
bool interlaced, bool interleaved);
bool CopyOut15Bit(u32 src_x, u32 src_y, u32 width, u32 height, u32 line_skip);
template<GPUTexture::Format display_format>
void CopyOut24Bit(u32 src_x, u32 src_y, u32 skip_x, u32 width, u32 height, u32 field, bool interlaced,
bool interleaved);
void CopyOut24Bit(GPUTexture::Format display_format, u32 src_x, u32 src_y, u32 skip_x, u32 width, u32 height,
u32 field, bool interlaced, bool interleaved);
bool CopyOut24Bit(u32 src_x, u32 src_y, u32 skip_x, u32 width, u32 height, u32 line_skip);
bool CopyOut(u32 src_x, u32 src_y, u32 skip_x, u32 width, u32 height, u32 line_skip, bool is_24bit);
void ClearDisplay() override;
void UpdateDisplay() override;
void DispatchRenderCommand() override;
@ -62,10 +58,10 @@ protected:
GPUTexture* GetDisplayTexture(u32 width, u32 height, GPUTexture::Format format);
FixedHeapArray<u8, GPU_MAX_DISPLAY_WIDTH * GPU_MAX_DISPLAY_HEIGHT * sizeof(u32)> m_display_texture_buffer;
FixedHeapArray<u8, GPU_MAX_DISPLAY_WIDTH * GPU_MAX_DISPLAY_HEIGHT * sizeof(u32)> m_upload_buffer;
GPUTexture::Format m_16bit_display_format = GPUTexture::Format::RGB565;
GPUTexture::Format m_24bit_display_format = GPUTexture::Format::RGBA8;
std::unique_ptr<GPUTexture> m_private_display_texture; // TODO: Move to base.
std::unique_ptr<GPUTexture> m_upload_texture;
GPU_SW_Backend m_backend;
};

View File

@ -224,6 +224,11 @@ void Settings::Load(SettingsInterface& si)
gpu_pgxp_depth_buffer = si.GetBoolValue("GPU", "PGXPDepthBuffer", false);
SetPGXPDepthClearThreshold(si.GetFloatValue("GPU", "PGXPDepthClearThreshold", DEFAULT_GPU_PGXP_DEPTH_THRESHOLD));
display_deinterlacing_mode =
ParseDisplayDeinterlacingMode(si.GetStringValue("Display", "DeinterlacingMode",
GetDisplayDeinterlacingModeName(DEFAULT_DISPLAY_DEINTERLACING_MODE))
.c_str())
.value_or(DEFAULT_DISPLAY_DEINTERLACING_MODE);
display_crop_mode =
ParseDisplayCropMode(
si.GetStringValue("Display", "CropMode", GetDisplayCropModeName(DEFAULT_DISPLAY_CROP_MODE)).c_str())
@ -498,6 +503,7 @@ void Settings::Save(SettingsInterface& si) const
si.SetBoolValue("GPU", "PGXPDepthBuffer", gpu_pgxp_depth_buffer);
si.SetFloatValue("GPU", "PGXPDepthClearThreshold", GetPGXPDepthClearThreshold());
si.SetStringValue("Display", "DeinterlacingMode", GetDisplayDeinterlacingModeName(display_deinterlacing_mode));
si.SetStringValue("Display", "CropMode", GetDisplayCropModeName(display_crop_mode));
si.SetIntValue("Display", "ActiveStartOffset", display_active_start_offset);
si.SetIntValue("Display", "ActiveEndOffset", display_active_end_offset);
@ -1198,6 +1204,44 @@ const char* Settings::GetGPUWireframeModeDisplayName(GPUWireframeMode mode)
return Host::TranslateToCString("GPUWireframeMode", s_wireframe_mode_display_names[static_cast<int>(mode)]);
}
static constexpr const std::array s_display_deinterlacing_mode_names = {
"Disabled",
"Weave",
"Blend",
"Adaptive",
};
static constexpr const std::array s_display_deinterlacing_mode_display_names = {
TRANSLATE_NOOP("DisplayDeinterlacingMode", "Disabled (Flickering)"),
TRANSLATE_NOOP("DisplayDeinterlacingMode", "Weave (Combing)"),
TRANSLATE_NOOP("DisplayDeinterlacingMode", "Blend (Blur)"),
TRANSLATE_NOOP("DisplayDeinterlacingMode", "Adaptive (FastMAD)"),
};
std::optional<DisplayDeinterlacingMode> Settings::ParseDisplayDeinterlacingMode(const char* str)
{
int index = 0;
for (const char* name : s_display_deinterlacing_mode_names)
{
if (StringUtil::Strcasecmp(name, str) == 0)
return static_cast<DisplayDeinterlacingMode>(index);
index++;
}
return std::nullopt;
}
const char* Settings::GetDisplayDeinterlacingModeName(DisplayDeinterlacingMode mode)
{
return s_display_deinterlacing_mode_names[static_cast<int>(mode)];
}
const char* Settings::GetDisplayDeinterlacingModeDisplayName(DisplayDeinterlacingMode mode)
{
return Host::TranslateToCString("DisplayDeinterlacingMode",
s_display_deinterlacing_mode_display_names[static_cast<int>(mode)]);
}
static constexpr const std::array s_display_crop_mode_names = {"None", "Overscan", "Borders"};
static constexpr const std::array s_display_crop_mode_display_names = {
TRANSLATE_NOOP("DisplayCropMode", "None"), TRANSLATE_NOOP("DisplayCropMode", "Only Overscan Area"),

View File

@ -129,6 +129,7 @@ struct Settings
bool gpu_pgxp_cpu : 1 = false;
bool gpu_pgxp_preserve_proj_fp : 1 = false;
bool gpu_pgxp_depth_buffer : 1 = false;
DisplayDeinterlacingMode display_deinterlacing_mode = DEFAULT_DISPLAY_DEINTERLACING_MODE;
DisplayCropMode display_crop_mode = DEFAULT_DISPLAY_CROP_MODE;
DisplayAspectRatio display_aspect_ratio = DEFAULT_DISPLAY_ASPECT_RATIO;
DisplayAlignment display_alignment = DEFAULT_DISPLAY_ALIGNMENT;
@ -394,6 +395,10 @@ struct Settings
static const char* GetGPUWireframeModeName(GPUWireframeMode mode);
static const char* GetGPUWireframeModeDisplayName(GPUWireframeMode mode);
static std::optional<DisplayDeinterlacingMode> ParseDisplayDeinterlacingMode(const char* str);
static const char* GetDisplayDeinterlacingModeName(DisplayDeinterlacingMode mode);
static const char* GetDisplayDeinterlacingModeDisplayName(DisplayDeinterlacingMode mode);
static std::optional<DisplayCropMode> ParseDisplayCropMode(const char* str);
static const char* GetDisplayCropModeName(DisplayCropMode crop_mode);
static const char* GetDisplayCropModeDisplayName(DisplayCropMode crop_mode);
@ -483,6 +488,7 @@ struct Settings
static constexpr AudioBackend DEFAULT_AUDIO_BACKEND = AudioBackend::Null;
#endif
static constexpr DisplayDeinterlacingMode DEFAULT_DISPLAY_DEINTERLACING_MODE = DisplayDeinterlacingMode::Adaptive;
static constexpr DisplayCropMode DEFAULT_DISPLAY_CROP_MODE = DisplayCropMode::Overscan;
static constexpr DisplayAspectRatio DEFAULT_DISPLAY_ASPECT_RATIO = DisplayAspectRatio::Auto;
static constexpr DisplayAlignment DEFAULT_DISPLAY_ALIGNMENT = DisplayAlignment::Center;

View File

@ -3684,6 +3684,7 @@ void System::CheckForSettingsChanges(const Settings& old_settings)
g_settings.gpu_downsample_mode != old_settings.gpu_downsample_mode ||
g_settings.gpu_downsample_scale != old_settings.gpu_downsample_scale ||
g_settings.gpu_wireframe_mode != old_settings.gpu_wireframe_mode ||
g_settings.display_deinterlacing_mode != old_settings.display_deinterlacing_mode ||
g_settings.display_crop_mode != old_settings.display_crop_mode ||
g_settings.display_aspect_ratio != old_settings.display_aspect_ratio ||
g_settings.display_alignment != old_settings.display_alignment ||

View File

@ -77,6 +77,15 @@ enum class GPURenderer : u8
Count
};
enum class DisplayDeinterlacingMode : u8
{
Disabled,
Weave,
Blend,
Adaptive,
Count
};
enum class GPUTextureFilter : u8
{
Nearest,

View File

@ -66,6 +66,9 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsWindow* dialog, QWidget*
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.customAspectRatioDenominator, "Display",
"CustomAspectRatioDenominator", 1);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.widescreenHack, "GPU", "WidescreenHack", false);
SettingWidgetBinder::BindWidgetToEnumSetting(
sif, m_ui.displayDeinterlacing, "Display", "DeinterlacingMode", &Settings::ParseDisplayDeinterlacingMode,
&Settings::GetDisplayDeinterlacingModeName, Settings::DEFAULT_DISPLAY_DEINTERLACING_MODE);
SettingWidgetBinder::BindWidgetToEnumSetting(sif, m_ui.displayCropMode, "Display", "CropMode",
&Settings::ParseDisplayCropMode, &Settings::GetDisplayCropModeName,
Settings::DEFAULT_DISPLAY_CROP_MODE);
@ -251,7 +254,7 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsWindow* dialog, QWidget*
dialog->registerWidgetHelp(
m_ui.gpuDownsampleMode, tr("Down-Sampling"), 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."));
"but should be disabled for pure 3D games."));
dialog->registerWidgetHelp(m_ui.gpuDownsampleScale, tr("Down-Sampling Display Scale"), tr("1x"),
tr("Selects the resolution scale that will be applied to the final image. 1x will "
"downsample to the original console resolution."));
@ -259,15 +262,21 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsWindow* dialog, QWidget*
m_ui.textureFiltering, tr("Texture Filtering"),
QString::fromUtf8(Settings::GetTextureFilterDisplayName(Settings::DEFAULT_GPU_TEXTURE_FILTER)),
tr("Smooths out the blockiness of magnified textures on 3D object by using filtering. <br>Will have a "
"greater effect on higher resolution scales. Only applies to the hardware renderers. <br>The JINC2 and "
"especially xBR filtering modes are very demanding, and may not be worth the speed penalty."));
"greater effect on higher resolution scales. <br>The JINC2 and especially xBR filtering modes are very "
"demanding, and may not be worth the speed penalty."));
dialog->registerWidgetHelp(
m_ui.displayAspectRatio, tr("Aspect Ratio"),
QString::fromUtf8(Settings::GetDisplayAspectRatioDisplayName(Settings::DEFAULT_DISPLAY_ASPECT_RATIO)),
tr("Changes the aspect ratio used to display the console's output to the screen. The default is Auto (Game Native) "
"which automatically adjusts the aspect ratio to match how a game would be shown on a typical TV of the era."));
dialog->registerWidgetHelp(
m_ui.displayCropMode, tr("Crop Mode"),
m_ui.displayCropMode, tr("Deinterlacing"),
QString::fromUtf8(Settings::GetDisplayDeinterlacingModeName(Settings::DEFAULT_DISPLAY_DEINTERLACING_MODE)),
tr("Determines which algorithm is used to convert interlaced frames to progressive for display on your system. "
"Generally, the \"Disable Interlacing\" enhancement provides better quality output, but some games require "
"interlaced rendering."));
dialog->registerWidgetHelp(
m_ui.displayCropMode, tr("Crop"),
QString::fromUtf8(Settings::GetDisplayCropModeDisplayName(Settings::DEFAULT_DISPLAY_CROP_MODE)),
tr("Determines how much of the area typically not visible on a consumer TV set to crop/hide. Some games display "
"content in the overscan area, or use it for screen effects. May not display correctly with the \"All Borders\" "
@ -285,16 +294,15 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsWindow* dialog, QWidget*
"channel. This produces nicer looking gradients at the cost of making some colours look slightly different. "
"Disabling the option also enables dithering, which makes the transition between colours less sharp by applying "
"a pattern around those pixels. Most games are compatible with this option, but there is a number which aren't "
"and will have broken effects with it enabled. Only applies to the hardware renderers."));
"and will have broken effects with it enabled."));
dialog->registerWidgetHelp(
m_ui.widescreenHack, tr("Widescreen Rendering"), tr("Unchecked"),
tr("Scales vertex positions in screen-space to a widescreen aspect ratio, essentially "
"increasing the field of view from 4:3 to the chosen display aspect ratio in 3D games. <b><u>May not be "
"compatible with all games.</u></b>"));
dialog->registerWidgetHelp(
m_ui.pgxpEnable, tr("PGXP Geometry Correction"), tr("Unchecked"),
tr("Reduces \"wobbly\" polygons and \"warping\" textures that are common in PS1 games. <br>Only "
"works with the hardware renderers. <b><u>May not be compatible with all games.</u></b>"));
dialog->registerWidgetHelp(m_ui.pgxpEnable, tr("PGXP Geometry Correction"), tr("Unchecked"),
tr("Reduces \"wobbly\" polygons and \"warping\" textures that are common in PS1 games. "
"<strong>May not be compatible with all games.</strong>"));
dialog->registerWidgetHelp(
m_ui.pgxpDepthBuffer, tr("PGXP Depth Buffer"), tr("Unchecked"),
tr("Attempts to reduce polygon Z-fighting by testing pixels against the depth values from PGXP. Low compatibility, "
@ -303,8 +311,7 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsWindow* dialog, QWidget*
m_ui.force43For24Bit, tr("Force 4:3 For FMVs"), tr("Unchecked"),
tr("Switches back to 4:3 display aspect ratio when displaying 24-bit content, usually FMVs."));
dialog->registerWidgetHelp(m_ui.chromaSmoothingFor24Bit, tr("FMV Chroma Smoothing"), tr("Unchecked"),
tr("Smooths out blockyness between colour transitions in 24-bit content, usually FMVs. "
"Only applies to the hardware renderers."));
tr("Smooths out blockyness between colour transitions in 24-bit content, usually FMVs."));
dialog->registerWidgetHelp(
m_ui.disableInterlacing, tr("Disable Interlacing"), tr("Checked"),
tr(
@ -364,7 +371,7 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsWindow* dialog, QWidget*
dialog->registerWidgetHelp(
m_ui.scaledDithering, tr("Scaled Dithering"), tr("Checked"),
tr("Scales the dither pattern to the resolution scale of the emulated GPU. This makes the dither pattern much less "
"obvious at higher resolutions. <br>Usually safe to enable, and only supported by the hardware renderers."));
"obvious at higher resolutions. Usually safe to enable."));
dialog->registerWidgetHelp(
m_ui.useSoftwareRendererForReadbacks, tr("Software Renderer Readbacks"), tr("Unchecked"),
tr("Runs the software renderer in parallel for VRAM readbacks. On some systems, this may result in greater "
@ -525,6 +532,12 @@ void GraphicsSettingsWidget::setupAdditionalUi()
QString::fromUtf8(Settings::GetDisplayAspectRatioDisplayName(static_cast<DisplayAspectRatio>(i))));
}
for (u32 i = 0; i < static_cast<u32>(DisplayDeinterlacingMode::Count); i++)
{
m_ui.displayDeinterlacing->addItem(
QString::fromUtf8(Settings::GetDisplayDeinterlacingModeDisplayName(static_cast<DisplayDeinterlacingMode>(i))));
}
for (u32 i = 0; i < static_cast<u32>(DisplayCropMode::Count); i++)
{
m_ui.displayCropMode->addItem(
@ -641,7 +654,6 @@ void GraphicsSettingsWidget::updateRendererDependentOptions()
m_ui.gpuDownsampleScale->setEnabled(is_hardware);
m_ui.trueColor->setEnabled(is_hardware);
m_ui.pgxpEnable->setEnabled(is_hardware);
m_ui.chromaSmoothingFor24Bit->setEnabled(is_hardware);
m_ui.gpuLineDetectMode->setEnabled(is_hardware);
m_ui.gpuLineDetectModeLabel->setEnabled(is_hardware);

View File

@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>584</width>
<height>434</height>
<height>450</height>
</rect>
</property>
<property name="windowTitle">
@ -260,37 +260,37 @@
</item>
</layout>
</item>
<item row="4" column="0">
<item row="5" column="0">
<widget class="QLabel" name="label_7">
<property name="text">
<string>Crop:</string>
</property>
</widget>
</item>
<item row="4" column="1">
<item row="5" column="1">
<widget class="QComboBox" name="displayCropMode"/>
</item>
<item row="5" column="0">
<item row="6" column="0">
<widget class="QLabel" name="label_8">
<property name="text">
<string>Scaling:</string>
</property>
</widget>
</item>
<item row="5" column="1">
<item row="6" column="1">
<widget class="QComboBox" name="displayScaling"/>
</item>
<item row="6" column="0">
<item row="7" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>VSync:</string>
</property>
</widget>
</item>
<item row="6" column="1">
<item row="7" column="1">
<widget class="QComboBox" name="displaySyncMode"/>
</item>
<item row="7" column="0" colspan="2">
<item row="8" column="0" colspan="2">
<layout class="QGridLayout" name="gridLayout_2">
<item row="1" column="0">
<widget class="QCheckBox" name="pgxpEnable">
@ -350,6 +350,16 @@
</item>
</layout>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Deinterlacing:</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QComboBox" name="displayDeinterlacing"/>
</item>
</layout>
</widget>
</item>

View File

@ -959,6 +959,46 @@ void GPUDevice::SetDisplayMaxFPS(float max_fps)
m_display_frame_interval = (max_fps > 0.0f) ? (1.0f / max_fps) : 0.0f;
}
bool GPUDevice::ResizeTexture(std::unique_ptr<GPUTexture>* tex, u32 new_width, u32 new_height, GPUTexture::Type type,
GPUTexture::Format format, bool preserve /* = true */)
{
GPUTexture* old_tex = tex->get();
DebugAssert(!old_tex || (old_tex->GetLayers() == 1 && old_tex->GetLevels() == 1 && old_tex->GetSamples() == 1));
std::unique_ptr<GPUTexture> new_tex = FetchTexture(new_width, new_height, 1, 1, 1, type, format);
if (!new_tex) [[unlikely]]
{
Log_ErrorFmt("Failed to create new {}x{} texture", new_width, new_height);
return false;
}
if (old_tex)
{
if (old_tex->GetState() == GPUTexture::State::Cleared)
{
if (type == GPUTexture::Type::RenderTarget)
ClearRenderTarget(new_tex.get(), old_tex->GetClearColor());
}
else if (old_tex->GetState() == GPUTexture::State::Dirty)
{
const u32 copy_width = std::min(new_width, old_tex->GetWidth());
const u32 copy_height = std::min(new_height, old_tex->GetHeight());
if (type == GPUTexture::Type::RenderTarget)
ClearRenderTarget(new_tex.get(), 0);
CopyTextureRegion(new_tex.get(), 0, 0, 0, 0, old_tex, 0, 0, 0, 0, copy_width, copy_height);
}
}
else if (preserve)
{
// If we're expecting data to be there, make sure to clear it.
if (type == GPUTexture::Type::RenderTarget)
ClearRenderTarget(new_tex.get(), 0);
}
RecycleTexture(std::move(*tex));
*tex = std::move(new_tex);
return true;
}
bool GPUDevice::ShouldSkipDisplayingFrame()
{
if (m_display_frame_interval == 0.0f)

View File

@ -650,6 +650,8 @@ public:
bool UsesLowerLeftOrigin() const;
static Common::Rectangle<s32> FlipToLowerLeft(const Common::Rectangle<s32>& rc, s32 target_height);
void SetDisplayMaxFPS(float max_fps);
bool ResizeTexture(std::unique_ptr<GPUTexture>* tex, u32 new_width, u32 new_height, GPUTexture::Type type,
GPUTexture::Format format, bool preserve = true);
bool ShouldSkipDisplayingFrame();
void ThrottlePresentation();