feat(render3d): HDR10 output, and DLSS that stays still
HDR output: an HDR10 swapchain (A2B10G10R10, ST 2084 over BT.2020) when the setting asks and the display offers it, with HDR metadata. The tonemap's HDR10 variant keeps the SDR picture up to a 200-nit paper white and rolls highlights on to 1000 nits; the overlay's converts the interface to the same white. The screen and LDR images go 10-bit with it; screenshots refuse while it is on. OpenGL and the Vulkan SDR frame are unchanged. The instance asks for VK_EXT_swapchain_colorspace. HDR metadata only where the loader has vkSetHdrMetadataEXT: Streamline's interposer does not, and calling the thunk crashed the game the moment the swapchain came up HDR10. PC 4K monitor: HDR10, validation 0. R3D_HDR overrides the setting. DLSS: - the vertical jitter offset flips with Streamline's image (rows from the top): unflipped, Quality resolved the ground into concentric rings; - preset K in every mode: the default M put Performance at 18 ms a frame at 4K on an RTX 3070 Ti (33 fps against 41 with DLSS off; with K, 60); - the camera is jittered only while this frame holds a token and the last evaluate worked. R3D_DLSS_PRESET, R3D_CAM_LOG (the camera and DLSS state a frame) and R3D_NOGRAIN for measuring. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
0224af64ab
commit
6525c11b3c
16 changed files with 273 additions and 29 deletions
|
|
@ -391,9 +391,10 @@ function gpu_caps_probe() -> void {
|
|||
|
||||
# Whether the renderer actually draws a feature yet. Until a feature lands, choosing it is saved and
|
||||
# shown, and says it takes effect later. The Vulkan renderer draws the whole game (phase 37-38), and
|
||||
# DLSS super resolution and Reflex run through NVIDIA Streamline (streamline.ludic); whether this
|
||||
# DLSS super resolution and Reflex run through NVIDIA Streamline (streamline.ludic), and HDR output is an
|
||||
# HDR10 swapchain (gpu_vk_draw.ludic); whether this
|
||||
# machine can use one is the caps' question, not this one.
|
||||
function gpu_feature_implemented(f: int) -> bool { return f == GF_VULKAN or f == GF_DLSS or f == GF_REFLEX }
|
||||
function gpu_feature_implemented(f: int) -> bool { return f == GF_VULKAN or f == GF_DLSS or f == GF_REFLEX or f == GF_HDR }
|
||||
|
||||
# ---- vertex data --------------------------------------------------------------------
|
||||
# A Mesh is built through these and records what it is made of - which buffer feeds which
|
||||
|
|
|
|||
|
|
@ -95,6 +95,9 @@ function gvk_init() -> bool {
|
|||
Vk.put_ptr(iext_names, n_iext * 8, VK_KHR_SURFACE_EXTENSION_NAME); n_iext += 1
|
||||
Vk.put_ptr(iext_names, n_iext * 8, VK_KHR_WIN32_SURFACE_EXTENSION_NAME); n_iext += 1
|
||||
}
|
||||
# HDR output: an instance only lists the HDR colour spaces when it asks for them
|
||||
gvk_has_colorspace = gvk_has_surface and gvk_ext_in(iexts, nie, VK_EXT_SWAPCHAIN_COLOR_SPACE_EXTENSION_NAME)
|
||||
if gvk_has_colorspace { Vk.put_ptr(iext_names, n_iext * 8, VK_EXT_SWAPCHAIN_COLOR_SPACE_EXTENSION_NAME); n_iext += 1 }
|
||||
if n_iext > 0 {
|
||||
Vk.put_i32(ici, VkInstanceCreateInfo_enabledExtensionCount, n_iext)
|
||||
Vk.put_ptr(ici, VkInstanceCreateInfo_ppEnabledExtensionNames, iext_names)
|
||||
|
|
@ -203,7 +206,7 @@ function gvk_init() -> bool {
|
|||
Vk.put_i32(qci, VkDeviceQueueCreateInfo_queueFamilyIndex, gvk_family)
|
||||
Vk.put_i32(qci, VkDeviceQueueCreateInfo_queueCount, 1)
|
||||
Vk.put_ptr(qci, VkDeviceQueueCreateInfo_pQueuePriorities, prio)
|
||||
let dext_names = bytes(32)
|
||||
let dext_names = bytes(48)
|
||||
var n_dext = 0
|
||||
let dci = bytes(VkDeviceCreateInfo_sizeof)
|
||||
Vk.zero(dci, VkDeviceCreateInfo_sizeof)
|
||||
|
|
@ -217,6 +220,11 @@ function gvk_init() -> bool {
|
|||
Vk.put_ptr(dext_names, n_dext * 8, VK_KHR_SWAPCHAIN_EXTENSION_NAME); n_dext += 1
|
||||
# Reflex: Streamline adds VK_NV_low_latency2 to this device, which needs present ids it does not add
|
||||
if gsl_on and gvk_ext_in(dexts, nde, "VK_KHR_present_id") { Vk.put_ptr(dext_names, n_dext * 8, "VK_KHR_present_id"); n_dext += 1 }
|
||||
# HDR output tells the display what the picture holds
|
||||
# and only where the loader has the command: Streamline's interposer exports no vkSetHdrMetadataEXT,
|
||||
# and calling its thunk there crashed the game the moment the swapchain came up HDR10
|
||||
gvk_has_hdr_meta = gvk_ext_in(dexts, nde, VK_EXT_HDR_METADATA_EXTENSION_NAME) and Vk.has("vkSetHdrMetadataEXT") == 1
|
||||
if gvk_has_hdr_meta { Vk.put_ptr(dext_names, n_dext * 8, VK_EXT_HDR_METADATA_EXTENSION_NAME); n_dext += 1 }
|
||||
}
|
||||
if n_dext > 0 {
|
||||
Vk.put_i32(dci, VkDeviceCreateInfo_enabledExtensionCount, n_dext)
|
||||
|
|
|
|||
|
|
@ -943,6 +943,43 @@ var gvk_layer_views: []string = null # "tex:layer" -> index into gvk_layer_view
|
|||
var gvk_layer_view: []long = null
|
||||
var gvk_fb_ncolor: words = null # per framebuffer: colour slots drawn (draw buffers; 0 = none)
|
||||
|
||||
# ---- HDR output ------------------------------------------------------------------------------
|
||||
# HDR10 (ST 2084 over BT.2020) when the player asks for it and the surface offers it. The screen
|
||||
# image and the tonemap's LDR image go 10-bit with it; the tonemap and the overlay switch to their
|
||||
# HDR10 variants (gpu_hdr_active). Headless there is no surface, so never.
|
||||
var gvk_hdr_want: bool = false # r3d_hdr: the setting
|
||||
var gvk_hdr_on: bool = false # the swapchain is HDR10 now
|
||||
var gvk_has_colorspace: bool = false # the instance took VK_EXT_swapchain_colorspace
|
||||
var gvk_has_hdr_meta: bool = false # the device took VK_EXT_hdr_metadata
|
||||
# R3D_HDR=1 / 0 overrides the setting, for a desktop test
|
||||
function r3d_hdr(on: bool) -> void {
|
||||
var want = on
|
||||
if Os.has_env("R3D_HDR") { want = Text.to_int(Os.env("R3D_HDR")) != 0 }
|
||||
if want == gvk_hdr_want { return }
|
||||
gvk_hdr_want = want
|
||||
if gvk_swap != 0 { gvk_swap_stale = true }
|
||||
}
|
||||
function gpu_hdr_active() -> bool { return gpu_kind == GPU_VK and gvk_hdr_on }
|
||||
function gvk_screen_fmt() -> int { if gvk_hdr_on { return GL_RGB10_A2 }; return GL_RGBA8 }
|
||||
|
||||
# what the picture is: graded in BT.709 around D65, highlights to 1000 nits, a 200-nit average
|
||||
function gvk_hdr_metadata() -> void {
|
||||
let md = bytes(VkHdrMetadataEXT_sizeof)
|
||||
Vk.zero(md, VkHdrMetadataEXT_sizeof)
|
||||
Vk.put_i32(md, VkHdrMetadataEXT_sType, VK_STRUCTURE_TYPE_HDR_METADATA_EXT)
|
||||
Vk.put_i32(md, VkHdrMetadataEXT_displayPrimaryRed + VkXYColorEXT_x, fl(0.64)); Vk.put_i32(md, VkHdrMetadataEXT_displayPrimaryRed + VkXYColorEXT_y, fl(0.33))
|
||||
Vk.put_i32(md, VkHdrMetadataEXT_displayPrimaryGreen + VkXYColorEXT_x, fl(0.30)); Vk.put_i32(md, VkHdrMetadataEXT_displayPrimaryGreen + VkXYColorEXT_y, fl(0.60))
|
||||
Vk.put_i32(md, VkHdrMetadataEXT_displayPrimaryBlue + VkXYColorEXT_x, fl(0.15)); Vk.put_i32(md, VkHdrMetadataEXT_displayPrimaryBlue + VkXYColorEXT_y, fl(0.06))
|
||||
Vk.put_i32(md, VkHdrMetadataEXT_whitePoint + VkXYColorEXT_x, fl(0.3127)); Vk.put_i32(md, VkHdrMetadataEXT_whitePoint + VkXYColorEXT_y, fl(0.3290))
|
||||
Vk.put_i32(md, VkHdrMetadataEXT_maxLuminance, fi(1000))
|
||||
Vk.put_i32(md, VkHdrMetadataEXT_minLuminance, fl(0.001))
|
||||
Vk.put_i32(md, VkHdrMetadataEXT_maxContentLightLevel, fi(1000))
|
||||
Vk.put_i32(md, VkHdrMetadataEXT_maxFrameAverageLightLevel, fi(200))
|
||||
let chains = bytes(8)
|
||||
Vk.put_i64(chains, 0, gvk_swap)
|
||||
Vk.set_hdr_metadata_ext(gvk_dev, 1, chains, md)
|
||||
}
|
||||
|
||||
function gvk_screen_make(w: int, h: int) -> bool {
|
||||
if gvk_vp == null {
|
||||
gvk_vp = words(4); gvk_sc = words(5); gvk_clear_rgba = words(4)
|
||||
|
|
@ -955,7 +992,7 @@ function gvk_screen_make(w: int, h: int) -> bool {
|
|||
gvk_screen_h = h
|
||||
if gvk_screen_color == 0 { gvk_screen_color = gvk_tex_new(); gvk_screen_depth = gvk_tex_new() }
|
||||
gvk_vp[0] = 0; gvk_vp[1] = 0; gvk_vp[2] = w; gvk_vp[3] = h
|
||||
return gvk_tex_storage(gvk_screen_color, false, GL_RGBA8, w, h, 1, false) and gvk_tex_storage(gvk_screen_depth, false, GL_DEPTH_COMPONENT32F, w, h, 1, false)
|
||||
return gvk_tex_storage(gvk_screen_color, false, gvk_screen_fmt(), w, h, 1, false) and gvk_tex_storage(gvk_screen_depth, false, GL_DEPTH_COMPONENT32F, w, h, 1, false)
|
||||
}
|
||||
|
||||
function gvk_frame_cb() -> pointer {
|
||||
|
|
@ -1292,6 +1329,8 @@ function gvk_present() -> void {
|
|||
# The screen as a binary PPM, top row first. The frame so far is finished first, then read back;
|
||||
# row 0 of the image is OpenGL's bottom row, so rows are written last to first, as gl_screenshot does.
|
||||
function gvk_screenshot(path: string) -> bool {
|
||||
# the screen image is PQ-encoded 10-bit while HDR is on, which an 8-bit PPM would misread
|
||||
if gvk_hdr_on { print("r3d: vulkan: screenshots are SDR only - turn HDR output off to take one"); return false }
|
||||
gvk_present()
|
||||
let w = gvk_screen_w
|
||||
let h = gvk_screen_h
|
||||
|
|
@ -1584,6 +1623,17 @@ function gvk_swap_make(w: int, h: int) -> bool {
|
|||
let c = Vk.get_i32(fmts, i * VkSurfaceFormatKHR_sizeof + VkSurfaceFormatKHR_colorSpace)
|
||||
if (f == VK_FORMAT_B8G8R8A8_UNORM or f == VK_FORMAT_R8G8B8A8_UNORM) and c == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR { fmt = f; cs = c }
|
||||
}
|
||||
var hdr = false
|
||||
if gvk_hdr_want and gvk_has_colorspace {
|
||||
for i in 0 .. nf {
|
||||
let f = Vk.get_i32(fmts, i * VkSurfaceFormatKHR_sizeof + VkSurfaceFormatKHR_format)
|
||||
let c = Vk.get_i32(fmts, i * VkSurfaceFormatKHR_sizeof + VkSurfaceFormatKHR_colorSpace)
|
||||
if f == VK_FORMAT_A2B10G10R10_UNORM_PACK32 and c == VK_COLOR_SPACE_HDR10_ST2084_EXT { fmt = f; cs = c; hdr = true }
|
||||
}
|
||||
if not hdr { print("r3d: vulkan: HDR output asked for, but this display offers no HDR10 swapchain") }
|
||||
}
|
||||
# the screen image carries the swapchain's depth of colour: remade when HDR comes or goes
|
||||
if hdr != gvk_hdr_on { gvk_hdr_on = hdr; gvk_screen_make(gvk_screen_w, gvk_screen_h) }
|
||||
Vk.put_i32(cnt, 0, 0)
|
||||
Vk.get_physical_device_surface_present_modes_khr(gvk_pd, gvk_surface, cnt, null)
|
||||
let nm = Vk.get_i32(cnt, 0)
|
||||
|
|
@ -1620,6 +1670,7 @@ function gvk_swap_make(w: int, h: int) -> bool {
|
|||
if r != VK_SUCCESS { return gvk_fail("vkCreateSwapchainKHR", r) }
|
||||
if gvk_swap != 0 { Vk.destroy_swapchain_khr(gvk_dev, gvk_swap, null) }
|
||||
gvk_swap = gvk_handle(out)
|
||||
if gvk_hdr_on and gvk_has_hdr_meta { gvk_hdr_metadata() }
|
||||
Vk.put_i32(cnt, 0, 0)
|
||||
Vk.get_swapchain_images_khr(gvk_dev, gvk_swap, cnt, null)
|
||||
gvk_swap_n = Vk.get_i32(cnt, 0)
|
||||
|
|
@ -1632,7 +1683,9 @@ function gvk_swap_make(w: int, h: int) -> bool {
|
|||
var mname = "fifo"
|
||||
if mode == VK_PRESENT_MODE_MAILBOX_KHR { mname = "mailbox" }
|
||||
if mode == VK_PRESENT_MODE_IMMEDIATE_KHR { mname = "immediate" }
|
||||
print(`r3d: vulkan swapchain {ew}x{eh}, {gvk_swap_n} images, {mname}`)
|
||||
var cname = "SDR"
|
||||
if gvk_hdr_on { cname = "HDR10" }
|
||||
print(`r3d: vulkan swapchain {ew}x{eh}, {gvk_swap_n} images, {mname}, {cname}`)
|
||||
return true
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
# supported Vulkan image format, so they are stored with four and expanded on upload.
|
||||
function gvk_format(ifmt: int) -> int {
|
||||
if ifmt == GL_RGBA8 or ifmt == GL_RGB8 { return VK_FORMAT_R8G8B8A8_UNORM }
|
||||
if ifmt == GL_RGB10_A2 { return VK_FORMAT_A2B10G10R10_UNORM_PACK32 } # the HDR10 screen and LDR image
|
||||
if ifmt == GL_SRGB8_ALPHA8 or ifmt == GL_SRGB8 { return VK_FORMAT_R8G8B8A8_SRGB }
|
||||
if ifmt == GL_R8 { return VK_FORMAT_R8_UNORM }
|
||||
if ifmt == GL_RG8 { return VK_FORMAT_R8G8_UNORM }
|
||||
|
|
|
|||
|
|
@ -46,6 +46,10 @@ var post_color_w: int = 0 # its size: the display's when DLSS upscaled
|
|||
var post_color_h: int = 0
|
||||
var post_color: int = 0 # the HDR colour the rest of post reads # the resolved depth, copied so passes can read it while drawing into the frame
|
||||
var post_p_sharp: int = 0
|
||||
var post_p_tone_hdr: int = 0 # the tonemap's HDR10 variant, made the first time HDR is on
|
||||
var post_ldr_hdr: bool = false # post_ldr was made for HDR10 output (10-bit)
|
||||
# the LDR image is 10-bit while the output is HDR10: PQ in 8 bits bands
|
||||
function post_ldr_fmt() -> int { if gpu_hdr_active() { return GL_RGB10_A2 }; return GL_RGBA8 }
|
||||
var post_sharpen: int = 0
|
||||
var post_grain: int = 0
|
||||
|
||||
|
|
@ -112,7 +116,8 @@ function post_init(w: int, h: int) -> void {
|
|||
post_ao = target_new(w, h, GL_RGBA16F, GL_RGBA, GL_HALF_FLOAT, false, GL_LINEAR)
|
||||
post_ao_blur = target_new(w, h, GL_RGBA16F, GL_RGBA, GL_HALF_FLOAT, false, GL_LINEAR)
|
||||
if post_p_ao == 0 { post_p_ao = r3d_program("fullscreen.vert", "ssgi.frag", ""); post_p_ao_blur = r3d_program("fullscreen.vert", "ssao_blur.frag", "") }
|
||||
post_ldr = target_new(w, h, GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, false, GL_LINEAR)
|
||||
post_ldr = target_new(w, h, post_ldr_fmt(), GL_RGBA, GL_UNSIGNED_BYTE, false, GL_LINEAR)
|
||||
post_ldr_hdr = gpu_hdr_active()
|
||||
post_depth_copy = target_new(w, h, GL_R8, GL_RED, GL_UNSIGNED_BYTE, true, GL_NEAREST)
|
||||
post_prev = target_new(w, h, GL_RGBA16F, GL_RGBA, GL_HALF_FLOAT, false, GL_LINEAR)
|
||||
post_scene = target_new(w, h, GL_RGBA16F, GL_RGBA, GL_HALF_FLOAT, false, GL_LINEAR)
|
||||
|
|
@ -291,26 +296,37 @@ function post_bloom_pass() -> void {
|
|||
}
|
||||
|
||||
function post_tonemap(color_tex: int) -> void {
|
||||
var prog = post_p_tone
|
||||
if gpu_hdr_active() {
|
||||
if post_p_tone_hdr == 0 { post_p_tone_hdr = r3d_program("fullscreen.vert", "tonemap.frag", "#define HDR10\n") }
|
||||
prog = post_p_tone_hdr
|
||||
}
|
||||
if post_ldr_hdr != gpu_hdr_active() {
|
||||
let w = post_ldr.w; let h = post_ldr.h
|
||||
target_free(post_ldr)
|
||||
post_ldr = target_new(w, h, post_ldr_fmt(), GL_RGBA, GL_UNSIGNED_BYTE, false, GL_LINEAR)
|
||||
post_ldr_hdr = gpu_hdr_active()
|
||||
}
|
||||
if post_auto { post_measure() }
|
||||
target_bind(post_ldr)
|
||||
gpu_depth_test(false)
|
||||
gpu_use_program(post_p_tone)
|
||||
r3d_bind_2d(post_p_tone, "u_hdr", 0, color_tex)
|
||||
r3d_bind_2d(post_p_tone, "u_bloom", 1, post_bloom[0].color)
|
||||
r3d_bind_2d(post_p_tone, "u_ao", 2, post_ao_blur.color)
|
||||
u_f(gpu_uniform(post_p_tone, "u_ao_strength"), post_ao_strength)
|
||||
u_f(gpu_uniform(post_p_tone, "u_gi_strength"), post_gi_strength)
|
||||
u_f(gpu_uniform(post_p_tone, "u_exposure"), post_exposure)
|
||||
gpu_use_program(prog)
|
||||
r3d_bind_2d(prog, "u_hdr", 0, color_tex)
|
||||
r3d_bind_2d(prog, "u_bloom", 1, post_bloom[0].color)
|
||||
r3d_bind_2d(prog, "u_ao", 2, post_ao_blur.color)
|
||||
u_f(gpu_uniform(prog, "u_ao_strength"), post_ao_strength)
|
||||
u_f(gpu_uniform(prog, "u_gi_strength"), post_gi_strength)
|
||||
u_f(gpu_uniform(prog, "u_exposure"), post_exposure)
|
||||
var auto = F_ZERO
|
||||
if post_auto and post_adapt_t != null { auto = F_ONE; r3d_bind_2d(post_p_tone, "u_adapt", 3, post_adapt_t[post_adapt_i].color) }
|
||||
u_f(gpu_uniform(post_p_tone, "u_auto"), auto)
|
||||
u_f(gpu_uniform(post_p_tone, "u_bloom_strength"), post_bloom_strength)
|
||||
u_f(gpu_uniform(post_p_tone, "u_vignette"), post_vignette)
|
||||
u_f(gpu_uniform(post_p_tone, "u_saturation"), post_saturation)
|
||||
u_f(gpu_uniform(post_p_tone, "u_contrast"), post_contrast)
|
||||
u_f3(gpu_uniform(post_p_tone, "u_wb"), fl(1.02), F_ONE, fl(0.97))
|
||||
u_f3(gpu_uniform(post_p_tone, "u_lift"), fl(0.004), fl(0.004), fl(0.012))
|
||||
u_f3(gpu_uniform(post_p_tone, "u_gain"), fl(0.99), fl(0.995), fl(1.0))
|
||||
if post_auto and post_adapt_t != null { auto = F_ONE; r3d_bind_2d(prog, "u_adapt", 3, post_adapt_t[post_adapt_i].color) }
|
||||
u_f(gpu_uniform(prog, "u_auto"), auto)
|
||||
u_f(gpu_uniform(prog, "u_bloom_strength"), post_bloom_strength)
|
||||
u_f(gpu_uniform(prog, "u_vignette"), post_vignette)
|
||||
u_f(gpu_uniform(prog, "u_saturation"), post_saturation)
|
||||
u_f(gpu_uniform(prog, "u_contrast"), post_contrast)
|
||||
u_f3(gpu_uniform(prog, "u_wb"), fl(1.02), F_ONE, fl(0.97))
|
||||
u_f3(gpu_uniform(prog, "u_lift"), fl(0.004), fl(0.004), fl(0.012))
|
||||
u_f3(gpu_uniform(prog, "u_gain"), fl(0.99), fl(0.995), fl(1.0))
|
||||
mesh_draw(post_fs)
|
||||
# sharpen + grain onto the screen
|
||||
gpu_fb_bind(gpu_screen_fb())
|
||||
|
|
@ -319,7 +335,10 @@ function post_tonemap(color_tex: int) -> void {
|
|||
r3d_bind_2d(post_p_sharp, "u_src", 0, post_ldr.color)
|
||||
u_f2(gpu_uniform(post_p_sharp, "u_texel"), fr(1, post_ldr.w), fr(1, post_ldr.h))
|
||||
u_f(gpu_uniform(post_p_sharp, "u_amount"), post_sharpen)
|
||||
u_f(gpu_uniform(post_p_sharp, "u_grain"), post_grain)
|
||||
# R3D_NOGRAIN=1: no film grain, so two frames of a still camera can be compared for what else moves
|
||||
var grain = post_grain
|
||||
if Os.has_env("R3D_NOGRAIN") { grain = F_ZERO }
|
||||
u_f(gpu_uniform(post_p_sharp, "u_grain"), grain)
|
||||
u_f(gpu_uniform(post_p_sharp, "u_time"), r3d_time)
|
||||
mesh_draw(post_fs)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -157,6 +157,7 @@ function r3d_resize() -> void {
|
|||
if water_refl != null { target_free(water_refl); water_refl = null }
|
||||
print(`r3d: resized to {gl_w}x{gl_h}`)
|
||||
}
|
||||
var r3d_cam_log: int = -1
|
||||
function r3d_frame(time: int) -> void {
|
||||
gpu_glcheck_after("the time between frames")
|
||||
outline_frame()
|
||||
|
|
@ -185,6 +186,12 @@ function r3d_frame(time: int) -> void {
|
|||
prof_gen_frame()
|
||||
prof_mark_start()
|
||||
cam_begin_frame(post_frame, gl_w, gl_h)
|
||||
# R3D_CAM_LOG=1: the camera each frame, in millimetres and thousandths of a radian - to tell a
|
||||
# camera that moves while the hiker stands still from a picture that shakes on its own
|
||||
if r3d_cam_log < 0 { r3d_cam_log = 0; if Os.has_env("R3D_CAM_LOG") { r3d_cam_log = 1 } }
|
||||
if r3d_cam_log == 1 {
|
||||
print(`cam {r3d_test_frame} pos {f_to_int(f_mul(cam_pos[0], fi(1000)))} {f_to_int(f_mul(cam_pos[1], fi(1000)))} {f_to_int(f_mul(cam_pos[2], fi(1000)))} yaw {f_to_int(f_mul(cam_yaw, fi(1000)))} pitch {f_to_int(f_mul(cam_pitch, fi(1000)))} jitter {f_to_int(f_mul(gsl_jitter_x, fi(1000000)))} {f_to_int(f_mul(gsl_jitter_y, fi(1000000)))} render {post_w}x{post_h} reset {gsl_reset} evalok {gsl_eval_ok} fresh {gsl_fresh}`)
|
||||
}
|
||||
# the height-field shadow rebakes as the light moves in steps (daylight), or with the sky yaw when there is no clock
|
||||
if (not day_on and ter_shadow_yaw != sky_yaw) or ter_shadow_gen != day_gen {
|
||||
ter_shadow_gen = day_gen
|
||||
|
|
|
|||
|
|
@ -6,6 +6,18 @@ in vec4 v_col;
|
|||
uniform sampler2D u_tex;
|
||||
uniform sampler2D u_font;
|
||||
out vec4 o_color;
|
||||
#ifdef HDR10
|
||||
// the interface is sRGB: drawn into an HDR10 frame it sits at paper white, as the picture's white does
|
||||
const float HDR_PAPER = 200.0;
|
||||
vec3 bt709_to_2020(vec3 c) {
|
||||
return mat3(0.6274, 0.0691, 0.0164, 0.3293, 0.9195, 0.0880, 0.0433, 0.0114, 0.8956) * c;
|
||||
}
|
||||
vec3 pq_encode(vec3 nits) {
|
||||
const float m1 = 0.1593017578125, m2 = 78.84375, c1 = 0.8359375, c2 = 18.8515625, c3 = 18.6875;
|
||||
vec3 yp = pow(clamp(nits / 10000.0, 0.0, 1.0), vec3(m1));
|
||||
return pow((c1 + c2 * yp) / (1.0 + c3 * yp), vec3(m2));
|
||||
}
|
||||
#endif
|
||||
void main() {
|
||||
float mode = floor((v_uv.y + 0.5) * 0.25);
|
||||
vec2 uv = vec2(v_uv.x, v_uv.y - 4.0 * mode);
|
||||
|
|
@ -17,4 +29,7 @@ void main() {
|
|||
vec4 t = texture(u_tex, uv);
|
||||
o_color = vec4(v_col.rgb * t.rgb, v_col.a * t.a);
|
||||
}
|
||||
#ifdef HDR10
|
||||
o_color.rgb = pq_encode(bt709_to_2020(pow(max(o_color.rgb, vec3(0.0)), vec3(2.2))) * HDR_PAPER);
|
||||
#endif
|
||||
}
|
||||
|
|
|
|||
Binary file not shown.
BIN
packages/ludic.render3d/shaders/spv/a4683767.frag.spv
Normal file
BIN
packages/ludic.render3d/shaders/spv/a4683767.frag.spv
Normal file
Binary file not shown.
BIN
packages/ludic.render3d/shaders/spv/a4683767.vert.spv
Normal file
BIN
packages/ludic.render3d/shaders/spv/a4683767.vert.spv
Normal file
Binary file not shown.
BIN
packages/ludic.render3d/shaders/spv/e86f78e8.frag.spv
Normal file
BIN
packages/ludic.render3d/shaders/spv/e86f78e8.frag.spv
Normal file
Binary file not shown.
BIN
packages/ludic.render3d/shaders/spv/e86f78e8.vert.spv
Normal file
BIN
packages/ludic.render3d/shaders/spv/e86f78e8.vert.spv
Normal file
Binary file not shown.
|
|
@ -577,6 +577,57 @@ T 725523d9 u_prefilter 8
|
|||
T 725523d9 u_shadow 9
|
||||
T 725523d9 u_tershadow 10
|
||||
T 725523d9 u_ts_height 11
|
||||
P a4683767 fullscreen.vert tonemap.frag #define HDR10;
|
||||
B a4683767 frag 1 852
|
||||
U a4683767 frag u_cascade_vp 0 mat4 5 64
|
||||
U a4683767 frag u_cascade_split 320 float 5 16
|
||||
U a4683767 frag u_cascade_range 400 float 5 16
|
||||
U a4683767 frag u_cascade_texel 480 float 5 16
|
||||
U a4683767 frag u_sun_dir 560 vec3 1 0
|
||||
U a4683767 frag u_sun_color 576 vec3 1 0
|
||||
U a4683767 frag u_cam_pos 592 vec3 1 0
|
||||
U a4683767 frag u_prefilter_levels 604 float 1 0
|
||||
U a4683767 frag u_fog_density 608 float 1 0
|
||||
U a4683767 frag u_fog_height_falloff 612 float 1 0
|
||||
U a4683767 frag u_fog_base 616 float 1 0
|
||||
U a4683767 frag u_clip_y 620 float 1 0
|
||||
U a4683767 frag u_spec_scale 624 float 1 0
|
||||
U a4683767 frag u_sky_rot 632 vec2 1 0
|
||||
U a4683767 frag u_ibl_scale 640 vec3 1 0
|
||||
U a4683767 frag u_daylight 652 float 1 0
|
||||
U a4683767 frag u_fire_pos 656 vec3 1 0
|
||||
U a4683767 frag u_fire_color 672 vec3 1 0
|
||||
U a4683767 frag u_hand_pos 688 vec3 1 0
|
||||
U a4683767 frag u_hand_color 704 vec3 1 0
|
||||
U a4683767 frag u_hand_dir 720 vec3 1 0
|
||||
U a4683767 frag u_hand_cone 732 float 1 0
|
||||
U a4683767 frag u_ts_origin 736 vec2 1 0
|
||||
U a4683767 frag u_ts_half 744 float 1 0
|
||||
U a4683767 frag u_ts_on 748 float 1 0
|
||||
U a4683767 frag u_force_cascade 752 int 1 0
|
||||
U a4683767 frag u_cloud_shadow 756 float 1 0
|
||||
U a4683767 frag u_time 760 float 1 0
|
||||
U a4683767 frag u_ao_strength 764 float 1 0
|
||||
U a4683767 frag u_gi_strength 768 float 1 0
|
||||
U a4683767 frag u_wb 784 vec3 1 0
|
||||
U a4683767 frag u_lift 800 vec3 1 0
|
||||
U a4683767 frag u_gain 816 vec3 1 0
|
||||
U a4683767 frag u_exposure 828 float 1 0
|
||||
U a4683767 frag u_auto 832 float 1 0
|
||||
U a4683767 frag u_bloom_strength 836 float 1 0
|
||||
U a4683767 frag u_vignette 840 float 1 0
|
||||
U a4683767 frag u_saturation 844 float 1 0
|
||||
U a4683767 frag u_contrast 848 float 1 0
|
||||
T a4683767 u_adapt 2
|
||||
T a4683767 u_ao 3
|
||||
T a4683767 u_bloom 4
|
||||
T a4683767 u_brdf 5
|
||||
T a4683767 u_hdr 6
|
||||
T a4683767 u_irradiance 7
|
||||
T a4683767 u_prefilter 8
|
||||
T a4683767 u_shadow 9
|
||||
T a4683767 u_tershadow 10
|
||||
T a4683767 u_ts_height 11
|
||||
P 7ed7de52 grass.vert model.frag #define FOLIAGE;#define BLADE;
|
||||
B 7ed7de52 vert 0 296
|
||||
U 7ed7de52 vert u_view 0 mat4 1 0
|
||||
|
|
@ -1808,6 +1859,49 @@ T 1b654972 u_shadow 6
|
|||
T 1b654972 u_tershadow 7
|
||||
T 1b654972 u_tex 8
|
||||
T 1b654972 u_ts_height 9
|
||||
P e86f78e8 overlay.vert overlay.frag #define HDR10;
|
||||
B e86f78e8 vert 0 8
|
||||
U e86f78e8 vert u_screen 0 vec2 1 0
|
||||
I e86f78e8 a_pos 0 vec2
|
||||
I e86f78e8 a_uv 1 vec2
|
||||
I e86f78e8 a_col 2 vec4
|
||||
B e86f78e8 frag 0 764
|
||||
U e86f78e8 frag u_cascade_vp 0 mat4 5 64
|
||||
U e86f78e8 frag u_cascade_split 320 float 5 16
|
||||
U e86f78e8 frag u_cascade_range 400 float 5 16
|
||||
U e86f78e8 frag u_cascade_texel 480 float 5 16
|
||||
U e86f78e8 frag u_sun_dir 560 vec3 1 0
|
||||
U e86f78e8 frag u_sun_color 576 vec3 1 0
|
||||
U e86f78e8 frag u_cam_pos 592 vec3 1 0
|
||||
U e86f78e8 frag u_prefilter_levels 604 float 1 0
|
||||
U e86f78e8 frag u_fog_density 608 float 1 0
|
||||
U e86f78e8 frag u_fog_height_falloff 612 float 1 0
|
||||
U e86f78e8 frag u_fog_base 616 float 1 0
|
||||
U e86f78e8 frag u_clip_y 620 float 1 0
|
||||
U e86f78e8 frag u_spec_scale 624 float 1 0
|
||||
U e86f78e8 frag u_sky_rot 632 vec2 1 0
|
||||
U e86f78e8 frag u_ibl_scale 640 vec3 1 0
|
||||
U e86f78e8 frag u_daylight 652 float 1 0
|
||||
U e86f78e8 frag u_fire_pos 656 vec3 1 0
|
||||
U e86f78e8 frag u_fire_color 672 vec3 1 0
|
||||
U e86f78e8 frag u_hand_pos 688 vec3 1 0
|
||||
U e86f78e8 frag u_hand_color 704 vec3 1 0
|
||||
U e86f78e8 frag u_hand_dir 720 vec3 1 0
|
||||
U e86f78e8 frag u_hand_cone 732 float 1 0
|
||||
U e86f78e8 frag u_ts_origin 736 vec2 1 0
|
||||
U e86f78e8 frag u_ts_half 744 float 1 0
|
||||
U e86f78e8 frag u_ts_on 748 float 1 0
|
||||
U e86f78e8 frag u_force_cascade 752 int 1 0
|
||||
U e86f78e8 frag u_cloud_shadow 756 float 1 0
|
||||
U e86f78e8 frag u_time 760 float 1 0
|
||||
T e86f78e8 u_brdf 2
|
||||
T e86f78e8 u_font 3
|
||||
T e86f78e8 u_irradiance 4
|
||||
T e86f78e8 u_prefilter 5
|
||||
T e86f78e8 u_shadow 6
|
||||
T e86f78e8 u_tershadow 7
|
||||
T e86f78e8 u_tex 8
|
||||
T e86f78e8 u_ts_height 9
|
||||
P 1fde9cd2 skin.vert model.frag
|
||||
B 1fde9cd2 vert 0 3344
|
||||
U 1fde9cd2 vert u_model 0 mat4 1 0
|
||||
|
|
|
|||
|
|
@ -21,6 +21,19 @@ vec3 aces(vec3 x) {
|
|||
return clamp((x * (a * x + b)) / (x * (c * x + d) + e), 0.0, 1.0);
|
||||
}
|
||||
float hash(vec2 p) { return fract(sin(dot(p, vec2(12.9898, 78.233))) * 43758.5453); }
|
||||
#ifdef HDR10
|
||||
// HDR10 output: nits in BT.2020 primaries, encoded with the SMPTE ST 2084 (PQ) curve
|
||||
const float HDR_PAPER = 200.0; // nits the SDR picture's white sits at
|
||||
const float HDR_PEAK = 1000.0; // nits the brightest highlight rolls on to
|
||||
vec3 bt709_to_2020(vec3 c) {
|
||||
return mat3(0.6274, 0.0691, 0.0164, 0.3293, 0.9195, 0.0880, 0.0433, 0.0114, 0.8956) * c;
|
||||
}
|
||||
vec3 pq_encode(vec3 nits) {
|
||||
const float m1 = 0.1593017578125, m2 = 78.84375, c1 = 0.8359375, c2 = 18.8515625, c3 = 18.6875;
|
||||
vec3 yp = pow(clamp(nits / 10000.0, 0.0, 1.0), vec3(m1));
|
||||
return pow((c1 + c2 * yp) / (1.0 + c3 * yp), vec3(m2));
|
||||
}
|
||||
#endif
|
||||
void main() {
|
||||
vec3 hdr = sane(texture(u_hdr, v_uv).rgb);
|
||||
vec4 gi = texture(u_ao, v_uv);
|
||||
|
|
@ -34,14 +47,25 @@ void main() {
|
|||
// filmic contrast around mid grey in log space
|
||||
c = max(c, vec3(0.0));
|
||||
c = pow(c / 0.18, vec3(u_contrast)) * 0.18;
|
||||
vec3 pre = c; // what ACES is about to roll off, kept for HDR highlights
|
||||
c = aces(c);
|
||||
// lift / gain grade in display space
|
||||
c = c * u_gain + u_lift * (1.0 - c);
|
||||
float lum = dot(c, vec3(0.2126, 0.7152, 0.0722));
|
||||
c = mix(vec3(lum), c, u_saturation);
|
||||
vec2 q = v_uv * 2.0 - 1.0;
|
||||
c *= 1.0 - u_vignette * dot(q, q) * 0.5;
|
||||
float vig = 1.0 - u_vignette * dot(q, q) * 0.5;
|
||||
c *= vig;
|
||||
#ifdef HDR10
|
||||
// up to paper white the SDR picture as it is; past it, what ACES clipped runs on to the peak
|
||||
vec3 hi = max(pre - 1.0, vec3(0.0));
|
||||
vec3 nits = (c + hi / (hi + 1.0) * (HDR_PEAK / HDR_PAPER - 1.0) * vig) * HDR_PAPER;
|
||||
vec3 e = pq_encode(bt709_to_2020(nits));
|
||||
e += (hash(gl_FragCoord.xy) - 0.5) / 1023.0;
|
||||
o_color = vec4(e, 1.0);
|
||||
#else
|
||||
c = pow(c, vec3(1.0 / 2.2));
|
||||
c += (hash(gl_FragCoord.xy) - 0.5) / 255.0;
|
||||
o_color = vec4(c, 1.0);
|
||||
#endif
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ fullscreen.vert|ssgi.frag|
|
|||
fullscreen.vert|ternormal.frag|
|
||||
fullscreen.vert|tershadow.frag|#define NOISE_ONLY;
|
||||
fullscreen.vert|tonemap.frag|
|
||||
fullscreen.vert|tonemap.frag|#define HDR10;
|
||||
grass.vert|model.frag|#define FOLIAGE;#define BLADE;
|
||||
grass.vert|model.frag|#define FOLIAGE;#define BLADE;#define TILES;
|
||||
impostor.vert|impostor.frag|
|
||||
|
|
@ -37,6 +38,7 @@ model.vert|shadow.frag|#define SHADOW_PASS;
|
|||
model.vert|shadow.frag|#define SHADOW_PASS;#define WIND;
|
||||
model.vert|shadow.frag|#define SHADOW_PASS;#define WIND;#define ALPHA_TEST;
|
||||
overlay.vert|overlay.frag|
|
||||
overlay.vert|overlay.frag|#define HDR10;
|
||||
skin.vert|model.frag|
|
||||
skin.vert|model.frag|#define ALPHA_TEST;
|
||||
skin.vert|outline.frag|#define OUTLINE;
|
||||
|
|
|
|||
|
|
@ -243,6 +243,7 @@ var gsl_jitter_x: int = 0 # float bits, NDC offsets the projection c
|
|||
var gsl_jitter_y: int = 0
|
||||
var gsl_jpx: int = 0 # float bits, the same in pixels
|
||||
var gsl_jpy: int = 0
|
||||
var gsl_eval_ok: bool = true # the last evaluate worked: only then is the next frame jittered
|
||||
|
||||
# R3D_DLSS=0..4 overrides the setting, for a headless take
|
||||
function r3d_dlss(mode: int) -> void {
|
||||
|
|
@ -273,6 +274,13 @@ function gsl_fill_options(w: int, h: int) -> void {
|
|||
Vk.put_i32(gsl_opts, 48, F_ONE) # preExposure
|
||||
Vk.put_i32(gsl_opts, 52, F_ONE) # exposureScale
|
||||
Vk.put_i32(gsl_opts, 56, 1) # colorBuffersHDR eTrue
|
||||
# The model: preset K (the transformer NVIDIA calls its best image quality) in every mode. The
|
||||
# defaults put Performance on preset M, which on an RTX 3070 Ti at 4K evaluated in 18 ms against
|
||||
# K's 2.8 - slower than no DLSS at all (33 fps against 41; with K, 60). R3D_DLSS_PRESET=<n>
|
||||
# (sl::DLSSPreset: 11 K, 12 L, 13 M) sets every mode's, to measure them against each other.
|
||||
var preset = 11
|
||||
if Os.has_env("R3D_DLSS_PRESET") { preset = Text.to_int(Os.env("R3D_DLSS_PRESET")) }
|
||||
if preset > 0 { for f in 0 .. 6 { Vk.put_i32(gsl_opts, 60 + f * 4, preset) } } # dlaa, quality, balanced, performance, ultra performance, ultra quality
|
||||
}
|
||||
|
||||
# the render size for the display's size and the mode, asked once per change
|
||||
|
|
@ -311,7 +319,10 @@ function gsl_halton(i: int, b: int) -> int {
|
|||
# cam_begin_frame: this frame's sub-pixel offset, before the camera builds its matrices
|
||||
function gsl_jitter_frame() -> void {
|
||||
gsl_jitter_x = F_ZERO; gsl_jitter_y = F_ZERO; gsl_jpx = F_ZERO; gsl_jpy = F_ZERO
|
||||
if not r3d_dlss_live() or post_w <= 0 or post_h <= 0 { return }
|
||||
# a jittered frame nobody resolves shakes on screen however still the camera is: jitter only while
|
||||
# this frame holds a DLSS token and the last evaluate worked. (Not gsl_fresh: gsl_frame_start has
|
||||
# already taken the token and cleared it by the time the camera asks, which turned jitter off.)
|
||||
if not r3d_dlss_live() or not gsl_eval_ok or gsl_token == null or post_w <= 0 or post_h <= 0 { return }
|
||||
# DLSS wants at least 8 x (display / render)^2 phases; 32 covers performance mode
|
||||
let i = (gsl_frame_n % 32) + 1
|
||||
gsl_jpx = f_sub(gsl_halton(i, 2), F_HALF)
|
||||
|
|
@ -400,9 +411,16 @@ function gsl_constants() -> void {
|
|||
m4_inverse(p2c, c2p)
|
||||
gsl_put_m4(k, 224, c2p) # clipToPrevClip
|
||||
gsl_put_m4(k, 288, p2c) # prevClipToClip
|
||||
# the sample's offset from the pixel centre, in the flipped image
|
||||
Vk.put_i32(k, 352, f_neg(gsl_jpx))
|
||||
Vk.put_i32(k, 356, gsl_jpy)
|
||||
# the sample's offset from the pixel centre, in the image Streamline sees: row 0 at the top, so the
|
||||
# vertical offset flips with it. With jy unflipped DLSS resolved the ground into concentric
|
||||
# rings; with jx flipped too, thin stems doubled sideways (PC shots, 2026-09-15).
|
||||
var jx = f_neg(gsl_jpx)
|
||||
var jy = f_neg(gsl_jpy)
|
||||
# R3D_DLSS_JX / R3D_DLSS_JY = -1 flip a sign, to check the convention against the picture
|
||||
if Os.has_env("R3D_DLSS_JX") and Text.to_int(Os.env("R3D_DLSS_JX")) < 0 { jx = f_neg(jx) }
|
||||
if Os.has_env("R3D_DLSS_JY") and Text.to_int(Os.env("R3D_DLSS_JY")) < 0 { jy = f_neg(jy) }
|
||||
Vk.put_i32(k, 352, jx)
|
||||
Vk.put_i32(k, 356, jy)
|
||||
Vk.put_i32(k, 360, F_ONE) # mvecScale
|
||||
Vk.put_i32(k, 364, F_ONE)
|
||||
gsl_put_v3(k, 376, cam_pos)
|
||||
|
|
@ -438,7 +456,8 @@ function gsl_targets() -> void {
|
|||
# the LDR image the tonemap writes follows the upscaled size
|
||||
if post_ldr.w != gl_w or post_ldr.h != gl_h {
|
||||
target_free(post_ldr)
|
||||
post_ldr = target_new(gl_w, gl_h, GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, false, GL_LINEAR)
|
||||
post_ldr = target_new(gl_w, gl_h, post_ldr_fmt(), GL_RGBA, GL_UNSIGNED_BYTE, false, GL_LINEAR)
|
||||
post_ldr_hdr = gpu_hdr_active()
|
||||
}
|
||||
if gsl_res == null { gsl_res = gsl_struct(4 * 112); gsl_tags = gsl_struct(4 * 64); gsl_inputs = bytes(8) }
|
||||
}
|
||||
|
|
@ -470,6 +489,7 @@ function gsl_dlss_eval() -> int {
|
|||
gsl_reset = false
|
||||
# Streamline records its own pipeline and descriptors into the command buffer; nothing needs
|
||||
# forgetting, because every gvk_draw binds its pipeline, view and set afresh
|
||||
gsl_eval_ok = r == 0
|
||||
if r != 0 {
|
||||
if not gsl_said { print(`r3d: streamline: DLSS evaluate failed ({r}); drawing without it`); gsl_said = true }
|
||||
post_color_w = post_w; post_color_h = post_h
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue