aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorduck912 <350528965+Duck912@users.noreply.github.com>2026-10-31 01:45:57 -0400
committerDuck912 <350528965+Duck912@users.noreply.github.com>2026-11-01 14:59:07 -0500
commitcce19772ce434e462223663b88294bfd8a0f203e (patch)
tree80d07da05e966152346f3b999636ec7e5f7b71ec /src
parente6c0a42d11f15e623c399d14454e5ae8e1a72942 (diff)
nx2:vi2: crop presents to their frame size and honour RGB10A2 images
Present word 7 holds the size of the frame drawn into the image's top-left corner, and image preference word 6 its format (1 RGBA8, 4 RGB10A2). Fire Emblem: Fortune's Weave draws 1280x720 RGB10A2 frames into 3840x2160 images; showing the whole image as RGBA8 gave a corner of wrongly decoded pixels. Assisted-by: Claude Opus 5.5 (debugging, porting and drafting) Signed-off-by: duck912 <350528965+Duck912@users.noreply.github.com>
Diffstat (limited to 'src')
-rw-r--r--src/core/hle/service/nvnflinger/pixel_format.h1
-rw-r--r--src/core/hle/service/nx2_gsv.h17
-rw-r--r--src/video_core/renderer_opengl/present/layer.cpp5
-rw-r--r--src/video_core/renderer_vulkan/present/layer.cpp2
-rw-r--r--src/video_core/surface.cpp2
5 files changed, 24 insertions, 3 deletions
diff --git a/src/core/hle/service/nvnflinger/pixel_format.h b/src/core/hle/service/nvnflinger/pixel_format.h
index b6e8b5f8a0..a38c8be777 100644
--- a/src/core/hle/service/nvnflinger/pixel_format.h
+++ b/src/core/hle/service/nvnflinger/pixel_format.h
@@ -19,6 +19,7 @@ enum class PixelFormat : u32 {
Bgra8888 = 5,
Rgba5551 = 6,
Rgba4444 = 7,
+ Rgba1010102 = 43,
};
} // namespace Service::android
diff --git a/src/core/hle/service/nx2_gsv.h b/src/core/hle/service/nx2_gsv.h
index 72ef2d39d4..cb92bc107b 100644
--- a/src/core/hle/service/nx2_gsv.h
+++ b/src/core/hle/service/nx2_gsv.h
@@ -1499,7 +1499,7 @@ private:
WaitForFrame(~w[22], w[24]);
}
const auto waited = std::chrono::steady_clock::now();
- Composite(w[5], ~w[22], w[24]);
+ Composite(w[5], ~w[22], w[24], w[7]);
const auto composited = std::chrono::steady_clock::now();
PaceToVblank();
const auto paced = std::chrono::steady_clock::now();
@@ -1621,9 +1621,12 @@ private:
// Shows a presented native image: word 5 is the image id, ~word 22 / word 24 the GPU fence
// (syncpoint, value) to wait for (layout from changes.patch). The image is a block-linear
// RGBA8 surface in the console's native GOB layout at GPU address == CPU address.
- void Composite(u32 image_id, u32 fence_id, u32 fence_value) {
+ // present_size: word 7 of the present, (height - 1) << 16 | (width - 1) of the frame drawn
+ // into the image's top-left corner (Fire Emblem: Fortune's Weave: 1280x720 in 3840x2160).
+ void Composite(u32 image_id, u32 fence_id, u32 fence_value, u32 present_size = 0) {
u64 address{};
Nx2ImageLayout layout{};
+ u32 image_format{};
{
std::scoped_lock lk{m_objects->mutex};
const auto image = m_objects->images.find(image_id);
@@ -1636,6 +1639,7 @@ private:
}
address = memory->second.address;
layout = Nx2CalculateImageLayout(image->second.preference.data());
+ image_format = image->second.preference[6];
}
const auto device = Nvidia::Devices::Nx2GpuMemoryRegistry::Get().DeviceAddressOf(address);
if (!device) {
@@ -1645,6 +1649,11 @@ private:
}
u32 crop_width = layout.width;
u32 crop_height = layout.height;
+ // Present word 7 holds the size of the frame drawn into the image's top-left corner.
+ if (present_size != 0) {
+ crop_width = (std::min)(crop_width, (present_size & 0xffffU) + 1);
+ crop_height = (std::min)(crop_height, (present_size >> 16) + 1);
+ }
// Handheld titles keep their docked present images (up to 3840x2160) and draw the frame
// into the top-left 1920x1080, the handheld panel's resolution; the console's display
// scans out only that region. Showing whole images put the frame in a corner inside black
@@ -1683,7 +1692,9 @@ private:
.width = layout.width,
.height = layout.height,
.stride = static_cast<u32>(layout.pitch / 4),
- .pixel_format = android::PixelFormat::Rgba8888,
+ // Preference word 6: 1 = RGBA8, 4 = RGB10A2.
+ .pixel_format = image_format == 4 ? android::PixelFormat::Rgba1010102
+ : android::PixelFormat::Rgba8888,
.transform_flags = android::BufferTransformFlags::Unset,
.crop_rect = {0, 0, static_cast<int>(crop_width), static_cast<int>(crop_height)},
.blending = Tegra::BlendMode::Opaque,
diff --git a/src/video_core/renderer_opengl/present/layer.cpp b/src/video_core/renderer_opengl/present/layer.cpp
index 9e4a0f854a..2656892f4a 100644
--- a/src/video_core/renderer_opengl/present/layer.cpp
+++ b/src/video_core/renderer_opengl/present/layer.cpp
@@ -183,6 +183,11 @@ void Layer::ConfigureFramebufferTexture(const Tegra::FramebufferConfig& framebuf
framebuffer_texture.gl_format = GL_RGBA;
framebuffer_texture.gl_type = GL_UNSIGNED_INT_8_8_8_8_REV;
break;
+ case Service::android::PixelFormat::Rgba1010102:
+ internal_format = GL_RGB10_A2;
+ framebuffer_texture.gl_format = GL_RGBA;
+ framebuffer_texture.gl_type = GL_UNSIGNED_INT_2_10_10_10_REV;
+ break;
case Service::android::PixelFormat::Rgb565:
internal_format = GL_RGB565;
framebuffer_texture.gl_format = GL_RGB;
diff --git a/src/video_core/renderer_vulkan/present/layer.cpp b/src/video_core/renderer_vulkan/present/layer.cpp
index fa24c02f6d..5652090bd7 100644
--- a/src/video_core/renderer_vulkan/present/layer.cpp
+++ b/src/video_core/renderer_vulkan/present/layer.cpp
@@ -52,6 +52,8 @@ VkFormat GetFormat(const Tegra::FramebufferConfig& framebuffer) {
return VK_FORMAT_R5G6B5_UNORM_PACK16;
case Service::android::PixelFormat::Bgra8888:
return VK_FORMAT_B8G8R8A8_UNORM;
+ case Service::android::PixelFormat::Rgba1010102:
+ return VK_FORMAT_A2B10G10R10_UNORM_PACK32;
default:
UNIMPLEMENTED_MSG("Unknown framebuffer pixel format: {}",
static_cast<u32>(framebuffer.pixel_format));
diff --git a/src/video_core/surface.cpp b/src/video_core/surface.cpp
index 66b9577cb5..ef4859ea3e 100644
--- a/src/video_core/surface.cpp
+++ b/src/video_core/surface.cpp
@@ -214,6 +214,8 @@ PixelFormat PixelFormatFromGPUPixelFormat(Service::android::PixelFormat format)
return PixelFormat::R5G6B5_UNORM;
case Service::android::PixelFormat::Bgra8888:
return PixelFormat::B8G8R8A8_UNORM;
+ case Service::android::PixelFormat::Rgba1010102:
+ return PixelFormat::A2B10G10R10_UNORM;
default:
UNIMPLEMENTED_MSG("Unimplemented format={}", format);
return PixelFormat::A8B8G8R8_UNORM;