Files
Henrik RydgårdandClaude Opus 5.5 5fbd134a7d Android: Show the game on a second display, with the controls on the phone
A presentation display (HDMI or USB-C video out, some wireless displays,
or the simulated ones in Developer options) gets a Presentation with a
SurfaceView, whose surface becomes a Game output, when the backend is
Vulkan. Otherwise Android keeps mirroring.

The surface mustn't be touched after surfaceDestroyed returns, so the
output is removed with the new Native_RemoveOutputAndWait(), which
returns once the render thread has let go of it (or right away if the
graphics are down). The UI drawn on an output now leaves out the main
swapchain's pre-rotation, as the game already did.

"In menus on %1" gets the device name from Settings.Global.DEVICE_NAME.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 22:31:07 +02:00

142 lines
4.0 KiB
C++

#include <cstdio>
#include "Common/System/Display.h"
#include "Common/Math/math_util.h"
#include "Common/GPU/MiscTypes.h"
#include "Common/Log.h"
DisplayProperties g_display;
DeviceOrientation DisplayProperties::GetDeviceOrientation() const {
return (dp_yres > dp_xres * 1.1f) ? DeviceOrientation::Portrait : DeviceOrientation::Landscape;
}
std::string_view DeviceOrientationToString(DeviceOrientation orientation) {
switch (orientation) {
case DeviceOrientation::Landscape:
return "Landscape";
case DeviceOrientation::Portrait:
return "Portrait";
default:
return "N/A";
}
}
template<class T>
void RotateRectToDisplayImpl(DisplayRect<T> &rect, T curRTWidth, T curRTHeight) {
switch (g_display.rotation) {
case DisplayRotation::ROTATE_180:
rect.x = curRTWidth - rect.w - rect.x;
rect.y = curRTHeight - rect.h - rect.y;
break;
case DisplayRotation::ROTATE_90: {
// Note that curRTWidth_ and curRTHeight_ are "swapped"!
T origX = rect.x;
T origY = rect.y;
T rth = curRTWidth;
rect.x = clamp_value(rth - rect.h - origY, T{}, curRTWidth);
rect.y = origX;
T temp = rect.w;
rect.w = rect.h;
rect.h = temp;
break;
}
case DisplayRotation::ROTATE_270: {
T origX = rect.x;
T origY = rect.y;
T rtw = curRTHeight;
rect.x = origY;
// y runs along the physical height. Clamping it to the width broke scissors on the left of the screen (#15773).
rect.y = clamp_value(rtw - rect.w - origX, T{}, curRTHeight);
T temp = rect.w;
rect.w = rect.h;
rect.h = temp;
break;
}
case DisplayRotation::ROTATE_0:
default:
break;
}
}
void RotateRectToDisplay(DisplayRect<int> &rect, int curRTWidth, int curRTHeight) {
RotateRectToDisplayImpl<int>(rect, curRTWidth, curRTHeight);
}
void RotateRectToDisplay(DisplayRect<float> &rect, float curRTWidth, float curRTHeight) {
RotateRectToDisplayImpl<float>(rect, curRTWidth, curRTHeight);
}
DisplayProperties::DisplayProperties() {
rot_matrix.setIdentity();
}
bool DisplayProperties::Recalculate(int new_pixel_xres, int new_pixel_yres, float new_scale_x, float new_scale_y, float customScale) {
INFO_LOG(Log::G3D, "recalculate: %dx%d, %f, %f, %f", new_pixel_xres, new_pixel_yres, new_scale_x, new_scale_y, customScale);
bool px_changed = false;
if (new_pixel_xres > 0 && pixel_xres != new_pixel_xres) {
pixel_xres = new_pixel_xres;
px_changed = true;
}
if (new_pixel_yres > 0 && pixel_yres != new_pixel_yres) {
pixel_yres = new_pixel_yres;
px_changed = true;
}
if (new_scale_x > 0) {
dpi_scale_real_x = new_scale_x;
}
if (new_scale_y > 0) {
dpi_scale_real_y = new_scale_y;
}
dpi_scale_x = dpi_scale_real_x / customScale;
dpi_scale_y = dpi_scale_real_y / customScale;
pixel_in_dps_x = 1.0f / dpi_scale_x;
pixel_in_dps_y = 1.0f / dpi_scale_y;
int new_dp_xres = (int)(pixel_xres * dpi_scale_x);
int new_dp_yres = (int)(pixel_yres * dpi_scale_y);
if (new_dp_xres != dp_xres || new_dp_yres != dp_yres || px_changed) {
dp_xres = new_dp_xres;
dp_yres = new_dp_yres;
return true;
} else {
return false;
}
}
void DisplayProperties::Print() {
printf("dp_xres/yres: %d, %d\n", dp_xres, dp_yres);
printf("pixel_xres/yres: %d, %d\n", pixel_xres, pixel_yres);
printf("dpi_scale: %f, %f\n", dpi_scale_x, dpi_scale_y);
printf("pixel_in_dps: %f, %f\n", pixel_in_dps_x, pixel_in_dps_y);
printf("dpi_real: %f, %f\n", dpi_scale_real_x, dpi_scale_real_y);
printf("display_hz: %f\n", display_hz);
printf("rotation: %d\n", (int)rotation);
rot_matrix.print();
}
// Y-flip compensation is done elsewhere now.
Lin::Matrix4x4 ComputeOrthoMatrix(float xres, float yres, CoordConvention coordConvention, bool rotateToDisplay) {
using namespace Lin;
Matrix4x4 ortho;
switch (coordConvention) {
case CoordConvention::Vulkan:
case CoordConvention::Direct3D11:
ortho.setOrthoD3D(0.0f, xres, 0, yres, -1.0f, 1.0f);
break;
case CoordConvention::OpenGL:
default:
ortho.setOrthoGL(0.0f, xres, yres, 0.0f, -1.0f, 1.0f);
break;
}
// Compensate for rotated display if needed.
if (rotateToDisplay && g_display.rotation != DisplayRotation::ROTATE_0) {
ortho = ortho * g_display.rot_matrix;
}
return ortho;
}