diff --git a/packages/ludic.render3d/gpu.ludic b/packages/ludic.render3d/gpu.ludic index 6633aa6f..03eff7da 100644 --- a/packages/ludic.render3d/gpu.ludic +++ b/packages/ludic.render3d/gpu.ludic @@ -448,6 +448,8 @@ function gpu_mesh_record(m: Mesh, index: int, comps: int, type: int, stride: int let o = index * GPU_ATTR_W var st = stride if st == 0 { st = comps * gpu_type_bytes(type) } + # re-pointing an attribute at another buffer keeps the layout; changing its shape does not + if m.attrs[o + 1] != comps or m.attrs[o + 2] != type or m.attrs[o + 3] != st or m.attrs[o + 4] != offset or m.attrs[o + 5] != gpu_b(normalized) or m.attrs[o + 6] != gpu_b(inst) or (m.attrs[o] == m.attrs[0]) != (m.cur_buf == m.attrs[0]) { m.vk_layout = 0 } m.attrs[o] = m.cur_buf; m.attrs[o + 1] = comps; m.attrs[o + 2] = type; m.attrs[o + 3] = st m.attrs[o + 4] = offset; m.attrs[o + 5] = gpu_b(normalized); m.attrs[o + 6] = gpu_b(inst) if index + 1 > m.n_attrs { m.n_attrs = index + 1 } diff --git a/packages/ludic.render3d/gpu_vk_draw.ludic b/packages/ludic.render3d/gpu_vk_draw.ludic index 288370e8..7df701d2 100644 --- a/packages/ludic.render3d/gpu_vk_draw.ludic +++ b/packages/ludic.render3d/gpu_vk_draw.ludic @@ -371,10 +371,27 @@ var gvk_ublk_v: []bytes = null # per program: the vertex stage's block as var gvk_ublk_f: []bytes = null var gvk_prog_tex: []words = null # per program: the texture bound to each manifest sampler var gvk_prog_unit: []words = null # per program: the unit + 1 each sampler was pointed at with u_i +var gvk_prog_dirty: []int = null # per program: its blocks or textures changed since its last set +var gvk_set_last: []long = null # per program: the descriptor set its last draw used +var gvk_set_frame: []int = null # per program: the frame that set belongs to +var gvk_set_tex: []words = null # per program: the textures that set was filled with + +function gvk_same(a: pointer, b: pointer, n: int) -> bool { + var i = 0 + while i < n { if Vk.get_i32(a, i) != Vk.get_i32(b, i) { return false }; i += 4 } + return true +} function gvk_uniform_blocks(p: int) -> void { - if gvk_ublk_v == null { gvk_ublk_v = new []bytes; gvk_ublk_f = new []bytes; gvk_prog_tex = new []words; gvk_prog_unit = new []words } - while len(gvk_ublk_v) <= p { push(gvk_ublk_v, null); push(gvk_ublk_f, null); push(gvk_prog_tex, null); push(gvk_prog_unit, null) } + if gvk_ublk_v == null { + gvk_ublk_v = new []bytes; gvk_ublk_f = new []bytes; gvk_prog_tex = new []words; gvk_prog_unit = new []words + gvk_prog_dirty = new []int; gvk_set_last = new []long; gvk_set_frame = new []int; gvk_set_tex = new []words + } + let zero: long = 0 + while len(gvk_ublk_v) <= p { + push(gvk_ublk_v, null); push(gvk_ublk_f, null); push(gvk_prog_tex, null); push(gvk_prog_unit, null) + push(gvk_prog_dirty, 1); push(gvk_set_last, zero); push(gvk_set_frame, 0); push(gvk_set_tex, null) + } let v = gvk_prog_var[p] if v == null or gvk_ublk_v[p] != null or gvk_prog_tex[p] != null { return } if v.vblock > 0 { let b = bytes(v.vblock); Vk.zero(b, v.vblock); gvk_ublk_v[p] = b } @@ -385,6 +402,9 @@ function gvk_uniform_blocks(p: int) -> void { for i in 0 .. nt { t[i] = 0; u[i] = 0 } gvk_prog_tex[p] = t gvk_prog_unit[p] = u + let st = words(nt) + for i in 0 .. nt { st[i] = -1 } + gvk_set_tex[p] = st } function gvk_uniform(p: int, name: string) -> int { @@ -407,7 +427,7 @@ function gvk_u_set(loc: int, src: pointer, size: int, n: int) -> void { gvk_uniform_blocks(p) if loc % 4096 >= 2048 { let si = loc % 4096 - 2048 - if si < len(v.t_name) { gvk_prog_unit[p][si] = Vk.get_i32(src, 0) + 1 } + if si < len(v.t_name) and gvk_prog_unit[p][si] != Vk.get_i32(src, 0) + 1 { gvk_prog_unit[p][si] = Vk.get_i32(src, 0) + 1; gvk_prog_dirty[p] = 1 } return } let name = v.u_name[loc % 4096] @@ -423,7 +443,11 @@ function gvk_u_set(loc: int, src: pointer, size: int, n: int) -> void { if count > v.u_count[j] { count = v.u_count[j] } for k in 0 .. count { let at = v.u_off[j] + k * stride - if at + size <= cap { mem_copy(mem_off(blk, at), mem_off(src, k * size), size) } + # the same value again (most per-draw uniforms repeat) leaves the program's set reusable + if at + size <= cap and not gvk_same(mem_off(blk, at), mem_off(src, k * size), size) { + mem_copy(mem_off(blk, at), mem_off(src, k * size), size) + gvk_prog_dirty[p] = 1 + } } } } @@ -434,7 +458,7 @@ function gvk_bind_texture(p: int, name: string, tex: int) -> void { let v = gvk_prog_var[p] if v == null { return } gvk_uniform_blocks(p) - for i in 0 .. len(v.t_name) { if v.t_name[i] == name { gvk_prog_tex[p][i] = tex } } + for i in 0 .. len(v.t_name) { if v.t_name[i] == name and gvk_prog_tex[p][i] != tex { gvk_prog_tex[p][i] = tex; gvk_prog_dirty[p] = 1 } } } # ---- per-frame uniform ring and descriptor sets ------------------------------------------- @@ -497,6 +521,16 @@ function gvk_draw_set(p: int, tx: words, tx_w: int, tx_cap: int) -> long { let zero: long = 0 let v = gvk_prog_var[p] gvk_uniform_blocks(p) + let nt0 = len(v.t_name) + # the textures this draw reads, resolved the way the set below binds them + var same_tex = true + for t in 0 .. nt0 { + var tt = gvk_prog_tex[p][t] + let u1 = gvk_prog_unit[p][t] + if tt <= 0 and u1 > 0 and u1 <= 32 and gpu_unit_2d != null { tt = gpu_unit_2d[u1 - 1] } + if gvk_set_tex[p][t] != tt { same_tex = false; gvk_set_tex[p][t] = tt } + } + if same_tex and gvk_prog_dirty[p] == 0 and gvk_set_frame[p] == gvk_frame_no and gvk_set_last[p] != 0 { return gvk_set_last[p] } let dsai = bytes(VkDescriptorSetAllocateInfo_sizeof) Vk.zero(dsai, VkDescriptorSetAllocateInfo_sizeof) Vk.put_i32(dsai, VkDescriptorSetAllocateInfo_sType, VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO) @@ -548,7 +582,7 @@ function gvk_draw_set(p: int, tx: words, tx_w: int, tx_cap: int) -> long { var smp: long = 0 let o = tex * tx_w if tex != gvk_white and tex < tx_cap { - smp = gvk_sampler(tx[o + 5], tx[o + 6], tx[o + 7], tx[o + 8], tx[o + 9], tx[o + 11]) + smp = gvk_tex_sampler(tex, tx[o + 5], tx[o + 6], tx[o + 7], tx[o + 8], tx[o + 9], tx[o + 11]) } else { smp = gvk_sampler(GL_LINEAR, GL_LINEAR, GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE, 0, 0) } @@ -564,6 +598,9 @@ function gvk_draw_set(p: int, tx: words, tx_w: int, tx_cap: int) -> long { nw += 1 } if nw > 0 { Vk.update_descriptor_sets(gvk_dev, nw, writes, 0, null) } + gvk_set_last[p] = set + gvk_set_frame[p] = gvk_frame_no + gvk_prog_dirty[p] = 0 return set } @@ -832,7 +869,7 @@ function gvk_draw(p: int, m: Mesh, st: GvkState, first: int, count: int, instanc gvk_pass_begin(rec, rec_o) let cb = gvk_cb let samples = 1 - let pipe = gvk_pipeline(p, m, st, gvk_pass_ncolor, gvk_pass_cfmt, gvk_pass_dfmt, samples) + let pipe = gvk_pipeline_fast(p, m, st, gvk_pass_ncolor, gvk_pass_cfmt, gvk_pass_dfmt, samples) if pipe == 0 { return } var t1: long = 0 if prof { t1 = gl_now_us(); gvk_us_pipe = gvk_us_pipe + (t1 - t0) } @@ -1305,3 +1342,69 @@ function gvk_prof_frame() -> void { gvk_us_pipe = zero; gvk_us_set = zero; gvk_us_draw = zero gvk_n_draws = 0; gvk_n_flush = 0; gvk_prof_frames = 0 } + +# ---- the pipeline cache, by integers ----------------------------------------------------------- +# gvk_pipeline builds and keys pipelines by a string; a draw only needs to find one it already has. +# A mesh carries an interned layout id (its recorded layout, buffers named by order), the render +# state packs into one int, and the pass's formats into another; the program's last hit is tried +# first, then the entries it owns. +var gvk_layout_keys: []string = null +var gvk_pc_prog: []int = null +var gvk_pc_layout: []int = null +var gvk_pc_state: []int = null +var gvk_pc_bias: []int = null +var gvk_pc_pass: []int = null +var gvk_pc_pipe: []long = null +var gvk_pc_last: words = null + +function gvk_layout_id(m: Mesh) -> int { + if m == null or m.attrs == null { return 1 } + if m.vk_layout > 0 { return m.vk_layout } + let key = gvk_layout_key(m) + if gvk_layout_keys == null { gvk_layout_keys = new []string } + var id = 0 + for i in 0 .. len(gvk_layout_keys) { if id == 0 and gvk_layout_keys[i] == key { id = i + 2 } } + if id == 0 { push(gvk_layout_keys, key); id = len(gvk_layout_keys) + 1 } + m.vk_layout = id + return id +} +function gvk_blend_index(f: int) -> int { + if f == GL_ZERO { return 0 } + if f == GL_ONE { return 1 } + if f == GL_SRC_ALPHA { return 2 } + if f == GL_ONE_MINUS_SRC_ALPHA { return 3 } + if f == GL_DST_ALPHA { return 4 } + if f == GL_ONE_MINUS_DST_ALPHA { return 5 } + if f == GL_SRC_COLOR { return 6 } + if f == GL_ONE_MINUS_SRC_COLOR { return 7 } + return 8 +} +function gvk_pipeline_fast(p: int, m: Mesh, st: GvkState, n_color: int, color_fmt: int, depth_fmt: int, samples: int) -> long { + let layout = gvk_layout_id(m) + var state = st.depth_test | (st.depth_write << 1) | (st.blend << 2) | (st.cull << 3) | (st.color_write << 4) | (st.a2c << 5) | (st.bias << 6) | (st.wireframe << 7) + state = state | ((st.depth_func & 15) << 8) | ((st.cull_face & 15) << 12) | (gvk_blend_index(st.blend_src) << 16) | (gvk_blend_index(st.blend_dst) << 20) + let bias = st.bias_factor * 31 + st.bias_units + let pass = ((n_color * 1000 + color_fmt) * 1000 + depth_fmt) * 8 + samples + if gvk_pc_prog == null { + gvk_pc_prog = new []int; gvk_pc_layout = new []int; gvk_pc_state = new []int; gvk_pc_bias = new []int + gvk_pc_pass = new []int; gvk_pc_pipe = new []long + gvk_pc_last = words(4096) + for i in 0 .. 4096 { gvk_pc_last[i] = -1 } + } + if p < 4096 { + let k = gvk_pc_last[p] + if k >= 0 and gvk_pc_layout[k] == layout and gvk_pc_state[k] == state and gvk_pc_pass[k] == pass and gvk_pc_bias[k] == bias { return gvk_pc_pipe[k] } + } + for k in 0 .. len(gvk_pc_prog) { + if gvk_pc_prog[k] == p and gvk_pc_layout[k] == layout and gvk_pc_state[k] == state and gvk_pc_pass[k] == pass and gvk_pc_bias[k] == bias { + if p < 4096 { gvk_pc_last[p] = k } + return gvk_pc_pipe[k] + } + } + let pipe = gvk_pipeline(p, m, st, n_color, color_fmt, depth_fmt, samples) + if pipe == 0 { return pipe } + push(gvk_pc_prog, p); push(gvk_pc_layout, layout); push(gvk_pc_state, state) + push(gvk_pc_bias, bias); push(gvk_pc_pass, pass); push(gvk_pc_pipe, pipe) + if p < 4096 { gvk_pc_last[p] = len(gvk_pc_prog) - 1 } + return pipe +} diff --git a/packages/ludic.render3d/gpu_vk_res.ludic b/packages/ludic.render3d/gpu_vk_res.ludic index 6e7534e7..44422113 100644 --- a/packages/ludic.render3d/gpu_vk_res.ludic +++ b/packages/ludic.render3d/gpu_vk_res.ludic @@ -55,6 +55,8 @@ var gvk_tex_dims_h: []int = null var gvk_tex_glfmt: []int = null # the format the renderer asked for (gpu.ludic's vocabulary) var gvk_tex_array: []int = null # 1 for an array image var gvk_tex_gen: []int = null # bumped whenever the image behind a handle is replaced +var gvk_tex_smp_sig: []int = null # the sampling parameters the cached sampler was made for +var gvk_tex_smp: []long = null var gvk_unpack_swap: bool = false # GL_UNPACK_SWAP_BYTES: 16-bit PNG samples arrive big-endian function gvk_tex_new() -> int { @@ -64,8 +66,10 @@ function gvk_tex_new() -> int { gvk_tex_levels = new []int; gvk_tex_layers = new []int; gvk_tex_vkfmt = new []int gvk_tex_dims_w = new []int; gvk_tex_dims_h = new []int gvk_tex_glfmt = new []int; gvk_tex_array = new []int; gvk_tex_gen = new []int + gvk_tex_smp_sig = new []int; gvk_tex_smp = new []long push(gvk_tex_dims_w, 0); push(gvk_tex_dims_h, 0) push(gvk_tex_glfmt, 0); push(gvk_tex_array, 0); push(gvk_tex_gen, 0) + push(gvk_tex_smp_sig, 0); push(gvk_tex_smp, zero) # handle 0 is "no texture", as it is on OpenGL push(gvk_tex_image, zero); push(gvk_tex_view, zero); push(gvk_tex_mem, zero) push(gvk_tex_levels, 0); push(gvk_tex_layers, 0); push(gvk_tex_vkfmt, 0) @@ -74,6 +78,7 @@ function gvk_tex_new() -> int { push(gvk_tex_levels, 0); push(gvk_tex_layers, 0); push(gvk_tex_vkfmt, 0) push(gvk_tex_dims_w, 0); push(gvk_tex_dims_h, 0) push(gvk_tex_glfmt, 0); push(gvk_tex_array, 0); push(gvk_tex_gen, 0) + push(gvk_tex_smp_sig, 0); push(gvk_tex_smp, zero) return len(gvk_tex_image) - 1 } @@ -446,6 +451,19 @@ function gvk_compare_op(f: int) -> int { if f == GL_ALWAYS { return VK_COMPARE_OP_ALWAYS } return VK_COMPARE_OP_LESS_OR_EQUAL } +# the sampler for texture tex's parameters, from the texture's own one-entry cache when they have +# not changed since it last asked - a draw asks for every texture it binds +function gvk_tex_sampler(tex: int, min_f: int, mag_f: int, wrap_s: int, wrap_t: int, compare: int, aniso: int) -> long { + var sig = min_f * 31 + mag_f + sig = sig * 31 + wrap_s + sig = sig * 31 + wrap_t + sig = sig * 31 + compare + sig = (sig * 31 + aniso) | 1 + if tex > 0 and tex < len(gvk_tex_smp_sig) and gvk_tex_smp_sig[tex] == sig { return gvk_tex_smp[tex] } + let s = gvk_sampler(min_f, mag_f, wrap_s, wrap_t, compare, aniso) + if tex > 0 and tex < len(gvk_tex_smp_sig) { gvk_tex_smp_sig[tex] = sig; gvk_tex_smp[tex] = s } + return s +} function gvk_sampler(min_f: int, mag_f: int, wrap_s: int, wrap_t: int, compare: int, aniso: int) -> long { let key = `{min_f}/{mag_f}/{wrap_s}/{wrap_t}/{compare}/{aniso}` if gvk_smp_keys == null { gvk_smp_keys = new []string; gvk_smp = new []long } diff --git a/packages/ludic.render3d/mesh.ludic b/packages/ludic.render3d/mesh.ludic index e387fcee..9b1d8800 100644 --- a/packages/ludic.render3d/mesh.ludic +++ b/packages/ludic.render3d/mesh.ludic @@ -16,7 +16,8 @@ property Mesh { vbufs: words, # every vertex buffer the mesh owns, freed with it n_vbufs: int = 0, cur_buf: int = 0, # the buffer the next attribute reads (while building) - ibuf: int = 0 # the instance buffer attached last + ibuf: int = 0, # the instance buffer attached last + vk_layout: int = 0 # the Vulkan backend's interned id for the recorded layout (0: not yet) } function mesh_draw(m: Mesh) -> void { gpu_draw_mesh(m) }