Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions dm/games/0400C3F00006E000/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,8 @@ Current, 2026-11-03: an isolated iOS build defaults to handheld mode and request

The guest's fixed heap and virtual reservations are preserved. iOS uses 32 MiB upload batches ([mechanism and fixture](../../../docs/reference/memory.md#upload-staging-grows-within-one-rendering-submission)). `memory-budget.tsv` in Documents records footprint, available memory, their sum, peak footprint and graphics ledger every second from preload. No Metal device is initialized by this monitor. The TSV and `log.txt` are needed to establish the actual MKW high-water mark on the phone. No arbitrary guest-page discard or host swap mechanism is enabled. Fitting the device budget, frame pacing and gameplay remain unverified on iOS.

HDR peak by device (2026-11-07, an iPhone 19,4): the game is told the phone's own peak (1,600 nits for the iPhone 12 Pro and later, `front_ios.m`) instead of a fixed 1,000 in `QueryDisplayHdrStatus`, and the layer metadata carries the same value (`dm_hdr_mastering_peak`, `nvn_hdr_layer.m`); `DM_HDR_EDR_MAX` (a ratio of paper white) still caps it. On the phone it looks good in a race; no comparison against the 1,000-nit value was measured, and a model absent from the table keeps 1,000.

## Open

- `nvnCommandBufferShadingRateImagePalette` and `BindShadingRateImage` are not implemented (about 2,800 calls a race): the game draws at the full pixel rate. Unknown names: `nvn*CooperativeVectorMatrix*`, `nvn*PoolWriteDescriptorTableData`.
Expand Down
4 changes: 2 additions & 2 deletions dm/gfx/hle_vi.c
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,8 @@ HLE_FORCE("_ZN2nn2vi8FinalizeEv", "display service shutdown; the host has no vi
HLE_FORCE("_ZN2nn2vi21QueryDisplayHdrStatusEPbPNS0_15HdrOutputFormatEPtPKNS0_7DisplayE", "display HDR status: EDR capability of the current host screen", Result, h_vi_HdrStatus,
uint8_t *on, uint32_t *fmt, uint16_t *peak, const void *disp) {
(void)disp; int enabled = dm_hdr_query();
if (on) *on = enabled; if (fmt) *fmt = enabled ? 1 : 0; if (peak) *peak = enabled ? DM_HDR_MASTERING_PEAK : 0;
static int last = -1; if (last != enabled) { LOG("vi HDR: %s, virtual peak %d nits, paper white %d nits", enabled ? "enabled (EDR display)" : "off", enabled ? DM_HDR_MASTERING_PEAK : 0, DM_HDR_PAPER_WHITE); last = enabled; }
if (on) *on = enabled; if (fmt) *fmt = enabled ? 1 : 0; if (peak) *peak = enabled ? (uint16_t)dm_hdr_mastering_peak() : 0;
static int last = -1; if (last != enabled) { LOG("vi HDR: %s, virtual peak %d nits, paper white %d nits", enabled ? "enabled (EDR display)" : "off", enabled ? dm_hdr_mastering_peak() : 0, DM_HDR_PAPER_WHITE); last = enabled; }
return RES_OK; }
HLE_FORCE("_ZN2nn2vi25QueryDisplayHdrPaperWhiteEPtPKNS0_7DisplayE", "display HDR paper white: 203 nits (BT.2408)", Result, h_vi_PaperWhite, uint16_t *nits, const void *disp) {
(void)disp; if (nits) *nits = DM_HDR_PAPER_WHITE; return RES_OK; }
Expand Down
4 changes: 4 additions & 0 deletions dm/gfx/nvn/nvn_hdr.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,9 @@ void dm_hdr_luminance(int extended, uint16_t low, uint16_t high);
void dm_hdr_luminance_range(int extended, uint16_t *low, uint16_t *high);
// Called on the UI thread before the guest starts, and when its screen changes.
void dm_hdr_configure_layer(void *layer, double potential_headroom);
// The device's own peak HDR brightness in nits (front ends publish it by the hardware model);
// the layers' mastering metadata tops out at it when it is below the game's own peak
void dm_hdr_peak(double peak_nits);
int dm_hdr_mastering_peak(void); // what the game is told: the device peak when known, else DM_HDR_MASTERING_PEAK
// Called by the present owner; applies content metadata for following drawables.
void dm_hdr_layer_metadata(void *layer, int hdr_content);
22 changes: 20 additions & 2 deletions dm/gfx/nvn/nvn_hdr_layer.m
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,24 @@
#import <CoreGraphics/CoreGraphics.h>
#include "nvn_hdr.h"
#include <math.h>
// The device's own peak HDR brightness (nits), set by the front end from the hardware model
// (front_ios.m); 0 until then, in which case the layer keeps the mastering metadata it would
// have had. The environmental DM_HDR_EDR_MAX (a ratio of paper white) overrides it.
static double g_hdr_peak;
void dm_hdr_peak(double peak_nits) { if (peak_nits >= 0) g_hdr_peak = peak_nits; }
static double hdr_peak_clamp(double nits) {
const char *em = getenv("DM_HDR_EDR_MAX");
double clamp = em && *em ? strtod(em, NULL) : 0;
if (isfinite(clamp) && clamp > 1.0) { double cap = DM_HDR_PAPER_WHITE * clamp; return nits < cap ? nits : cap; }
if (g_hdr_peak > 0 && nits > g_hdr_peak) return g_hdr_peak;
return nits;
}
// The peak the game is told its display reaches (QueryDisplayHdrStatus): the device's own peak when the front end published one, else the virtual 1,000 nits. A game tone-maps to
// this number, so the 1,600-nit phones were shown a 1,000-nit display; DM_HDR_EDR_MAX (a ratio of paper white) still caps it
int dm_hdr_mastering_peak(void) {
double p = g_hdr_peak > 0 ? g_hdr_peak : DM_HDR_MASTERING_PEAK;
return (int)hdr_peak_clamp(p);
}
void dm_hdr_configure_layer(void *ptr, double headroom) {
CAMetalLayer *layer = (__bridge CAMetalLayer *)ptr;
int requested = dm_hdr_requested();
Expand All @@ -14,7 +32,7 @@ void dm_hdr_configure_layer(void *ptr, double headroom) {
CGColorSpaceRef cs = CGColorSpaceCreateWithName(kCGColorSpaceExtendedLinearITUR_2020);
layer.colorspace = cs; CGColorSpaceRelease(cs);
layer.wantsExtendedDynamicRangeContent = YES;
if (!layer.EDRMetadata) layer.EDRMetadata = [CAEDRMetadata HDR10MetadataWithMinLuminance:0 maxLuminance:DM_HDR_MASTERING_PEAK opticalOutputScale:DM_HDR_LINEAR_UNIT];
if (!layer.EDRMetadata) layer.EDRMetadata = [CAEDRMetadata HDR10MetadataWithMinLuminance:0 maxLuminance:dm_hdr_mastering_peak() opticalOutputScale:DM_HDR_LINEAR_UNIT];
}
void dm_hdr_layer_metadata(void *ptr, int hdr_content) {
CAMetalLayer *layer = (__bridge CAMetalLayer *)ptr;
Expand All @@ -25,6 +43,6 @@ void dm_hdr_layer_metadata(void *ptr, int hdr_content) {
static uint32_t last_range;
uint32_t range = lo | (uint32_t)hi << 16;
if (last_layer == layer && last_range == range) return;
layer.EDRMetadata = [CAEDRMetadata HDR10MetadataWithMinLuminance:lo maxLuminance:hi opticalOutputScale:DM_HDR_LINEAR_UNIT];
layer.EDRMetadata = [CAEDRMetadata HDR10MetadataWithMinLuminance:lo maxLuminance:hdr_peak_clamp(hi) opticalOutputScale:DM_HDR_LINEAR_UNIT];
last_layer = layer; last_range = range;
}
17 changes: 17 additions & 0 deletions dm/ios/Sources/front_ios.m
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#import <Metal/Metal.h>
#import <AVFAudio/AVFAudio.h>
#include <errno.h>
#include <sys/utsname.h>
#include "front.h"
#include "nvn_hdr.h"
#include "hle.h"
Expand Down Expand Up @@ -45,6 +46,22 @@ - (void)didMoveToWindow { // the phone's screen or a TV's (DmTVSceneDelegate):
[super didMoveToWindow];
UIScreen *s = self.window.windowScene.screen;
if (!s) { dm_hdr_display(0); return; }
// The device's own peak HDR brightness (nits), from Apple's published display specs by the
// hardware model: the mastering metadata the layers publish tops out where the display reads,
// so the PQ range is calibrated to the screen instead of always 1,000 nits (the foldable's
// panel is of the 1,600-nit class like the Pro phones). A model this table does not know keeps
// the layer's own mastering peak, and DM_HDR_EDR_MAX (a ratio of paper white) still overrides.
struct utsname un; uname(&un);
const char *m = un.machine; NSString *model = m ? [NSString stringWithUTF8String:m] : @"";
double peak = 0; // 0 = unknown: the layer keeps the mastering metadata it would have had
if ([model hasPrefix:@"iPhone"] && [model length] > 6) {
NSUInteger gen = [[model substringWithRange:NSMakeRange(6, [model length] - 6)] integerValue];
if (gen >= 12) peak = 1600; // 1,600 nits on the 12 Pro and later, the modern peak the user's panels read
else if (gen >= 10) peak = 1200;
} else if ([model hasPrefix:@"iPad14"]) {
peak = 1600; // the M-series iPad XDR panels
}
dm_hdr_peak(peak); // nvn_hdr_layer.m: the layers' mastering metadata peak is the model's own
// Advertise HDR only after configuring the actual phone/TV drawable. The
// shared present pass already decodes negotiated PQ/scRGB into this layer.
dm_hdr_configure_layer((__bridge void *)self.layer, s.potentialEDRHeadroom);
Expand Down