[GPU/D3D12] Letterboxing cropping to action-safe area
This commit is contained in:
parent
52efbcf741
commit
0be0eb2b38
|
@ -10,7 +10,9 @@
|
||||||
#include "xenia/gpu/d3d12/d3d12_graphics_system.h"
|
#include "xenia/gpu/d3d12/d3d12_graphics_system.h"
|
||||||
|
|
||||||
#include "xenia/base/logging.h"
|
#include "xenia/base/logging.h"
|
||||||
|
#include "xenia/base/math.h"
|
||||||
#include "xenia/gpu/d3d12/d3d12_command_processor.h"
|
#include "xenia/gpu/d3d12/d3d12_command_processor.h"
|
||||||
|
#include "xenia/gpu/draw_util.h"
|
||||||
#include "xenia/ui/d3d12/d3d12_util.h"
|
#include "xenia/ui/d3d12/d3d12_util.h"
|
||||||
#include "xenia/xbox.h"
|
#include "xenia/xbox.h"
|
||||||
|
|
||||||
|
@ -265,22 +267,39 @@ void D3D12GraphicsSystem::Swap(xe::ui::UIEvent* e) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint32_t window_width, window_height;
|
||||||
|
display_context_->GetSwapChainSize(window_width, window_height);
|
||||||
|
|
||||||
|
int32_t target_x, target_y;
|
||||||
|
uint32_t target_width, target_height;
|
||||||
|
draw_util::GetPresentArea(swap_state.width, swap_state.height, window_width,
|
||||||
|
window_height, target_x, target_y, target_width,
|
||||||
|
target_height);
|
||||||
|
// For safety.
|
||||||
|
target_x = clamp(target_x, int32_t(D3D12_VIEWPORT_BOUNDS_MIN),
|
||||||
|
int32_t(D3D12_VIEWPORT_BOUNDS_MAX));
|
||||||
|
target_y = clamp(target_y, int32_t(D3D12_VIEWPORT_BOUNDS_MIN),
|
||||||
|
int32_t(D3D12_VIEWPORT_BOUNDS_MAX));
|
||||||
|
target_width = std::min(
|
||||||
|
target_width, uint32_t(int32_t(D3D12_VIEWPORT_BOUNDS_MAX) - target_x));
|
||||||
|
target_height = std::min(
|
||||||
|
target_height, uint32_t(int32_t(D3D12_VIEWPORT_BOUNDS_MAX) - target_y));
|
||||||
|
|
||||||
auto command_list = display_context_->GetSwapCommandList();
|
auto command_list = display_context_->GetSwapCommandList();
|
||||||
uint32_t swap_width, swap_height;
|
// Assuming the window has already been cleared to the needed letterbox color.
|
||||||
display_context_->GetSwapChainSize(swap_width, swap_height);
|
|
||||||
D3D12_VIEWPORT viewport;
|
D3D12_VIEWPORT viewport;
|
||||||
viewport.TopLeftX = 0.0f;
|
viewport.TopLeftX = float(target_x);
|
||||||
viewport.TopLeftY = 0.0f;
|
viewport.TopLeftY = float(target_y);
|
||||||
viewport.Width = float(swap_width);
|
viewport.Width = float(target_width);
|
||||||
viewport.Height = float(swap_height);
|
viewport.Height = float(target_height);
|
||||||
viewport.MinDepth = 0.0f;
|
viewport.MinDepth = 0.0f;
|
||||||
viewport.MaxDepth = 0.0f;
|
viewport.MaxDepth = 0.0f;
|
||||||
command_list->RSSetViewports(1, &viewport);
|
command_list->RSSetViewports(1, &viewport);
|
||||||
D3D12_RECT scissor;
|
D3D12_RECT scissor;
|
||||||
scissor.left = 0;
|
scissor.left = 0;
|
||||||
scissor.top = 0;
|
scissor.top = 0;
|
||||||
scissor.right = swap_width;
|
scissor.right = window_width;
|
||||||
scissor.bottom = swap_height;
|
scissor.bottom = window_height;
|
||||||
command_list->RSSetScissorRects(1, &scissor);
|
command_list->RSSetScissorRects(1, &scissor);
|
||||||
command_list->SetDescriptorHeaps(1, &swap_srv_heap);
|
command_list->SetDescriptorHeaps(1, &swap_srv_heap);
|
||||||
StretchTextureToFrontBuffer(
|
StretchTextureToFrontBuffer(
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
|
|
||||||
#include "xenia/gpu/draw_util.h"
|
#include "xenia/gpu/draw_util.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
|
||||||
|
@ -31,6 +32,36 @@ DEFINE_bool(
|
||||||
"for certain games like GTA IV to work).",
|
"for certain games like GTA IV to work).",
|
||||||
"GPU");
|
"GPU");
|
||||||
|
|
||||||
|
DEFINE_bool(
|
||||||
|
present_stretch, true,
|
||||||
|
"Whether to rescale the image, instead of maintaining the original pixel "
|
||||||
|
"size, when presenting to the window. When this is disabled, other "
|
||||||
|
"positioning options are ignored.",
|
||||||
|
"GPU");
|
||||||
|
DEFINE_bool(
|
||||||
|
present_letterbox, true,
|
||||||
|
"Maintain aspect ratio when stretching by displaying bars around the image "
|
||||||
|
"when there's no more overscan area to crop out.",
|
||||||
|
"GPU");
|
||||||
|
// https://github.com/MonoGame/MonoGame/issues/4697#issuecomment-217779403
|
||||||
|
// Using the value from DirectXTK (5% cropped out from each side, thus 90%),
|
||||||
|
// which is not exactly the Xbox One title-safe area, but close, and within the
|
||||||
|
// action-safe area:
|
||||||
|
// https://github.com/microsoft/DirectXTK/blob/1e80a465c6960b457ef9ab6716672c1443a45024/Src/SimpleMath.cpp#L144
|
||||||
|
// XNA TitleSafeArea is 80%, but it's very conservative, designed for CRT, and
|
||||||
|
// is the title-safe area rather than the action-safe area.
|
||||||
|
// 90% is also exactly the fraction of 16:9 height in 16:10.
|
||||||
|
DEFINE_int32(
|
||||||
|
present_safe_area_x, 90,
|
||||||
|
"Percentage of the image width that can be kept when presenting to "
|
||||||
|
"maintain aspect ratio without letterboxing or stretching.",
|
||||||
|
"GPU");
|
||||||
|
DEFINE_int32(
|
||||||
|
present_safe_area_y, 90,
|
||||||
|
"Percentage of the image height that can be kept when presenting to "
|
||||||
|
"maintain aspect ratio without letterboxing or stretching.",
|
||||||
|
"GPU");
|
||||||
|
|
||||||
namespace xe {
|
namespace xe {
|
||||||
namespace gpu {
|
namespace gpu {
|
||||||
namespace draw_util {
|
namespace draw_util {
|
||||||
|
@ -589,6 +620,87 @@ ResolveCopyShaderIndex ResolveInfo::GetCopyShader(
|
||||||
return shader;
|
return shader;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GetPresentArea(uint32_t source_width, uint32_t source_height,
|
||||||
|
uint32_t window_width, uint32_t window_height,
|
||||||
|
int32_t& target_x_out, int32_t& target_y_out,
|
||||||
|
uint32_t& target_width_out, uint32_t& target_height_out) {
|
||||||
|
if (!cvars::present_stretch) {
|
||||||
|
target_x_out = (int32_t(window_width) - int32_t(source_width)) / 2;
|
||||||
|
target_y_out = (int32_t(window_height) - int32_t(source_height)) / 2;
|
||||||
|
target_width_out = source_width;
|
||||||
|
target_height_out = source_height;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Prevent division by zero.
|
||||||
|
if (!source_width || !source_height) {
|
||||||
|
target_x_out = 0;
|
||||||
|
target_y_out = 0;
|
||||||
|
target_width_out = 0;
|
||||||
|
target_height_out = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (uint64_t(window_width) * source_height >
|
||||||
|
uint64_t(source_width) * window_height) {
|
||||||
|
// The window is wider that the source - crop along Y, then letterbox or
|
||||||
|
// stretch along X.
|
||||||
|
uint32_t present_safe_area;
|
||||||
|
if (cvars::present_safe_area_y > 0 && cvars::present_safe_area_y < 100) {
|
||||||
|
present_safe_area = uint32_t(cvars::present_safe_area_y);
|
||||||
|
} else {
|
||||||
|
present_safe_area = 100;
|
||||||
|
}
|
||||||
|
uint32_t target_height =
|
||||||
|
uint32_t(uint64_t(window_width) * source_height / source_width);
|
||||||
|
bool letterbox = false;
|
||||||
|
if (target_height * present_safe_area > window_height * 100) {
|
||||||
|
// Don't crop out more than the safe area margin - letterbox or stretch.
|
||||||
|
target_height = window_height * 100 / present_safe_area;
|
||||||
|
letterbox = true;
|
||||||
|
}
|
||||||
|
if (letterbox && cvars::present_letterbox) {
|
||||||
|
uint32_t target_width =
|
||||||
|
uint32_t(uint64_t(source_width) * window_height * 100 /
|
||||||
|
(source_height * present_safe_area));
|
||||||
|
target_x_out = (int32_t(window_width) - int32_t(target_width)) / 2;
|
||||||
|
target_width_out = target_width;
|
||||||
|
} else {
|
||||||
|
target_x_out = 0;
|
||||||
|
target_width_out = window_width;
|
||||||
|
}
|
||||||
|
target_y_out = (int32_t(window_height) - int32_t(target_height)) / 2;
|
||||||
|
target_height_out = target_height;
|
||||||
|
} else {
|
||||||
|
// The window is taller than the source - crop along X, then letterbox or
|
||||||
|
// stretch along Y.
|
||||||
|
uint32_t present_safe_area;
|
||||||
|
if (cvars::present_safe_area_x > 0 && cvars::present_safe_area_x < 100) {
|
||||||
|
present_safe_area = uint32_t(cvars::present_safe_area_x);
|
||||||
|
} else {
|
||||||
|
present_safe_area = 100;
|
||||||
|
}
|
||||||
|
uint32_t target_width =
|
||||||
|
uint32_t(uint64_t(window_height) * source_width / source_height);
|
||||||
|
bool letterbox = false;
|
||||||
|
if (target_width * present_safe_area > window_width * 100) {
|
||||||
|
// Don't crop out more than the safe area margin - letterbox or stretch.
|
||||||
|
target_width = window_width * 100 / present_safe_area;
|
||||||
|
letterbox = true;
|
||||||
|
}
|
||||||
|
if (letterbox && cvars::present_letterbox) {
|
||||||
|
uint32_t target_height =
|
||||||
|
uint32_t(uint64_t(source_height) * window_width * 100 /
|
||||||
|
(source_width * present_safe_area));
|
||||||
|
target_y_out = (int32_t(window_height) - int32_t(target_height)) / 2;
|
||||||
|
target_height_out = target_height;
|
||||||
|
} else {
|
||||||
|
target_y_out = 0;
|
||||||
|
target_height_out = window_height;
|
||||||
|
}
|
||||||
|
target_x_out = (int32_t(window_width) - int32_t(target_width)) / 2;
|
||||||
|
target_width_out = target_width;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace draw_util
|
} // namespace draw_util
|
||||||
} // namespace gpu
|
} // namespace gpu
|
||||||
} // namespace xe
|
} // namespace xe
|
||||||
|
|
|
@ -272,6 +272,14 @@ bool GetResolveInfo(const RegisterFile& regs, const Memory& memory,
|
||||||
TraceWriter& trace_writer, uint32_t resolution_scale,
|
TraceWriter& trace_writer, uint32_t resolution_scale,
|
||||||
bool edram_16_as_minus_1_to_1, ResolveInfo& info_out);
|
bool edram_16_as_minus_1_to_1, ResolveInfo& info_out);
|
||||||
|
|
||||||
|
// Taking user configuration - stretching or letterboxing, overscan region to
|
||||||
|
// crop to fill while maintaining the aspect ratio - into account, returns the
|
||||||
|
// area where the frame should be presented in the host window.
|
||||||
|
void GetPresentArea(uint32_t source_width, uint32_t source_height,
|
||||||
|
uint32_t window_width, uint32_t window_height,
|
||||||
|
int32_t& target_x_out, int32_t& target_y_out,
|
||||||
|
uint32_t& target_width_out, uint32_t& target_height_out);
|
||||||
|
|
||||||
} // namespace draw_util
|
} // namespace draw_util
|
||||||
} // namespace gpu
|
} // namespace gpu
|
||||||
} // namespace xe
|
} // namespace xe
|
||||||
|
|
|
@ -300,9 +300,9 @@ void D3D12Context::BeginSwap() {
|
||||||
clear_color[1] = 1.0f;
|
clear_color[1] = 1.0f;
|
||||||
clear_color[2] = 0.0f;
|
clear_color[2] = 0.0f;
|
||||||
} else {
|
} else {
|
||||||
clear_color[0] = 238.0f / 255.0f;
|
clear_color[0] = 0.0f;
|
||||||
clear_color[1] = 238.0f / 255.0f;
|
clear_color[1] = 0.0f;
|
||||||
clear_color[2] = 238.0f / 255.0f;
|
clear_color[2] = 0.0f;
|
||||||
}
|
}
|
||||||
clear_color[3] = 1.0f;
|
clear_color[3] = 1.0f;
|
||||||
swap_command_list_->ClearRenderTargetView(back_buffer_rtv, clear_color, 0,
|
swap_command_list_->ClearRenderTargetView(back_buffer_rtv, clear_color, 0,
|
||||||
|
|
|
@ -253,20 +253,6 @@ bool Win32Window::ReleaseMouse() {
|
||||||
|
|
||||||
bool Win32Window::is_fullscreen() const { return fullscreen_; }
|
bool Win32Window::is_fullscreen() const { return fullscreen_; }
|
||||||
|
|
||||||
// https://blogs.msdn.microsoft.com/oldnewthing/20131017-00/?p=2903
|
|
||||||
BOOL UnadjustWindowRect(LPRECT prc, DWORD dwStyle, BOOL fMenu) {
|
|
||||||
RECT rc;
|
|
||||||
SetRectEmpty(&rc);
|
|
||||||
BOOL fRc = AdjustWindowRect(&rc, dwStyle, fMenu);
|
|
||||||
if (fRc) {
|
|
||||||
prc->left -= rc.left;
|
|
||||||
prc->top -= rc.top;
|
|
||||||
prc->right -= rc.right;
|
|
||||||
prc->bottom -= rc.bottom;
|
|
||||||
}
|
|
||||||
return fRc;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Win32Window::ToggleFullscreen(bool fullscreen) {
|
void Win32Window::ToggleFullscreen(bool fullscreen) {
|
||||||
if (fullscreen == is_fullscreen()) {
|
if (fullscreen == is_fullscreen()) {
|
||||||
return;
|
return;
|
||||||
|
@ -288,9 +274,6 @@ void Win32Window::ToggleFullscreen(bool fullscreen) {
|
||||||
AdjustWindowRect(&rc, GetWindowLong(hwnd_, GWL_STYLE), false);
|
AdjustWindowRect(&rc, GetWindowLong(hwnd_, GWL_STYLE), false);
|
||||||
MoveWindow(hwnd_, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top,
|
MoveWindow(hwnd_, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top,
|
||||||
TRUE);
|
TRUE);
|
||||||
|
|
||||||
width_ = rc.right - rc.left;
|
|
||||||
height_ = rc.bottom - rc.top;
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Reinstate borders, resize to 1280x720
|
// Reinstate borders, resize to 1280x720
|
||||||
|
@ -301,15 +284,13 @@ void Win32Window::ToggleFullscreen(bool fullscreen) {
|
||||||
if (main_menu) {
|
if (main_menu) {
|
||||||
::SetMenu(hwnd_, main_menu->handle());
|
::SetMenu(hwnd_, main_menu->handle());
|
||||||
}
|
}
|
||||||
|
|
||||||
auto& rc = windowed_pos_.rcNormalPosition;
|
|
||||||
bool has_menu = main_menu_ ? true : false;
|
|
||||||
UnadjustWindowRect(&rc, GetWindowLong(hwnd_, GWL_STYLE), has_menu);
|
|
||||||
width_ = rc.right - rc.left;
|
|
||||||
height_ = rc.bottom - rc.top;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fullscreen_ = fullscreen;
|
fullscreen_ = fullscreen;
|
||||||
|
|
||||||
|
// width_ and height_ will be updated by the WM_SIZE handler -
|
||||||
|
// windowed_pos_.rcNormalPosition is also not the correct source for them when
|
||||||
|
// switching from fullscreen to maximized.
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Win32Window::is_bordered() const {
|
bool Win32Window::is_bordered() const {
|
||||||
|
|
Loading…
Reference in New Issue