diff --git a/dm/games/0400C3F00006E000/README.md b/dm/games/0400C3F00006E000/README.md index d1c100289d..02eebce4eb 100644 --- a/dm/games/0400C3F00006E000/README.md +++ b/dm/games/0400C3F00006E000/README.md @@ -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`. diff --git a/dm/gfx/hle_vi.c b/dm/gfx/hle_vi.c index 74678d7e57..3f8b0f7858 100644 --- a/dm/gfx/hle_vi.c +++ b/dm/gfx/hle_vi.c @@ -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; } diff --git a/dm/gfx/nvn/nvn_hdr.h b/dm/gfx/nvn/nvn_hdr.h index 90f9faf741..de6a2f0f13 100644 --- a/dm/gfx/nvn/nvn_hdr.h +++ b/dm/gfx/nvn/nvn_hdr.h @@ -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); diff --git a/dm/gfx/nvn/nvn_hdr_layer.m b/dm/gfx/nvn/nvn_hdr_layer.m index e87ce47b87..04cac50635 100644 --- a/dm/gfx/nvn/nvn_hdr_layer.m +++ b/dm/gfx/nvn/nvn_hdr_layer.m @@ -3,6 +3,24 @@ #import #include "nvn_hdr.h" #include +// 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(); @@ -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; @@ -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; } diff --git a/dm/ios/Sources/front_ios.m b/dm/ios/Sources/front_ios.m index 02fe9d3729..d207916f2a 100644 --- a/dm/ios/Sources/front_ios.m +++ b/dm/ios/Sources/front_ios.m @@ -6,6 +6,7 @@ #import #import #include +#include #include "front.h" #include "nvn_hdr.h" #include "hle.h" @@ -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);