Merge commit '883ea8d' into lang/foundations
This commit is contained in:
commit
253fe397f4
4 changed files with 211 additions and 12 deletions
133
packages/ludic.render3d/bake_parts.ludic
Normal file
133
packages/ludic.render3d/bake_parts.ludic
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
# bake_parts.ludic — the renderer's deterministic work as bytes a build-time bake can keep, and the same
|
||||
# textures made from those bytes at run time instead of being computed again. Each pair is: *_bytes
|
||||
# (run the work as the renderer always has, read the result back) and *_from_bytes (make the textures
|
||||
# as the work would have, then fill them). A bundle is a word count of parts, each a word length and
|
||||
# its bytes, so a reader can check it before using it.
|
||||
|
||||
# the parts of a bundle packed after a header of their lengths
|
||||
@alloc_ok("a bake: made once at build time, or read once at load")
|
||||
function bake_pack(render3d_st: mut Render3dState, texs: words) -> []byte {
|
||||
var total = 4 + len(texs) * 4
|
||||
for i in 0 .. len(texs) { total += gpu_tex_byte_size(render3d_st, texs[i]) }
|
||||
let out = buffer(total)
|
||||
let p = data_of(out)
|
||||
Vk.put_i32(p, 0, len(texs))
|
||||
var at = 4 + len(texs) * 4
|
||||
for i in 0 .. len(texs) {
|
||||
let n = gpu_tex_byte_size(render3d_st, texs[i])
|
||||
Vk.put_i32(p, 4 + i * 4, n)
|
||||
gpu_tex_read_all(render3d_st, texs[i], mem_off(p, at))
|
||||
at += n
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
# the textures filled from a bundle bake_pack made of textures of the same shapes; false when it does not fit
|
||||
function bake_unpack(render3d_st: mut Render3dState, data: []byte, texs: words) -> bool {
|
||||
let p = data_of(data)
|
||||
if len(data) < 4 or Vk.get_i32(p, 0) != len(texs) { return false }
|
||||
var at = 4 + len(texs) * 4
|
||||
for i in 0 .. len(texs) {
|
||||
let n = Vk.get_i32(p, 4 + i * 4)
|
||||
if n != gpu_tex_byte_size(render3d_st, texs[i]) or at + n > len(data) { return false }
|
||||
if not gpu_tex_write_all(render3d_st, texs[i], mem_off(p, at)) { return false }
|
||||
at += n
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@alloc_ok("a bake's list of textures, once")
|
||||
function bake_list2(a: int, b: int) -> words {
|
||||
let w = words(2)
|
||||
w[0] = a; w[1] = b
|
||||
return w
|
||||
}
|
||||
@alloc_ok("a bake's list of textures, once")
|
||||
function bake_list3(a: int, b: int, c: int) -> words {
|
||||
let w = words(3)
|
||||
w[0] = a; w[1] = b; w[2] = c
|
||||
return w
|
||||
}
|
||||
|
||||
# ---- an impostor: its albedo and normal atlases (level 0; mips are made after) ----
|
||||
function impostor_bytes(render3d_st: mut Render3dState, im: Impostor) -> []byte { return bake_pack(render3d_st, bake_list2(im.albedo, im.normal)) }
|
||||
|
||||
# an impostor with the bake's shape (model, tiles and tile size as impostor_bake takes them) from bytes
|
||||
function impostor_from_bytes(render3d_st: mut Render3dState, model: Model, tiles: int, tw: int, th: int, data: []byte) -> Impostor {
|
||||
let im = new Impostor
|
||||
im.tiles = tiles; im.radius = model.radius * 1.02; im.height = model.height
|
||||
im.model = model; im.tw = tw; im.th = th; im.flower = render3d_st.sc_bake_flower
|
||||
impostor_fill(render3d_st, im, data)
|
||||
return im
|
||||
}
|
||||
|
||||
# an impostor's atlases made and filled from bytes (at a load, or when a fog opens past its cards)
|
||||
function impostor_fill(render3d_st: mut Render3dState, im: Impostor, data: []byte) -> bool {
|
||||
let was = render3d_st.gvk_tag
|
||||
render3d_st.gvk_tag = VKM_IMPOSTOR
|
||||
let aw = im.tiles * im.tw
|
||||
im.albedo = tex_target(render3d_st, aw, im.th, GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, GL_LINEAR)
|
||||
im.normal = tex_target(render3d_st, aw, im.th, GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, GL_LINEAR)
|
||||
let ok = bake_unpack(render3d_st, data, bake_list2(im.albedo, im.normal))
|
||||
for t in 0 .. 2 {
|
||||
var tex = im.albedo
|
||||
if t == 1 { tex = im.normal }
|
||||
gpu_tex_bind(render3d_st, GPU_TEX2D, tex)
|
||||
gpu_tex_param(render3d_st, GPU_TEX2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR)
|
||||
gpu_tex_mips(render3d_st, GPU_TEX2D)
|
||||
}
|
||||
render3d_st.gvk_tag = was
|
||||
return ok
|
||||
}
|
||||
|
||||
# ---- the terrain's height-field sun shadow: R32F lit height, RG16F distance and cloud mask ----
|
||||
function terrain_shadow_bytes(render3d_st: mut Render3dState) -> []byte {
|
||||
terrain_bake_shadow(render3d_st)
|
||||
return bake_pack(render3d_st, bake_list2(render3d_st.ter_shadow_tex, render3d_st.ter_shadow_aux))
|
||||
}
|
||||
function terrain_shadow_from_bytes(render3d_st: mut Render3dState, data: []byte) -> bool {
|
||||
if render3d_st.ter_shadow_tex == 0 { render3d_st.ter_shadow_tex = tex_target(render3d_st, TERRAIN_SHADOW_RES, TERRAIN_SHADOW_RES, GL_R32F, GL_RED, GL_FLOAT, GL_LINEAR) }
|
||||
if render3d_st.ter_shadow_aux == 0 { render3d_st.ter_shadow_aux = tex_target(render3d_st, TERRAIN_SHADOW_RES, TERRAIN_SHADOW_RES, GL_RG16F, GL_RG, GL_FLOAT, GL_LINEAR) }
|
||||
let ok = bake_unpack(render3d_st, data, bake_list2(render3d_st.ter_shadow_tex, render3d_st.ter_shadow_aux))
|
||||
if ok { render3d_st.ter_shadow_yaw = render3d_st.sky_yaw }
|
||||
return ok
|
||||
}
|
||||
|
||||
# ---- the sky's image-based lighting: irradiance, the prefiltered levels, the BRDF table ----
|
||||
# for the HDRI already loaded and the yaw already set (the yaw is part of what a bake is keyed on)
|
||||
function sky_ibl_bytes(render3d_st: mut Render3dState) -> []byte {
|
||||
sky_precompute(render3d_st)
|
||||
return bake_pack(render3d_st, bake_list3(render3d_st.sky_irradiance, render3d_st.sky_prefilter, render3d_st.sky_brdf))
|
||||
}
|
||||
|
||||
# the IBL set made from a bake of the same HDRI, yaw and prefilter width; false (and nothing kept) when
|
||||
# it does not fit, and the caller computes it
|
||||
function sky_ibl_from_bytes(render3d_st: mut Render3dState, data: []byte) -> bool {
|
||||
sky_ibl_make(render3d_st)
|
||||
if not bake_unpack(render3d_st, data, bake_list3(render3d_st.sky_irradiance, render3d_st.sky_prefilter, render3d_st.sky_brdf)) { return false }
|
||||
render3d_st.sky_baked = true
|
||||
render3d_st.sky_baked_yaw = render3d_st.sky_yaw
|
||||
return true
|
||||
}
|
||||
|
||||
# ---- reading a bake's file (ludic.base's format; render3d cannot import ludic.base) ----
|
||||
# "LBAK" | u32 format 1 | u32 version | u32 payload offset | u64 inputs hash | u64 payload length | key
|
||||
const BAKE_MAGIC: int = 1262567756 # "LBAK", little-endian
|
||||
|
||||
# the payload of the bake at `path` when its key and version are these, else null (missing, stale, torn)
|
||||
@alloc_ok("a bake read once at load")
|
||||
function r3d_baked_read(path: string, key: string, version: int) -> []byte {
|
||||
if not Fs.exists(path) { return null }
|
||||
let all = Fs.read_bytes(path)
|
||||
if all == null or len(all) < 32 { return null }
|
||||
let p = data_of(all)
|
||||
if Vk.get_i32(p, 0) != BAKE_MAGIC or Vk.get_i32(p, 4) != 1 or Vk.get_i32(p, 8) != version { return null }
|
||||
let off = Vk.get_i32(p, 12)
|
||||
let n = Vk.get_i32(p, 24)
|
||||
if off < 32 or off + n > len(all) { return null }
|
||||
for i in 0 .. len(key) { if off <= 32 + i or all[32 + i] != key[i] { return null } }
|
||||
let out = buffer(n)
|
||||
mem_copy(data_of(out), mem_off(p, off), n)
|
||||
free(all)
|
||||
return out
|
||||
}
|
||||
61
packages/ludic.render3d/gpu_vk_readback.ludic
Normal file
61
packages/ludic.render3d/gpu_vk_readback.ludic
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
# gpu_vk_readback.ludic — a texture's level 0, every layer, read back as the bytes it stores, and put back
|
||||
# from them: what a build-time bake keeps of a texture the renderer made (bake_parts.ludic).
|
||||
|
||||
# the bytes a texture's level 0 takes, every layer, in the format it is stored in
|
||||
function gpu_tex_byte_size(render3d_st: Render3dState, tex: int) -> int {
|
||||
let ifmt = render3d_st.gvk_tex_glfmt[tex]
|
||||
return render3d_st.gvk_tex_dims_w[tex] * render3d_st.gvk_tex_dims_h[tex] * render3d_st.gvk_tex_layers[tex] * gvk_channels(ifmt) * gvk_channel_bytes(ifmt)
|
||||
}
|
||||
|
||||
# level 0 of layer `layer` into `out` (w * h * channels * bytes), as stored
|
||||
function gvk_tex_read_layer(render3d_st: mut Render3dState, tex: int, layer: int, out: pointer) -> bool {
|
||||
let ifmt = render3d_st.gvk_tex_glfmt[tex]
|
||||
let w = render3d_st.gvk_tex_dims_w[tex]
|
||||
let h = render3d_st.gvk_tex_dims_h[tex]
|
||||
let layers = render3d_st.gvk_tex_layers[tex]
|
||||
let n = w * h * gvk_channels(ifmt) * gvk_channel_bytes(ifmt)
|
||||
let src = gvk_staging(render3d_st, n, VK_BUFFER_USAGE_TRANSFER_DST_BIT)
|
||||
if src == null { return false }
|
||||
let image = render3d_st.gvk_tex_image[tex]
|
||||
let levels = render3d_st.gvk_tex_levels[tex]
|
||||
let cb = gvk_once_begin(render3d_st)
|
||||
gvk_barrier(render3d_st, cb, image, false, 0, levels, layers, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL)
|
||||
let bic = gvk_tmp(render3d_st, VkBufferImageCopy_sizeof)
|
||||
Vk.zero(bic, VkBufferImageCopy_sizeof)
|
||||
Vk.put_i32(bic, VkBufferImageCopy_imageSubresource + VkImageSubresourceLayers_aspectMask, VK_IMAGE_ASPECT_COLOR_BIT)
|
||||
Vk.put_i32(bic, VkBufferImageCopy_imageSubresource + VkImageSubresourceLayers_baseArrayLayer, layer)
|
||||
Vk.put_i32(bic, VkBufferImageCopy_imageSubresource + VkImageSubresourceLayers_layerCount, 1)
|
||||
Vk.put_i32(bic, VkBufferImageCopy_imageExtent + VkExtent3D_width, w)
|
||||
Vk.put_i32(bic, VkBufferImageCopy_imageExtent + VkExtent3D_height, h)
|
||||
Vk.put_i32(bic, VkBufferImageCopy_imageExtent + VkExtent3D_depth, 1)
|
||||
Vk.cmd_copy_image_to_buffer(cb, image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, render3d_st.gvk_st_buf, 1, bic)
|
||||
gvk_barrier(render3d_st, cb, image, false, 0, levels, layers, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)
|
||||
let ok = gvk_once_end(render3d_st, cb)
|
||||
if ok { mem_copy(out, src, n) }
|
||||
gvk_staging_free(render3d_st)
|
||||
return ok
|
||||
}
|
||||
|
||||
# every layer's level 0 into `out` (gpu_tex_byte_size bytes), layer after layer
|
||||
function gpu_tex_read_all(render3d_st: mut Render3dState, tex: int, out: pointer) -> bool {
|
||||
let layers = render3d_st.gvk_tex_layers[tex]
|
||||
let per = gpu_tex_byte_size(render3d_st, tex) / max(layers, 1)
|
||||
for l in 0 .. layers { if not gvk_tex_read_layer(render3d_st, tex, l, mem_off(out, l * per)) { return false } }
|
||||
return true
|
||||
}
|
||||
|
||||
# level 0 of every layer written from bytes read back by gpu_tex_read_all; the texture's storage must
|
||||
# already be what it was (same format, size and layers). Mips, where it has them, are the caller's
|
||||
function gpu_tex_write_all(render3d_st: mut Render3dState, tex: int, data: pointer) -> bool {
|
||||
let ifmt = render3d_st.gvk_tex_glfmt[tex]
|
||||
let c = gvk_channels(ifmt)
|
||||
let b = gvk_channel_bytes(ifmt)
|
||||
var fmt = GL_RGBA
|
||||
if c == 1 { fmt = GL_RED }
|
||||
if c == 2 { fmt = GL_RG }
|
||||
if c == 3 { fmt = GL_RGB }
|
||||
var ty = GL_UNSIGNED_BYTE
|
||||
if b == 2 { ty = GL_HALF_FLOAT }
|
||||
if b == 4 { ty = GL_FLOAT }
|
||||
return gvk_tex_upload(render3d_st, tex, ifmt, render3d_st.gvk_tex_dims_w[tex], render3d_st.gvk_tex_dims_h[tex], render3d_st.gvk_tex_layers[tex], fmt, ty, data)
|
||||
}
|
||||
|
|
@ -14,6 +14,7 @@ import "vkmem_report.ludic"
|
|||
import "gpu_vk_res.ludic"
|
||||
import "gpu_vk_layers.ludic"
|
||||
import "gpu_vk_bc.ludic"
|
||||
import "gpu_vk_readback.ludic"
|
||||
import "texture_dds.ludic"
|
||||
import "prof.ludic"
|
||||
import "drawstats.ludic"
|
||||
|
|
@ -51,4 +52,5 @@ import "grass_gpu.ludic"
|
|||
import "water.ludic"
|
||||
import "streamline.ludic"
|
||||
import "render.ludic"
|
||||
import "bake_parts.ludic"
|
||||
import "safe_api.ludic"
|
||||
|
|
|
|||
|
|
@ -92,20 +92,31 @@ function sky_set_quality(render3d_st: mut Render3dState, w: int) -> void {
|
|||
|
||||
function sky_precompute(render3d_st: mut Render3dState) -> void {
|
||||
gpu_depth_test(render3d_st, false)
|
||||
sky_ibl_make(render3d_st)
|
||||
if render3d_st.sky_p_irr == 0 { render3d_st.sky_p_irr = r3d_program(render3d_st, "fullscreen.vert", "ibl_irradiance.frag", ""); render3d_st.sky_p_pre = r3d_program(render3d_st, "fullscreen.vert", "ibl_prefilter.frag", ""); render3d_st.sky_p_brdf = r3d_program(render3d_st, "fullscreen.vert", "ibl_brdf.frag", "") }
|
||||
sky_convolve(render3d_st, render3d_st.sky_p_irr, render3d_st.sky_irradiance, -1, 128, 64, 0.0)
|
||||
for l in 0 .. SKY_PREFILTER_LEVELS {
|
||||
sky_convolve(render3d_st, render3d_st.sky_p_pre, render3d_st.sky_prefilter, l, render3d_st.sky_prefilter_w, render3d_st.sky_prefilter_w / 2, float(l) / float(SKY_PREFILTER_LEVELS - 1))
|
||||
}
|
||||
sky_convolve(render3d_st, render3d_st.sky_p_brdf, render3d_st.sky_brdf, -1, 256, 256, 0.0)
|
||||
render3d_st.sky_baked = true
|
||||
render3d_st.sky_baked_yaw = render3d_st.sky_yaw
|
||||
gpu_check(render3d_st, "sky precompute")
|
||||
}
|
||||
|
||||
# the IBL set's textures, made empty (the last ones let go): what sky_precompute convolves into, and
|
||||
# what sky_ibl_from_bytes fills from a bake
|
||||
function sky_ibl_make(render3d_st: mut Render3dState) -> void {
|
||||
if render3d_st.sky_irradiance != 0 {
|
||||
gpu_tex_free(render3d_st, render3d_st.sky_irradiance)
|
||||
gpu_tex_free(render3d_st, render3d_st.sky_prefilter)
|
||||
gpu_tex_free(render3d_st, render3d_st.sky_brdf)
|
||||
}
|
||||
# irradiance: 128 x 64 equirect
|
||||
if render3d_st.sky_p_irr == 0 { render3d_st.sky_p_irr = r3d_program(render3d_st, "fullscreen.vert", "ibl_irradiance.frag", ""); render3d_st.sky_p_pre = r3d_program(render3d_st, "fullscreen.vert", "ibl_prefilter.frag", ""); render3d_st.sky_p_brdf = r3d_program(render3d_st, "fullscreen.vert", "ibl_brdf.frag", "") }
|
||||
let p_irr = render3d_st.sky_p_irr
|
||||
render3d_st.sky_irradiance = tex_target(render3d_st, 128, 64, GL_RGB16F, GL_RGB, GL_FLOAT, GL_LINEAR)
|
||||
gpu_tex_bind(render3d_st, GPU_TEX2D, render3d_st.sky_irradiance)
|
||||
gpu_tex_param(render3d_st, GPU_TEX2D, GL_TEXTURE_WRAP_S, GL_REPEAT)
|
||||
sky_convolve(render3d_st, p_irr, render3d_st.sky_irradiance, -1, 128, 64, 0.0)
|
||||
# prefiltered specular: 6 roughness levels, sky_prefilter_w x half each, as a 2D array
|
||||
let p_pre = render3d_st.sky_p_pre
|
||||
render3d_st.sky_prefilter = gpu_tex_new(render3d_st)
|
||||
gpu_tex_bind(render3d_st, GPU_TEX2D_ARRAY, render3d_st.sky_prefilter)
|
||||
gpu_tex_image3d(render3d_st, GL_RGB16F, render3d_st.sky_prefilter_w, render3d_st.sky_prefilter_w / 2, SKY_PREFILTER_LEVELS, GL_RGB, GL_FLOAT, null)
|
||||
|
|
@ -113,16 +124,8 @@ function sky_precompute(render3d_st: mut Render3dState) -> void {
|
|||
gpu_tex_param(render3d_st, GPU_TEX2D_ARRAY, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)
|
||||
gpu_tex_param(render3d_st, GPU_TEX2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
|
||||
gpu_tex_param(render3d_st, GPU_TEX2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
|
||||
for l in 0 .. SKY_PREFILTER_LEVELS {
|
||||
sky_convolve(render3d_st, p_pre, render3d_st.sky_prefilter, l, render3d_st.sky_prefilter_w, render3d_st.sky_prefilter_w / 2, float(l) / float(SKY_PREFILTER_LEVELS - 1))
|
||||
}
|
||||
# BRDF LUT
|
||||
let p_brdf = render3d_st.sky_p_brdf
|
||||
render3d_st.sky_brdf = tex_target(render3d_st, 256, 256, GL_RG16F, GL_RG, GL_FLOAT, GL_LINEAR)
|
||||
sky_convolve(render3d_st, p_brdf, render3d_st.sky_brdf, -1, 256, 256, 0.0)
|
||||
render3d_st.sky_baked = true
|
||||
render3d_st.sky_baked_yaw = render3d_st.sky_yaw
|
||||
gpu_check(render3d_st, "sky precompute")
|
||||
}
|
||||
|
||||
# bind the IBL set + sun for a lit program (units 12..14)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue