GPUDevice: Support pre-rotating swap chains
This commit is contained in:
parent
acf04ed67a
commit
f0c456893c
|
@ -1997,15 +1997,11 @@ GPUDevice::PresentResult GPU::RenderDisplay(GPUTexture* target, const GSVector4i
|
||||||
|
|
||||||
// Now we can apply the post chain.
|
// Now we can apply the post chain.
|
||||||
GPUTexture* post_output_texture = PostProcessing::InternalChain.GetOutputTexture();
|
GPUTexture* post_output_texture = PostProcessing::InternalChain.GetOutputTexture();
|
||||||
if (const GPUDevice::PresentResult pres = PostProcessing::InternalChain.Apply(
|
if (PostProcessing::InternalChain.Apply(display_texture, m_display_depth_buffer, post_output_texture,
|
||||||
display_texture, m_display_depth_buffer, post_output_texture,
|
GSVector4i(0, 0, display_texture_view_width, display_texture_view_height),
|
||||||
GSVector4i(0, 0, display_texture_view_width, display_texture_view_height), display_texture_view_width,
|
display_texture_view_width, display_texture_view_height,
|
||||||
display_texture_view_height, m_crtc_state.display_width, m_crtc_state.display_height);
|
m_crtc_state.display_width,
|
||||||
pres != GPUDevice::PresentResult::OK)
|
m_crtc_state.display_height) == GPUDevice::PresentResult::OK)
|
||||||
{
|
|
||||||
return pres;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
display_texture_view_x = 0;
|
display_texture_view_x = 0;
|
||||||
display_texture_view_y = 0;
|
display_texture_view_y = 0;
|
||||||
|
@ -2020,8 +2016,13 @@ GPUDevice::PresentResult GPU::RenderDisplay(GPUTexture* target, const GSVector4i
|
||||||
const bool really_postfx = (postfx && PostProcessing::DisplayChain.IsActive() && g_gpu_device->HasMainSwapChain() &&
|
const bool really_postfx = (postfx && PostProcessing::DisplayChain.IsActive() && g_gpu_device->HasMainSwapChain() &&
|
||||||
hdformat != GPUTexture::Format::Unknown && target_width > 0 && target_height > 0 &&
|
hdformat != GPUTexture::Format::Unknown && target_width > 0 && target_height > 0 &&
|
||||||
PostProcessing::DisplayChain.CheckTargets(hdformat, target_width, target_height));
|
PostProcessing::DisplayChain.CheckTargets(hdformat, target_width, target_height));
|
||||||
const GSVector4i real_draw_rect =
|
GSVector4i real_draw_rect = target ? draw_rect : g_gpu_device->GetMainSwapChain()->PreRotateClipRect(draw_rect);
|
||||||
g_gpu_device->UsesLowerLeftOrigin() ? GPUDevice::FlipToLowerLeft(draw_rect, target_height) : draw_rect;
|
if (g_gpu_device->UsesLowerLeftOrigin())
|
||||||
|
{
|
||||||
|
real_draw_rect = GPUDevice::FlipToLowerLeft(
|
||||||
|
real_draw_rect,
|
||||||
|
(target || really_postfx) ? target_height : g_gpu_device->GetMainSwapChain()->GetPostRotatedHeight());
|
||||||
|
}
|
||||||
if (really_postfx)
|
if (really_postfx)
|
||||||
{
|
{
|
||||||
g_gpu_device->ClearRenderTarget(PostProcessing::DisplayChain.GetInputTexture(), GPUDevice::DEFAULT_CLEAR_COLOR);
|
g_gpu_device->ClearRenderTarget(PostProcessing::DisplayChain.GetInputTexture(), GPUDevice::DEFAULT_CLEAR_COLOR);
|
||||||
|
@ -2106,16 +2107,22 @@ GPUDevice::PresentResult GPU::RenderDisplay(GPUTexture* target, const GSVector4i
|
||||||
uniforms.src_size[2] = rcp_width;
|
uniforms.src_size[2] = rcp_width;
|
||||||
uniforms.src_size[3] = rcp_height;
|
uniforms.src_size[3] = rcp_height;
|
||||||
|
|
||||||
if (g_settings.display_rotation != DisplayRotation::Normal)
|
const WindowInfo::PreRotation surface_prerotation = (target || really_postfx) ?
|
||||||
|
WindowInfo::PreRotation::Identity :
|
||||||
|
g_gpu_device->GetMainSwapChain()->GetPreRotation();
|
||||||
|
if (g_settings.display_rotation != DisplayRotation::Normal ||
|
||||||
|
surface_prerotation != WindowInfo::PreRotation::Identity)
|
||||||
{
|
{
|
||||||
static constexpr const std::array<float, static_cast<size_t>(DisplayRotation::Count) - 1> rotation_radians = {{
|
static constexpr const std::array<float, static_cast<size_t>(DisplayRotation::Count)> rotation_radians = {{
|
||||||
|
0.0f, // Disabled
|
||||||
static_cast<float>(std::numbers::pi * 1.5f), // Rotate90
|
static_cast<float>(std::numbers::pi * 1.5f), // Rotate90
|
||||||
static_cast<float>(std::numbers::pi), // Rotate180
|
static_cast<float>(std::numbers::pi), // Rotate180
|
||||||
static_cast<float>(std::numbers::pi / 2.0), // Rotate270
|
static_cast<float>(std::numbers::pi / 2.0), // Rotate270
|
||||||
}};
|
}};
|
||||||
|
|
||||||
GSMatrix2x2::Rotation(rotation_radians[static_cast<size_t>(g_settings.display_rotation) - 1])
|
const u32 rotation_idx = (static_cast<u32>(g_settings.display_rotation) + static_cast<u32>(surface_prerotation)) %
|
||||||
.store(uniforms.rotation_matrix);
|
static_cast<u32>(rotation_radians.size());
|
||||||
|
GSMatrix2x2::Rotation(rotation_radians[rotation_idx]).store(uniforms.rotation_matrix);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
|
@ -233,6 +233,47 @@ GPUSwapChain::GPUSwapChain(const WindowInfo& wi, GPUVSyncMode vsync_mode, bool a
|
||||||
|
|
||||||
GPUSwapChain::~GPUSwapChain() = default;
|
GPUSwapChain::~GPUSwapChain() = default;
|
||||||
|
|
||||||
|
GSVector4i GPUSwapChain::PreRotateClipRect(const GSVector4i& v)
|
||||||
|
{
|
||||||
|
GSVector4i new_clip;
|
||||||
|
switch (m_window_info.surface_prerotation)
|
||||||
|
{
|
||||||
|
case WindowInfo::PreRotation::Identity:
|
||||||
|
new_clip = v;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case WindowInfo::PreRotation::Rotate90Clockwise:
|
||||||
|
{
|
||||||
|
const s32 height = (v.w - v.y);
|
||||||
|
const s32 y = m_window_info.surface_height - v.y - height;
|
||||||
|
new_clip = GSVector4i(y, v.x, y + height, v.z);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case WindowInfo::PreRotation::Rotate180Clockwise:
|
||||||
|
{
|
||||||
|
const s32 width = (v.z - v.x);
|
||||||
|
const s32 height = (v.w - v.y);
|
||||||
|
const s32 x = m_window_info.surface_width - v.x - width;
|
||||||
|
const s32 y = m_window_info.surface_height - v.y - height;
|
||||||
|
new_clip = GSVector4i(x, y, x + width, y + height);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case WindowInfo::PreRotation::Rotate270Clockwise:
|
||||||
|
{
|
||||||
|
const s32 width = (v.z - v.x);
|
||||||
|
const s32 x = m_window_info.surface_width - v.x - width;
|
||||||
|
new_clip = GSVector4i(v.y, x, v.w, x + width);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
DefaultCaseIsUnreachable()
|
||||||
|
}
|
||||||
|
|
||||||
|
return new_clip;
|
||||||
|
}
|
||||||
|
|
||||||
bool GPUSwapChain::ShouldSkipPresentingFrame()
|
bool GPUSwapChain::ShouldSkipPresentingFrame()
|
||||||
{
|
{
|
||||||
// Only needed with FIFO. But since we're so fast, we allow it always.
|
// Only needed with FIFO. But since we're so fast, we allow it always.
|
||||||
|
@ -674,11 +715,17 @@ void GPUDevice::RenderImGui(GPUSwapChain* swap_chain)
|
||||||
if (draw_data->CmdListsCount == 0 || !swap_chain)
|
if (draw_data->CmdListsCount == 0 || !swap_chain)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
const s32 post_rotated_height = swap_chain->GetPostRotatedHeight();
|
||||||
SetPipeline(m_imgui_pipeline.get());
|
SetPipeline(m_imgui_pipeline.get());
|
||||||
SetViewportAndScissor(0, 0, swap_chain->GetWidth(), swap_chain->GetHeight());
|
SetViewport(0, 0, swap_chain->GetPostRotatedWidth(), post_rotated_height);
|
||||||
|
|
||||||
const GSMatrix4x4 mproj = GSMatrix4x4::OffCenterOrthographicProjection(
|
GSMatrix4x4 mproj = GSMatrix4x4::OffCenterOrthographicProjection(
|
||||||
0.0f, 0.0f, static_cast<float>(swap_chain->GetWidth()), static_cast<float>(swap_chain->GetHeight()), 0.0f, 1.0f);
|
0.0f, 0.0f, static_cast<float>(swap_chain->GetWidth()), static_cast<float>(swap_chain->GetHeight()), 0.0f, 1.0f);
|
||||||
|
if (swap_chain->GetPreRotation() != WindowInfo::PreRotation::Identity)
|
||||||
|
{
|
||||||
|
mproj =
|
||||||
|
GSMatrix4x4::RotationZ(WindowInfo::GetZRotationForPreRotation(swap_chain->GetPreRotation())) * mproj;
|
||||||
|
}
|
||||||
PushUniformBuffer(&mproj, sizeof(mproj));
|
PushUniformBuffer(&mproj, sizeof(mproj));
|
||||||
|
|
||||||
// Render command lists
|
// Render command lists
|
||||||
|
@ -701,8 +748,9 @@ void GPUDevice::RenderImGui(GPUSwapChain* swap_chain)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
GSVector4i clip = GSVector4i(GSVector4::load<false>(&pcmd->ClipRect.x));
|
GSVector4i clip = GSVector4i(GSVector4::load<false>(&pcmd->ClipRect.x));
|
||||||
|
clip = swap_chain->PreRotateClipRect(clip);
|
||||||
if (flip)
|
if (flip)
|
||||||
clip = FlipToLowerLeft(clip, swap_chain->GetHeight());
|
clip = FlipToLowerLeft(clip, post_rotated_height);
|
||||||
|
|
||||||
SetScissor(clip);
|
SetScissor(clip);
|
||||||
SetTextureSampler(0, reinterpret_cast<GPUTexture*>(pcmd->TextureId), m_linear_sampler.get());
|
SetTextureSampler(0, reinterpret_cast<GPUTexture*>(pcmd->TextureId), m_linear_sampler.get());
|
||||||
|
|
|
@ -507,7 +507,10 @@ public:
|
||||||
ALWAYS_INLINE const WindowInfo& GetWindowInfo() const { return m_window_info; }
|
ALWAYS_INLINE const WindowInfo& GetWindowInfo() const { return m_window_info; }
|
||||||
ALWAYS_INLINE u32 GetWidth() const { return m_window_info.surface_width; }
|
ALWAYS_INLINE u32 GetWidth() const { return m_window_info.surface_width; }
|
||||||
ALWAYS_INLINE u32 GetHeight() const { return m_window_info.surface_height; }
|
ALWAYS_INLINE u32 GetHeight() const { return m_window_info.surface_height; }
|
||||||
|
ALWAYS_INLINE u32 GetPostRotatedWidth() const { return m_window_info.GetPostRotatedWidth(); }
|
||||||
|
ALWAYS_INLINE u32 GetPostRotatedHeight() const { return m_window_info.GetPostRotatedHeight(); }
|
||||||
ALWAYS_INLINE float GetScale() const { return m_window_info.surface_scale; }
|
ALWAYS_INLINE float GetScale() const { return m_window_info.surface_scale; }
|
||||||
|
ALWAYS_INLINE WindowInfo::PreRotation GetPreRotation() const { return m_window_info.surface_prerotation; }
|
||||||
ALWAYS_INLINE GPUTexture::Format GetFormat() const { return m_window_info.surface_format; }
|
ALWAYS_INLINE GPUTexture::Format GetFormat() const { return m_window_info.surface_format; }
|
||||||
|
|
||||||
ALWAYS_INLINE GPUVSyncMode GetVSyncMode() const { return m_vsync_mode; }
|
ALWAYS_INLINE GPUVSyncMode GetVSyncMode() const { return m_vsync_mode; }
|
||||||
|
@ -517,6 +520,8 @@ public:
|
||||||
virtual bool ResizeBuffers(u32 new_width, u32 new_height, float new_scale, Error* error) = 0;
|
virtual bool ResizeBuffers(u32 new_width, u32 new_height, float new_scale, Error* error) = 0;
|
||||||
virtual bool SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle, Error* error) = 0;
|
virtual bool SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle, Error* error) = 0;
|
||||||
|
|
||||||
|
GSVector4i PreRotateClipRect(const GSVector4i& v);
|
||||||
|
|
||||||
bool ShouldSkipPresentingFrame();
|
bool ShouldSkipPresentingFrame();
|
||||||
void ThrottlePresentation();
|
void ThrottlePresentation();
|
||||||
|
|
||||||
|
|
|
@ -443,6 +443,9 @@ void OpenGLContextEGL::UpdateWindowInfoSize(WindowInfo& wi, EGLSurface surface)
|
||||||
{
|
{
|
||||||
wi.surface_width = static_cast<u16>(surface_width);
|
wi.surface_width = static_cast<u16>(surface_width);
|
||||||
wi.surface_height = static_cast<u16>(surface_height);
|
wi.surface_height = static_cast<u16>(surface_height);
|
||||||
|
|
||||||
|
if (WindowInfo::ShouldSwapDimensionsForPreRotation(wi.surface_prerotation))
|
||||||
|
std::swap(wi.surface_width, wi.surface_height);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
#include "postprocessing_shader.h"
|
#include "postprocessing_shader.h"
|
||||||
#include "postprocessing_shader_fx.h"
|
#include "postprocessing_shader_fx.h"
|
||||||
#include "postprocessing_shader_glsl.h"
|
#include "postprocessing_shader_glsl.h"
|
||||||
|
#include "shadergen.h"
|
||||||
|
|
||||||
// TODO: Remove me
|
// TODO: Remove me
|
||||||
#include "core/host.h"
|
#include "core/host.h"
|
||||||
|
@ -28,9 +29,6 @@
|
||||||
|
|
||||||
LOG_CHANNEL(PostProcessing);
|
LOG_CHANNEL(PostProcessing);
|
||||||
|
|
||||||
// TODO: ProgressCallbacks for shader compiling, it can be a bit slow.
|
|
||||||
// TODO: buffer width/height is wrong on resize, need to change it somehow.
|
|
||||||
|
|
||||||
namespace PostProcessing {
|
namespace PostProcessing {
|
||||||
template<typename T>
|
template<typename T>
|
||||||
static u32 ParseVector(std::string_view line, ShaderOption::ValueVector* values);
|
static u32 ParseVector(std::string_view line, ShaderOption::ValueVector* values);
|
||||||
|
@ -369,6 +367,11 @@ PostProcessing::Chain::Chain(const char* section) : m_section(section)
|
||||||
|
|
||||||
PostProcessing::Chain::~Chain() = default;
|
PostProcessing::Chain::~Chain() = default;
|
||||||
|
|
||||||
|
GPUTexture* PostProcessing::Chain::GetTextureUnusedAtEndOfChain() const
|
||||||
|
{
|
||||||
|
return (m_stages.size() % 2) ? m_output_texture.get() : m_input_texture.get();
|
||||||
|
}
|
||||||
|
|
||||||
bool PostProcessing::Chain::IsActive() const
|
bool PostProcessing::Chain::IsActive() const
|
||||||
{
|
{
|
||||||
return m_enabled && !m_stages.empty();
|
return m_enabled && !m_stages.empty();
|
||||||
|
@ -561,16 +564,58 @@ bool PostProcessing::Chain::CheckTargets(GPUTexture::Format target_format, u32 t
|
||||||
if (m_target_format == target_format && m_target_width == target_width && m_target_height == target_height)
|
if (m_target_format == target_format && m_target_width == target_width && m_target_height == target_height)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
Error error;
|
||||||
|
|
||||||
|
if (!IsInternalChain() && (!m_rotated_copy_pipeline || m_target_format != target_format))
|
||||||
|
{
|
||||||
|
const RenderAPI rapi = g_gpu_device->GetRenderAPI();
|
||||||
|
const ShaderGen shadergen(rapi, ShaderGen::GetShaderLanguageForAPI(rapi), false, false);
|
||||||
|
const std::unique_ptr<GPUShader> vso = g_gpu_device->CreateShader(GPUShaderStage::Vertex, shadergen.GetLanguage(),
|
||||||
|
shadergen.GenerateRotateVertexShader(), &error);
|
||||||
|
const std::unique_ptr<GPUShader> fso = g_gpu_device->CreateShader(GPUShaderStage::Fragment, shadergen.GetLanguage(),
|
||||||
|
shadergen.GenerateRotateFragmentShader(), &error);
|
||||||
|
if (!vso || !fso)
|
||||||
|
{
|
||||||
|
ERROR_LOG("Failed to compile post-processing rotate shaders: {}", error.GetDescription());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
GL_OBJECT_NAME(vso, "Post-processing rotate blit VS");
|
||||||
|
GL_OBJECT_NAME(vso, "Post-processing rotate blit FS");
|
||||||
|
|
||||||
|
const GPUPipeline::GraphicsConfig config = {.layout = GPUPipeline::Layout::SingleTextureAndPushConstants,
|
||||||
|
.primitive = GPUPipeline::Primitive::Triangles,
|
||||||
|
.input_layout = {},
|
||||||
|
.rasterization = GPUPipeline::RasterizationState::GetNoCullState(),
|
||||||
|
.depth = GPUPipeline::DepthState::GetNoTestsState(),
|
||||||
|
.blend = GPUPipeline::BlendState::GetNoBlendingState(),
|
||||||
|
.vertex_shader = vso.get(),
|
||||||
|
.geometry_shader = nullptr,
|
||||||
|
.fragment_shader = fso.get(),
|
||||||
|
.color_formats = {target_format},
|
||||||
|
.depth_format = GPUTexture::Format::Unknown,
|
||||||
|
.samples = 1,
|
||||||
|
.per_sample_shading = false,
|
||||||
|
.render_pass_flags = GPUPipeline::NoRenderPassFlags};
|
||||||
|
m_rotated_copy_pipeline = g_gpu_device->CreatePipeline(config, &error);
|
||||||
|
if (!m_rotated_copy_pipeline)
|
||||||
|
{
|
||||||
|
ERROR_LOG("Failed to compile post-processing rotate pipeline: {}", error.GetDescription());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
GL_OBJECT_NAME(m_rotated_copy_pipeline, "Post-processing rotate pipeline");
|
||||||
|
}
|
||||||
|
|
||||||
// In case any allocs fail.
|
// In case any allocs fail.
|
||||||
DestroyTextures();
|
DestroyTextures();
|
||||||
|
|
||||||
if (!(m_input_texture =
|
if (!(m_input_texture =
|
||||||
g_gpu_device->FetchTexture(target_width, target_height, 1, 1, 1, GPUTexture::Type::RenderTarget,
|
g_gpu_device->FetchTexture(target_width, target_height, 1, 1, 1, GPUTexture::Type::RenderTarget,
|
||||||
target_format, GPUTexture::Flags::None)) ||
|
target_format, GPUTexture::Flags::None, nullptr, 0, &error)) ||
|
||||||
!(m_output_texture =
|
!(m_output_texture =
|
||||||
g_gpu_device->FetchTexture(target_width, target_height, 1, 1, 1, GPUTexture::Type::RenderTarget,
|
g_gpu_device->FetchTexture(target_width, target_height, 1, 1, 1, GPUTexture::Type::RenderTarget,
|
||||||
target_format, GPUTexture::Flags::None)))
|
target_format, GPUTexture::Flags::None, nullptr, 0, &error)))
|
||||||
{
|
{
|
||||||
|
ERROR_LOG("Failed to create input/output textures: {}", error.GetDescription());
|
||||||
DestroyTextures();
|
DestroyTextures();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -583,7 +628,6 @@ bool PostProcessing::Chain::CheckTargets(GPUTexture::Format target_format, u32 t
|
||||||
|
|
||||||
m_wants_depth_buffer = false;
|
m_wants_depth_buffer = false;
|
||||||
|
|
||||||
Error error;
|
|
||||||
for (size_t i = 0; i < m_stages.size(); i++)
|
for (size_t i = 0; i < m_stages.size(); i++)
|
||||||
{
|
{
|
||||||
Shader* const shader = m_stages[i].get();
|
Shader* const shader = m_stages[i].get();
|
||||||
|
@ -622,6 +666,11 @@ void PostProcessing::Chain::DestroyTextures()
|
||||||
g_gpu_device->RecycleTexture(std::move(m_input_texture));
|
g_gpu_device->RecycleTexture(std::move(m_input_texture));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PostProcessing::Chain::DestroyPipelines()
|
||||||
|
{
|
||||||
|
m_rotated_copy_pipeline.reset();
|
||||||
|
}
|
||||||
|
|
||||||
GPUDevice::PresentResult PostProcessing::Chain::Apply(GPUTexture* input_color, GPUTexture* input_depth,
|
GPUDevice::PresentResult PostProcessing::Chain::Apply(GPUTexture* input_color, GPUTexture* input_depth,
|
||||||
GPUTexture* final_target, GSVector4i final_rect, s32 orig_width,
|
GPUTexture* final_target, GSVector4i final_rect, s32 orig_width,
|
||||||
s32 orig_height, s32 native_width, s32 native_height)
|
s32 orig_height, s32 native_width, s32 native_height)
|
||||||
|
@ -633,13 +682,24 @@ GPUDevice::PresentResult PostProcessing::Chain::Apply(GPUTexture* input_color, G
|
||||||
if (input_depth)
|
if (input_depth)
|
||||||
input_depth->MakeReadyForSampling();
|
input_depth->MakeReadyForSampling();
|
||||||
|
|
||||||
|
GPUTexture* draw_final_target = final_target;
|
||||||
|
const WindowInfo::PreRotation prerotation =
|
||||||
|
final_target ? WindowInfo::PreRotation::Identity : g_gpu_device->GetMainSwapChain()->GetPreRotation();
|
||||||
|
if (prerotation != WindowInfo::PreRotation::Identity)
|
||||||
|
{
|
||||||
|
// We have prerotation and post processing. This is messy, since we need to run the shader on the "real" size,
|
||||||
|
// then copy it across to the rotated image. We can use the input or output texture from the chain, whichever
|
||||||
|
// was not the last that was drawn to.
|
||||||
|
draw_final_target = GetTextureUnusedAtEndOfChain();
|
||||||
|
}
|
||||||
|
|
||||||
for (const std::unique_ptr<Shader>& stage : m_stages)
|
for (const std::unique_ptr<Shader>& stage : m_stages)
|
||||||
{
|
{
|
||||||
const bool is_final = (stage.get() == m_stages.back().get());
|
const bool is_final = (stage.get() == m_stages.back().get());
|
||||||
|
|
||||||
if (const GPUDevice::PresentResult pres =
|
if (const GPUDevice::PresentResult pres =
|
||||||
stage->Apply(input_color, input_depth, is_final ? final_target : output, final_rect, orig_width, orig_height,
|
stage->Apply(input_color, input_depth, is_final ? draw_final_target : output, final_rect, orig_width,
|
||||||
native_width, native_height, m_target_width, m_target_height);
|
orig_height, native_width, native_height, m_target_width, m_target_height);
|
||||||
pres != GPUDevice::PresentResult::OK)
|
pres != GPUDevice::PresentResult::OK)
|
||||||
{
|
{
|
||||||
return pres;
|
return pres;
|
||||||
|
@ -653,6 +713,30 @@ GPUDevice::PresentResult PostProcessing::Chain::Apply(GPUTexture* input_color, G
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (prerotation != WindowInfo::PreRotation::Identity)
|
||||||
|
{
|
||||||
|
draw_final_target->MakeReadyForSampling();
|
||||||
|
|
||||||
|
// Rotate and blit to final swap chain.
|
||||||
|
GPUSwapChain* const swap_chain = g_gpu_device->GetMainSwapChain();
|
||||||
|
if (const GPUDevice::PresentResult pres = g_gpu_device->BeginPresent(swap_chain);
|
||||||
|
pres != GPUDevice::PresentResult::OK)
|
||||||
|
{
|
||||||
|
return pres;
|
||||||
|
}
|
||||||
|
|
||||||
|
GL_PUSH_FMT("Apply swap chain pre-rotation");
|
||||||
|
|
||||||
|
const GSMatrix2x2 rotmat = GSMatrix2x2::Rotation(WindowInfo::GetZRotationForPreRotation(prerotation));
|
||||||
|
g_gpu_device->SetPipeline(m_rotated_copy_pipeline.get());
|
||||||
|
g_gpu_device->PushUniformBuffer(&rotmat, sizeof(rotmat));
|
||||||
|
g_gpu_device->SetTextureSampler(0, draw_final_target, g_gpu_device->GetNearestSampler());
|
||||||
|
g_gpu_device->SetViewportAndScissor(0, 0, swap_chain->GetPostRotatedWidth(), swap_chain->GetPostRotatedHeight());
|
||||||
|
g_gpu_device->Draw(3, 0);
|
||||||
|
|
||||||
|
GL_POP();
|
||||||
|
}
|
||||||
|
|
||||||
return GPUDevice::PresentResult::OK;
|
return GPUDevice::PresentResult::OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -675,6 +759,7 @@ void PostProcessing::Shutdown()
|
||||||
s_samplers.clear();
|
s_samplers.clear();
|
||||||
ForAllChains([](Chain& chain) {
|
ForAllChains([](Chain& chain) {
|
||||||
chain.ClearStages();
|
chain.ClearStages();
|
||||||
|
chain.DestroyPipelines();
|
||||||
chain.DestroyTextures();
|
chain.DestroyTextures();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -691,6 +776,7 @@ bool PostProcessing::ReloadShaders()
|
||||||
|
|
||||||
ForAllChains([](Chain& chain) {
|
ForAllChains([](Chain& chain) {
|
||||||
chain.ClearStages();
|
chain.ClearStages();
|
||||||
|
chain.DestroyPipelines();
|
||||||
chain.DestroyTextures();
|
chain.DestroyTextures();
|
||||||
chain.LoadStages();
|
chain.LoadStages();
|
||||||
});
|
});
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
|
|
||||||
class Timer;
|
class Timer;
|
||||||
|
|
||||||
|
class GPUPipeline;
|
||||||
class GPUSampler;
|
class GPUSampler;
|
||||||
class GPUTexture;
|
class GPUTexture;
|
||||||
|
|
||||||
|
@ -117,6 +118,9 @@ public:
|
||||||
ALWAYS_INLINE GPUTexture* GetInputTexture() const { return m_input_texture.get(); }
|
ALWAYS_INLINE GPUTexture* GetInputTexture() const { return m_input_texture.get(); }
|
||||||
ALWAYS_INLINE GPUTexture* GetOutputTexture() const { return m_output_texture.get(); }
|
ALWAYS_INLINE GPUTexture* GetOutputTexture() const { return m_output_texture.get(); }
|
||||||
|
|
||||||
|
/// Returns either the input or output texture, whichever isn't the destination after the final pass.
|
||||||
|
GPUTexture* GetTextureUnusedAtEndOfChain() const;
|
||||||
|
|
||||||
bool IsActive() const;
|
bool IsActive() const;
|
||||||
bool IsInternalChain() const;
|
bool IsInternalChain() const;
|
||||||
|
|
||||||
|
@ -125,6 +129,7 @@ public:
|
||||||
void LoadStages();
|
void LoadStages();
|
||||||
void ClearStages();
|
void ClearStages();
|
||||||
void DestroyTextures();
|
void DestroyTextures();
|
||||||
|
void DestroyPipelines();
|
||||||
|
|
||||||
/// Temporarily toggles post-processing on/off.
|
/// Temporarily toggles post-processing on/off.
|
||||||
void Toggle();
|
void Toggle();
|
||||||
|
@ -151,6 +156,7 @@ private:
|
||||||
std::vector<std::unique_ptr<PostProcessing::Shader>> m_stages;
|
std::vector<std::unique_ptr<PostProcessing::Shader>> m_stages;
|
||||||
std::unique_ptr<GPUTexture> m_input_texture;
|
std::unique_ptr<GPUTexture> m_input_texture;
|
||||||
std::unique_ptr<GPUTexture> m_output_texture;
|
std::unique_ptr<GPUTexture> m_output_texture;
|
||||||
|
std::unique_ptr<GPUPipeline> m_rotated_copy_pipeline;
|
||||||
};
|
};
|
||||||
|
|
||||||
// [display_name, filename]
|
// [display_name, filename]
|
||||||
|
|
|
@ -791,6 +791,40 @@ void ShaderGen::DeclareFragmentEntryPoint(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string ShaderGen::GenerateRotateVertexShader() const
|
||||||
|
{
|
||||||
|
std::stringstream ss;
|
||||||
|
WriteHeader(ss);
|
||||||
|
DeclareUniformBuffer(ss, { "float2 u_rotation_matrix0", "float2 u_rotation_matrix1" }, true);
|
||||||
|
DeclareVertexEntryPoint(ss, {}, 0, 1, {}, true);
|
||||||
|
ss << "{\n";
|
||||||
|
ss << " v_tex0 = float2(float((v_id << 1) & 2u), float(v_id & 2u));\n";
|
||||||
|
ss << " v_pos = float4(v_tex0 * float2(2.0f, -2.0f) + float2(-1.0f, 1.0f), 0.0f, 1.0f);\n";
|
||||||
|
ss << " v_pos.xy = float2(dot(u_rotation_matrix0, v_pos.xy), dot(u_rotation_matrix1, v_pos.xy));\n";
|
||||||
|
ss << " #if API_OPENGL || API_OPENGL_ES || API_VULKAN\n";
|
||||||
|
ss << " v_pos.y = -v_pos.y;\n";
|
||||||
|
ss << " #endif\n";
|
||||||
|
ss << "}\n";
|
||||||
|
|
||||||
|
return ss.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string ShaderGen::GenerateRotateFragmentShader() const
|
||||||
|
{
|
||||||
|
std::stringstream ss;
|
||||||
|
WriteHeader(ss);
|
||||||
|
DeclareTexture(ss, "samp0", 0);
|
||||||
|
DeclareFragmentEntryPoint(ss, 0, 1);
|
||||||
|
|
||||||
|
ss << R"(
|
||||||
|
{
|
||||||
|
o_col0 = SAMPLE_TEXTURE(samp0, v_tex0);
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
|
||||||
|
return ss.str();
|
||||||
|
}
|
||||||
|
|
||||||
std::string ShaderGen::GenerateScreenQuadVertexShader(float z /* = 0.0f */) const
|
std::string ShaderGen::GenerateScreenQuadVertexShader(float z /* = 0.0f */) const
|
||||||
{
|
{
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
|
|
|
@ -25,6 +25,9 @@ public:
|
||||||
|
|
||||||
ALWAYS_INLINE GPUShaderLanguage GetLanguage() const { return m_shader_language; }
|
ALWAYS_INLINE GPUShaderLanguage GetLanguage() const { return m_shader_language; }
|
||||||
|
|
||||||
|
std::string GenerateRotateVertexShader() const;
|
||||||
|
std::string GenerateRotateFragmentShader() const;
|
||||||
|
|
||||||
std::string GenerateScreenQuadVertexShader(float z = 0.0f) const;
|
std::string GenerateScreenQuadVertexShader(float z = 0.0f) const;
|
||||||
std::string GenerateUVQuadVertexShader() const;
|
std::string GenerateUVQuadVertexShader() const;
|
||||||
std::string GenerateFillFragmentShader() const;
|
std::string GenerateFillFragmentShader() const;
|
||||||
|
|
|
@ -3406,7 +3406,7 @@ void VulkanDevice::BeginSwapChainRenderPass(VulkanSwapChain* swap_chain, u32 cle
|
||||||
const VkRenderingInfoKHR ri = {VK_STRUCTURE_TYPE_RENDERING_INFO_KHR,
|
const VkRenderingInfoKHR ri = {VK_STRUCTURE_TYPE_RENDERING_INFO_KHR,
|
||||||
nullptr,
|
nullptr,
|
||||||
0u,
|
0u,
|
||||||
{{}, {swap_chain->GetWidth(), swap_chain->GetHeight()}},
|
{{}, {swap_chain->GetPostRotatedWidth(), swap_chain->GetPostRotatedHeight()}},
|
||||||
1u,
|
1u,
|
||||||
0u,
|
0u,
|
||||||
1u,
|
1u,
|
||||||
|
@ -3427,7 +3427,7 @@ void VulkanDevice::BeginSwapChainRenderPass(VulkanSwapChain* swap_chain, u32 cle
|
||||||
nullptr,
|
nullptr,
|
||||||
m_current_render_pass,
|
m_current_render_pass,
|
||||||
swap_chain->GetCurrentFramebuffer(),
|
swap_chain->GetCurrentFramebuffer(),
|
||||||
{{0, 0}, {swap_chain->GetWidth(), swap_chain->GetHeight()}},
|
{{0, 0}, {swap_chain->GetPostRotatedWidth(), swap_chain->GetPostRotatedHeight()}},
|
||||||
1u,
|
1u,
|
||||||
&clear_value};
|
&clear_value};
|
||||||
vkCmdBeginRenderPass(GetCurrentCommandBuffer(), &rp, VK_SUBPASS_CONTENTS_INLINE);
|
vkCmdBeginRenderPass(GetCurrentCommandBuffer(), &rp, VK_SUBPASS_CONTENTS_INLINE);
|
||||||
|
|
|
@ -387,9 +387,50 @@ bool VulkanSwapChain::CreateSwapChain(VulkanDevice& dev, Error* error)
|
||||||
surface_caps.surfaceCapabilities.maxImageExtent.height);
|
surface_caps.surfaceCapabilities.maxImageExtent.height);
|
||||||
|
|
||||||
// Prefer identity transform if possible
|
// Prefer identity transform if possible
|
||||||
|
VkExtent2D window_size = size;
|
||||||
|
WindowInfo::PreRotation window_prerotation = WindowInfo::PreRotation::Identity;
|
||||||
VkSurfaceTransformFlagBitsKHR transform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
|
VkSurfaceTransformFlagBitsKHR transform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
|
||||||
|
switch (surface_caps.surfaceCapabilities.currentTransform)
|
||||||
|
{
|
||||||
|
case VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR:
|
||||||
|
transform = VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR;
|
||||||
|
window_prerotation = WindowInfo::PreRotation::Rotate90Clockwise;
|
||||||
|
std::swap(size.width, size.height);
|
||||||
|
DEV_LOG("Using VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR pretransform.");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case VK_SURFACE_TRANSFORM_ROTATE_180_BIT_KHR:
|
||||||
|
transform = VK_SURFACE_TRANSFORM_ROTATE_180_BIT_KHR;
|
||||||
|
window_prerotation = WindowInfo::PreRotation::Rotate180Clockwise;
|
||||||
|
DEV_LOG("Using VK_SURFACE_TRANSFORM_ROTATE_180_BIT_KHR pretransform.");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR:
|
||||||
|
transform = VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR;
|
||||||
|
window_prerotation = WindowInfo::PreRotation::Rotate270Clockwise;
|
||||||
|
std::swap(size.width, size.height);
|
||||||
|
DEV_LOG("Using VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR pretransform.");
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
{
|
||||||
if (!(surface_caps.surfaceCapabilities.supportedTransforms & VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR))
|
if (!(surface_caps.surfaceCapabilities.supportedTransforms & VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR))
|
||||||
|
{
|
||||||
|
WARNING_LOG("Unhandled surface transform 0x{:X}, identity unsupported.",
|
||||||
|
static_cast<u32>(surface_caps.surfaceCapabilities.supportedTransforms));
|
||||||
transform = surface_caps.surfaceCapabilities.currentTransform;
|
transform = surface_caps.surfaceCapabilities.currentTransform;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
WARNING_LOG("Unhandled surface transform 0x{:X}",
|
||||||
|
static_cast<u32>(surface_caps.surfaceCapabilities.supportedTransforms));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
VkCompositeAlphaFlagBitsKHR alpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
|
VkCompositeAlphaFlagBitsKHR alpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
|
||||||
if (!(surface_caps.surfaceCapabilities.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR))
|
if (!(surface_caps.surfaceCapabilities.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR))
|
||||||
|
@ -486,17 +527,18 @@ bool VulkanSwapChain::CreateSwapChain(VulkanDevice& dev, Error* error)
|
||||||
if (old_swap_chain != VK_NULL_HANDLE)
|
if (old_swap_chain != VK_NULL_HANDLE)
|
||||||
vkDestroySwapchainKHR(vkdev, old_swap_chain, nullptr);
|
vkDestroySwapchainKHR(vkdev, old_swap_chain, nullptr);
|
||||||
|
|
||||||
if (size.width == 0 || size.width > std::numeric_limits<u16>::max() || size.height == 0 ||
|
if (window_size.width == 0 || window_size.width > std::numeric_limits<u16>::max() || window_size.height == 0 ||
|
||||||
size.height > std::numeric_limits<u16>::max())
|
window_size.height > std::numeric_limits<u16>::max())
|
||||||
{
|
{
|
||||||
Error::SetStringFmt(error, "Invalid swap chain dimensions: {}x{}", size.width, size.height);
|
Error::SetStringFmt(error, "Invalid swap chain dimensions: {}x{}", window_size.width, window_size.height);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_present_mode = present_mode.value();
|
m_present_mode = present_mode.value();
|
||||||
m_window_info.surface_width = static_cast<u16>(size.width);
|
m_window_info.surface_width = static_cast<u16>(window_size.width);
|
||||||
m_window_info.surface_height = static_cast<u16>(size.height);
|
m_window_info.surface_height = static_cast<u16>(window_size.height);
|
||||||
m_window_info.surface_format = VulkanDevice::GetFormatForVkFormat(surface_format->format);
|
m_window_info.surface_format = VulkanDevice::GetFormatForVkFormat(surface_format->format);
|
||||||
|
m_window_info.surface_prerotation = window_prerotation;
|
||||||
if (m_window_info.surface_format == GPUTexture::Format::Unknown)
|
if (m_window_info.surface_format == GPUTexture::Format::Unknown)
|
||||||
{
|
{
|
||||||
Error::SetStringFmt(error, "Unknown surface format {}", static_cast<u32>(surface_format->format));
|
Error::SetStringFmt(error, "Unknown surface format {}", static_cast<u32>(surface_format->format));
|
||||||
|
@ -534,6 +576,8 @@ bool VulkanSwapChain::CreateSwapChainImages(VulkanDevice& dev, Error* error)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const u32 fb_width = GetPostRotatedWidth();
|
||||||
|
const u32 fb_height = GetPostRotatedHeight();
|
||||||
m_images.reserve(image_count);
|
m_images.reserve(image_count);
|
||||||
m_current_image = 0;
|
m_current_image = 0;
|
||||||
for (u32 i = 0; i < image_count; i++)
|
for (u32 i = 0; i < image_count; i++)
|
||||||
|
@ -565,7 +609,7 @@ bool VulkanSwapChain::CreateSwapChainImages(VulkanDevice& dev, Error* error)
|
||||||
Vulkan::FramebufferBuilder fbb;
|
Vulkan::FramebufferBuilder fbb;
|
||||||
fbb.AddAttachment(image.view);
|
fbb.AddAttachment(image.view);
|
||||||
fbb.SetRenderPass(render_pass);
|
fbb.SetRenderPass(render_pass);
|
||||||
fbb.SetSize(m_window_info.surface_width, m_window_info.surface_height, 1);
|
fbb.SetSize(fb_width, fb_height, 1);
|
||||||
if ((image.framebuffer = fbb.Create(vkdev)) == VK_NULL_HANDLE)
|
if ((image.framebuffer = fbb.Create(vkdev)) == VK_NULL_HANDLE)
|
||||||
{
|
{
|
||||||
Error::SetStringView(error, "Failed to create swap chain image framebuffer.");
|
Error::SetStringView(error, "Failed to create swap chain image framebuffer.");
|
||||||
|
|
|
@ -9,8 +9,31 @@
|
||||||
#include "common/log.h"
|
#include "common/log.h"
|
||||||
#include "common/scoped_guard.h"
|
#include "common/scoped_guard.h"
|
||||||
|
|
||||||
|
#include <numbers>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
LOG_CHANNEL(WindowInfo);
|
LOG_CHANNEL(WindowInfo);
|
||||||
|
|
||||||
|
void WindowInfo::SetPreRotated(PreRotation prerotation)
|
||||||
|
{
|
||||||
|
if (ShouldSwapDimensionsForPreRotation(prerotation) != ShouldSwapDimensionsForPreRotation(surface_prerotation))
|
||||||
|
std::swap(surface_width, surface_height);
|
||||||
|
|
||||||
|
surface_prerotation = prerotation;
|
||||||
|
}
|
||||||
|
|
||||||
|
float WindowInfo::GetZRotationForPreRotation(PreRotation prerotation)
|
||||||
|
{
|
||||||
|
static constexpr const std::array<float, 4> rotation_radians = {{
|
||||||
|
0.0f, // Identity
|
||||||
|
static_cast<float>(std::numbers::pi * 1.5f), // Rotate90Clockwise
|
||||||
|
static_cast<float>(std::numbers::pi), // Rotate180Clockwise
|
||||||
|
static_cast<float>(std::numbers::pi / 2.0), // Rotate270Clockwise
|
||||||
|
}};
|
||||||
|
|
||||||
|
return rotation_radians[static_cast<size_t>(prerotation)];
|
||||||
|
}
|
||||||
|
|
||||||
#if defined(_WIN32)
|
#if defined(_WIN32)
|
||||||
|
|
||||||
#include "common/windows_headers.h"
|
#include "common/windows_headers.h"
|
||||||
|
|
|
@ -24,8 +24,17 @@ struct WindowInfo
|
||||||
Android,
|
Android,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum class PreRotation : u8
|
||||||
|
{
|
||||||
|
Identity,
|
||||||
|
Rotate90Clockwise,
|
||||||
|
Rotate180Clockwise,
|
||||||
|
Rotate270Clockwise,
|
||||||
|
};
|
||||||
|
|
||||||
Type type = Type::Surfaceless;
|
Type type = Type::Surfaceless;
|
||||||
GPUTexture::Format surface_format = GPUTexture::Format::Unknown;
|
GPUTexture::Format surface_format = GPUTexture::Format::Unknown;
|
||||||
|
PreRotation surface_prerotation = PreRotation::Identity;
|
||||||
u16 surface_width = 0;
|
u16 surface_width = 0;
|
||||||
u16 surface_height = 0;
|
u16 surface_height = 0;
|
||||||
float surface_refresh_rate = 0.0f;
|
float surface_refresh_rate = 0.0f;
|
||||||
|
@ -35,5 +44,24 @@ struct WindowInfo
|
||||||
|
|
||||||
ALWAYS_INLINE bool IsSurfaceless() const { return type == Type::Surfaceless; }
|
ALWAYS_INLINE bool IsSurfaceless() const { return type == Type::Surfaceless; }
|
||||||
|
|
||||||
|
ALWAYS_INLINE u32 GetPostRotatedWidth() const
|
||||||
|
{
|
||||||
|
return ShouldSwapDimensionsForPreRotation(surface_prerotation) ? surface_height : surface_width;
|
||||||
|
}
|
||||||
|
ALWAYS_INLINE u32 GetPostRotatedHeight() const
|
||||||
|
{
|
||||||
|
return ShouldSwapDimensionsForPreRotation(surface_prerotation) ? surface_width : surface_height;
|
||||||
|
}
|
||||||
|
|
||||||
|
ALWAYS_INLINE static bool ShouldSwapDimensionsForPreRotation(PreRotation prerotation)
|
||||||
|
{
|
||||||
|
return (prerotation == PreRotation::Rotate90Clockwise || prerotation == PreRotation::Rotate270Clockwise);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets a new pre-rotation, adjusting the virtual width/height to suit.
|
||||||
|
void SetPreRotated(PreRotation prerotation);
|
||||||
|
|
||||||
|
static float GetZRotationForPreRotation(PreRotation prerotation);
|
||||||
|
|
||||||
static std::optional<float> QueryRefreshRateForWindow(const WindowInfo& wi, Error* error = nullptr);
|
static std::optional<float> QueryRefreshRateForWindow(const WindowInfo& wi, Error* error = nullptr);
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue