wip(render3d): one frame recording while one draws on Vulkan (double-buffered ring and pools), R3D_VK_LABELS, grass measuring switches (R3D_NOGRASS, R3D_BLADES, R3D_GRASS_S0/D0)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
4e2087d6eb
commit
f815f3e3cf
8 changed files with 100 additions and 19 deletions
|
|
@ -191,7 +191,13 @@ export state Render3dState {
|
|||
gvk_frame_fence: bytes = null # the presented frame still on the GPU (one frame in flight)
|
||||
gvk_frame_pending: bool = false
|
||||
gvk_frame_pending_cb: pointer = null
|
||||
gvk_inflight: int = -1 # -1 until asked: 1 leaves the presented frame in flight
|
||||
gvk_frame_pending_no: int = -1 # which frame it is (gvk_frame_no when it was submitted)
|
||||
gvk_dpools: []long = null # a descriptor pool per frame slot (frame_no & 1)
|
||||
gvk_ring_end: int = 0 # the end of this frame's half of the ring
|
||||
gvk_retired_frame: []int = null # the last frame that read each retired buffer
|
||||
gvk_labels: bool = false # R3D_VK_LABELS: each profiled pass is a debug label (Metal System Trace)
|
||||
gvk_inflight: int = -1
|
||||
gvk_label_depth: int = 0 # -1 until asked: 1 leaves the presented frame in flight
|
||||
gvk_has_hqr: bool = false
|
||||
gvk_ts_period: float = 0.0 # float bits: nanoseconds per timestamp tick
|
||||
gvk_qpool: long = 0
|
||||
|
|
@ -582,6 +588,8 @@ export state Render3dState {
|
|||
sc_cast_dy: float = 0.0
|
||||
sc_cast_k: float = 0.0
|
||||
sc_skip_blade: bool = false
|
||||
sc_skip_grass: bool = false
|
||||
grass_force: bool = false # R3D_BLADES: the blades on whatever the game set
|
||||
sc_skip_card: bool = false
|
||||
cb_state: int = 12345
|
||||
shadow_res: int = 2048
|
||||
|
|
|
|||
|
|
@ -85,6 +85,9 @@ function gvk_init(render3d_st: mut Render3dState) -> 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, surf_ext); n_iext += 1
|
||||
}
|
||||
# R3D_VK_LABELS: name each pass for a GPU capture (MoltenVK makes them Metal debug groups)
|
||||
render3d_st.gvk_labels = r3d_env_has(render3d_st, "R3D_VK_LABELS") and gvk_ext_in(iexts, nie, VK_EXT_DEBUG_UTILS_EXTENSION_NAME) and Vk.has("vkCmdBeginDebugUtilsLabelEXT") == 1
|
||||
if render3d_st.gvk_labels { Vk.put_ptr(iext_names, n_iext * 8, VK_EXT_DEBUG_UTILS_EXTENSION_NAME); n_iext += 1 }
|
||||
# HDR output: an instance only lists the HDR colour spaces when it asks for them
|
||||
render3d_st.gvk_has_colorspace = render3d_st.gvk_has_surface and gvk_ext_in(iexts, nie, VK_EXT_SWAPCHAIN_COLOR_SPACE_EXTENSION_NAME)
|
||||
if render3d_st.gvk_has_colorspace { Vk.put_ptr(iext_names, n_iext * 8, VK_EXT_SWAPCHAIN_COLOR_SPACE_EXTENSION_NAME); n_iext += 1 }
|
||||
|
|
@ -544,8 +547,11 @@ function gvk_once_end(render3d_st: mut Render3dState, cb: pointer) -> bool {
|
|||
# CPU while the GPU draws: waiting at the present serialised the two, which cost MoltenVK a third
|
||||
# of its frame rate against OpenGL. Anything that would reuse what that frame reads - the next
|
||||
# frame's command buffer and ring, a one-off submit, a descriptor pool reset, a swapchain rebuild -
|
||||
# waits for it first (gvk_frame_wait), and a buffer it reads is busy (gvk_buf_busy). On by default
|
||||
# on macOS; R3D_VK_INFLIGHT=0/1 says otherwise.
|
||||
# waits for it first (gvk_frame_wait), and a buffer it reads is busy (gvk_buf_busy). The frame's
|
||||
# uniform ring and descriptor pool are double-buffered by frame_no & 1, so the next frame records
|
||||
# while this one draws: waiting at the next frame's start left the GPU idle for the whole of its
|
||||
# recording and MoltenVK's encoding (a third of a frame). On by default on macOS; R3D_VK_INFLIGHT=0/1
|
||||
# says otherwise.
|
||||
function gvk_inflight_on(render3d_st: mut Render3dState) -> bool {
|
||||
if render3d_st.gvk_inflight < 0 {
|
||||
render3d_st.gvk_inflight = 0
|
||||
|
|
@ -571,6 +577,7 @@ function gvk_frame_submit(render3d_st: mut Render3dState, cb: pointer) -> bool {
|
|||
if r != VK_SUCCESS { return gvk_fail(render3d_st, "vkQueueSubmit", r) }
|
||||
render3d_st.gvk_frame_pending = true
|
||||
render3d_st.gvk_frame_pending_cb = cb
|
||||
render3d_st.gvk_frame_pending_no = render3d_st.gvk_frame_no
|
||||
return true
|
||||
}
|
||||
|
||||
|
|
@ -583,7 +590,7 @@ function gvk_frame_wait(render3d_st: mut Render3dState) -> void {
|
|||
Vk.free_command_buffers(render3d_st.gvk_dev, render3d_st.gvk_pool, 1, cbs)
|
||||
render3d_st.gvk_frame_pending = false
|
||||
render3d_st.gvk_frame_pending_cb = null
|
||||
gvk_retire_flush(render3d_st)
|
||||
gvk_retire_upto(render3d_st, render3d_st.gvk_frame_pending_no)
|
||||
}
|
||||
|
||||
# ---- teardown -----------------------------------------------------------------------------
|
||||
|
|
@ -640,3 +647,30 @@ function gvk_query_result(render3d_st: Render3dState, id: int, out: words) -> bo
|
|||
out[0] = int(float(ticks) * render3d_st.gvk_ts_period)
|
||||
return true
|
||||
}
|
||||
|
||||
# ---- debug labels (R3D_VK_LABELS) ------------------------------------------------------------
|
||||
function gvk_label_begin(render3d_st: mut Render3dState, name: pointer) -> void {
|
||||
# only inside a frame: a pass the load profiles comes before the frame's pools exist
|
||||
if render3d_st.gpu_kind != GPU_VK or render3d_st.gvk_cb == null { return }
|
||||
let cb = render3d_st.gvk_cb
|
||||
let li = bytes(VkDebugUtilsLabelEXT_sizeof)
|
||||
Vk.zero(li, VkDebugUtilsLabelEXT_sizeof)
|
||||
Vk.put_i32(li, VkDebugUtilsLabelEXT_sType, VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT)
|
||||
Vk.put_ptr(li, VkDebugUtilsLabelEXT_pLabelName, name)
|
||||
Vk.cmd_begin_debug_utils_label_ext(cb, li)
|
||||
render3d_st.gvk_label_depth += 1
|
||||
}
|
||||
function gvk_label_end(render3d_st: mut Render3dState) -> void {
|
||||
if render3d_st.gpu_kind != GPU_VK or render3d_st.gvk_label_depth <= 0 or render3d_st.gvk_cb == null { return }
|
||||
Vk.cmd_end_debug_utils_label_ext(render3d_st.gvk_cb)
|
||||
render3d_st.gvk_label_depth -= 1
|
||||
}
|
||||
# the frame's command buffer ends with every label it opened closed (called where it ends: a
|
||||
# pointer compares by what it points at, so once_end cannot ask which buffer it was handed)
|
||||
function gvk_labels_close(render3d_st: mut Render3dState) -> void {
|
||||
if render3d_st.gvk_cb == null { render3d_st.gvk_label_depth = 0; return }
|
||||
while render3d_st.gvk_label_depth > 0 {
|
||||
Vk.cmd_end_debug_utils_label_ext(render3d_st.gvk_cb)
|
||||
render3d_st.gvk_label_depth -= 1
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -650,7 +650,8 @@ function gvk_frame_init(render3d_st: mut Render3dState) -> bool {
|
|||
if (counts & VK_SAMPLE_COUNT_2_BIT) != 0 { render3d_st.gvk_msaa_max = 2 }
|
||||
if (counts & VK_SAMPLE_COUNT_4_BIT) != 0 { render3d_st.gvk_msaa_max = 4 }
|
||||
render3d_st.gvk_ring_buf = gvk_buf_new(render3d_st)
|
||||
if not gvk_buf_reserve(render3d_st, render3d_st.gvk_ring_buf, GVK_RING_BYTES) { return false }
|
||||
# one half per frame slot: the cached sets bind this one buffer, and each draw's offset says the half
|
||||
if not gvk_buf_reserve(render3d_st, render3d_st.gvk_ring_buf, GVK_RING_BYTES * 2) { return false }
|
||||
let sizes = bytes(VkDescriptorPoolSize_sizeof * 3)
|
||||
Vk.put_i32(sizes, VkDescriptorPoolSize_type, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER)
|
||||
Vk.put_i32(sizes, VkDescriptorPoolSize_descriptorCount, 32768)
|
||||
|
|
@ -665,9 +666,13 @@ function gvk_frame_init(render3d_st: mut Render3dState) -> bool {
|
|||
Vk.put_i32(dpci, VkDescriptorPoolCreateInfo_poolSizeCount, 3)
|
||||
Vk.put_ptr(dpci, VkDescriptorPoolCreateInfo_pPoolSizes, sizes)
|
||||
let out = bytes(8)
|
||||
let r = Vk.create_descriptor_pool(render3d_st.gvk_dev, dpci, null, out)
|
||||
if r != VK_SUCCESS { return gvk_fail(render3d_st, "vkCreateDescriptorPool", r) }
|
||||
render3d_st.gvk_dpool = gvk_handle(out)
|
||||
render3d_st.gvk_dpools = new []long
|
||||
for k in 0 .. 2 {
|
||||
let r = Vk.create_descriptor_pool(render3d_st.gvk_dev, dpci, null, out)
|
||||
if r != VK_SUCCESS { return gvk_fail(render3d_st, "vkCreateDescriptorPool", r) }
|
||||
push(render3d_st.gvk_dpools, gvk_handle(out))
|
||||
}
|
||||
render3d_st.gvk_dpool = render3d_st.gvk_dpools[0]
|
||||
if not gvk_kpool_make(render3d_st) { return false }
|
||||
render3d_st.gvk_white = gvk_tex_new(render3d_st)
|
||||
let px = bytes(4)
|
||||
|
|
@ -676,14 +681,17 @@ function gvk_frame_init(render3d_st: mut Render3dState) -> bool {
|
|||
}
|
||||
|
||||
function gvk_frame_reset(render3d_st: mut Render3dState) -> void {
|
||||
render3d_st.gvk_ring_off = 0
|
||||
let slot = render3d_st.gvk_frame_no & 1
|
||||
render3d_st.gvk_ring_off = slot * GVK_RING_BYTES
|
||||
render3d_st.gvk_ring_end = (slot + 1) * GVK_RING_BYTES
|
||||
render3d_st.gvk_dpool = render3d_st.gvk_dpools[slot]
|
||||
Vk.reset_descriptor_pool(render3d_st.gvk_dev, render3d_st.gvk_dpool, 0)
|
||||
}
|
||||
|
||||
# a block into the ring; its offset, or -1 when the frame has used the whole ring
|
||||
function gvk_ring_put(render3d_st: mut Render3dState, blk: pointer, n: int) -> int {
|
||||
let at = (render3d_st.gvk_ring_off + render3d_st.gvk_ring_align - 1) / render3d_st.gvk_ring_align * render3d_st.gvk_ring_align
|
||||
if at + n > GVK_RING_BYTES { return -1 }
|
||||
if at + n > render3d_st.gvk_ring_end { return -1 }
|
||||
mem_copy(mem_off(render3d_st.gvk_buf_map[render3d_st.gvk_ring_buf], at), blk, n)
|
||||
render3d_st.gvk_ring_off = at + n
|
||||
return at
|
||||
|
|
@ -954,7 +962,8 @@ function gvk_screen_make(render3d_st: mut Render3dState, w: int, h: int) -> bool
|
|||
|
||||
function gvk_frame_cb(render3d_st: mut Render3dState) -> pointer {
|
||||
if render3d_st.gvk_cb == null {
|
||||
gvk_frame_wait(render3d_st)
|
||||
# the frame in flight uses the other half of the ring and the other pool, unless it has this slot
|
||||
if render3d_st.gvk_frame_pending and (render3d_st.gvk_frame_pending_no & 1) == (render3d_st.gvk_frame_no & 1) { gvk_frame_wait(render3d_st) }
|
||||
gvk_frame_reset(render3d_st)
|
||||
render3d_st.gvk_cb = gvk_once_begin(render3d_st)
|
||||
}
|
||||
|
|
@ -1270,6 +1279,7 @@ function gvk_flush(render3d_st: mut Render3dState) -> void {
|
|||
if render3d_st.gvk_cb == null { return }
|
||||
render3d_st.gvk_n_flush += 1
|
||||
gvk_pass_end(render3d_st)
|
||||
gvk_labels_close(render3d_st)
|
||||
gvk_once_end(render3d_st, render3d_st.gvk_cb)
|
||||
render3d_st.gvk_cb = null
|
||||
render3d_st.gvk_frame_no += 1
|
||||
|
|
@ -1290,6 +1300,7 @@ function gvk_present(render3d_st: mut Render3dState) -> void {
|
|||
}
|
||||
if render3d_st.gvk_cb == null { return }
|
||||
gvk_pass_end(render3d_st)
|
||||
gvk_labels_close(render3d_st)
|
||||
gvk_once_end(render3d_st, render3d_st.gvk_cb)
|
||||
render3d_st.gvk_cb = null
|
||||
render3d_st.gvk_frame_no += 1
|
||||
|
|
@ -1669,6 +1680,7 @@ function gvk_swap_make(render3d_st: mut Render3dState, w: int, h: int) -> bool {
|
|||
function gvk_present_window(render3d_st: mut Render3dState) -> void {
|
||||
let cb = gvk_frame_cb(render3d_st)
|
||||
gvk_pass_end(render3d_st)
|
||||
gvk_labels_close(render3d_st)
|
||||
# a covered window is not drawn to: its layer would hold the frame for a drawable the compositor
|
||||
# hands back once a second. The frame still runs, paced at about 60 Hz, so the game keeps time.
|
||||
if win_visible() == 0 {
|
||||
|
|
|
|||
|
|
@ -560,6 +560,9 @@ function gvk_buf_release(render3d_st: mut Render3dState, b: int) -> void {
|
|||
if gvk_buf_busy(render3d_st, b) {
|
||||
# a draw recorded this frame (or the frame in flight) still reads it: destroy it once the frame has been submitted
|
||||
push(render3d_st.gvk_retired_buf, render3d_st.gvk_buf[b]); push(render3d_st.gvk_retired_mem, render3d_st.gvk_buf_mem[b])
|
||||
if render3d_st.gvk_retired_frame == null { render3d_st.gvk_retired_frame = new []int }
|
||||
while len(render3d_st.gvk_retired_frame) < len(render3d_st.gvk_retired_buf) - 1 { push(render3d_st.gvk_retired_frame, 0) }
|
||||
push(render3d_st.gvk_retired_frame, render3d_st.gvk_buf_used[b])
|
||||
} else {
|
||||
Vk.destroy_buffer(render3d_st.gvk_dev, render3d_st.gvk_buf[b], null)
|
||||
gvk_mem_free(render3d_st, gvk_mem_id(render3d_st.gvk_buf_mem[b]))
|
||||
|
|
@ -569,18 +572,32 @@ function gvk_buf_release(render3d_st: mut Render3dState, b: int) -> void {
|
|||
# read by a draw recorded this frame, or by the presented frame still on the GPU
|
||||
function gvk_buf_busy(render3d_st: Render3dState, b: int) -> bool {
|
||||
let u = render3d_st.gvk_buf_used[b]
|
||||
return u == render3d_st.gvk_frame_no or (render3d_st.gvk_frame_pending and u == render3d_st.gvk_frame_no - 1)
|
||||
return u == render3d_st.gvk_frame_no or (render3d_st.gvk_frame_pending and u == render3d_st.gvk_frame_pending_no)
|
||||
}
|
||||
|
||||
# the frame's submitted work is done: storage retired during it can go
|
||||
function gvk_retire_flush(render3d_st: mut Render3dState) -> void {
|
||||
# everything submitted is done: every retired buffer can go
|
||||
function gvk_retire_flush(render3d_st: mut Render3dState) -> void { gvk_retire_upto(render3d_st, render3d_st.gvk_frame_no) }
|
||||
|
||||
# frame `done` and every frame before it are finished: storage last read by them can go, and what
|
||||
# the frame being recorded read stays
|
||||
function gvk_retire_upto(render3d_st: mut Render3dState, done: int) -> void {
|
||||
if render3d_st.gvk_retired_buf == null { return }
|
||||
let keep_buf = new []long
|
||||
let keep_mem = new []long
|
||||
let keep_frame = new []int
|
||||
for i in 0 .. len(render3d_st.gvk_retired_buf) {
|
||||
Vk.destroy_buffer(render3d_st.gvk_dev, render3d_st.gvk_retired_buf[i], null)
|
||||
gvk_mem_free(render3d_st, gvk_mem_id(render3d_st.gvk_retired_mem[i]))
|
||||
var f = 0
|
||||
if render3d_st.gvk_retired_frame != null and i < len(render3d_st.gvk_retired_frame) { f = render3d_st.gvk_retired_frame[i] }
|
||||
if f > done {
|
||||
push(keep_buf, render3d_st.gvk_retired_buf[i]); push(keep_mem, render3d_st.gvk_retired_mem[i]); push(keep_frame, f)
|
||||
} else {
|
||||
Vk.destroy_buffer(render3d_st.gvk_dev, render3d_st.gvk_retired_buf[i], null)
|
||||
gvk_mem_free(render3d_st, gvk_mem_id(render3d_st.gvk_retired_mem[i]))
|
||||
}
|
||||
}
|
||||
render3d_st.gvk_retired_buf = new []long
|
||||
render3d_st.gvk_retired_mem = new []long
|
||||
render3d_st.gvk_retired_buf = keep_buf
|
||||
render3d_st.gvk_retired_mem = keep_mem
|
||||
render3d_st.gvk_retired_frame = keep_frame
|
||||
}
|
||||
|
||||
# A buffer a compute pass writes: a draw later in the same frame reads what the GPU put there,
|
||||
|
|
|
|||
|
|
@ -113,6 +113,8 @@ function grass_init(render3d_st: mut Render3dState) -> void {
|
|||
render3d_st.grass_radius = 1600.0
|
||||
if r3d_env_has(render3d_st, "R3D_NOBLADES") { render3d_st.grass_on = false }
|
||||
if r3d_env_has(render3d_st, "R3D_GRASS_R") { render3d_st.grass_radius = float(Text.to_int(r3d_env(render3d_st, "R3D_GRASS_R"))) }
|
||||
if r3d_env_has(render3d_st, "R3D_GRASS_S0") { render3d_st.grass_s0 = float(Text.to_int(r3d_env(render3d_st, "R3D_GRASS_S0"))) / 1000.0 }
|
||||
if r3d_env_has(render3d_st, "R3D_GRASS_D0") { render3d_st.grass_d0 = float(Text.to_int(r3d_env(render3d_st, "R3D_GRASS_D0"))) }
|
||||
if r3d_env_has(render3d_st, "R3D_GRASS_DBG") { render3d_st.grass_dbg = Text.to_int(r3d_env(render3d_st, "R3D_GRASS_DBG")) }
|
||||
}
|
||||
|
||||
|
|
@ -179,7 +181,7 @@ function grass_tiles(render3d_st: mut Render3dState, size: int, d_min: float, d_
|
|||
}
|
||||
|
||||
function grass_draw(render3d_st: mut Render3dState) -> void {
|
||||
if not render3d_st.grass_on or render3d_st.ter_reflect or render3d_st.grass_prog == 0 { return }
|
||||
if not (render3d_st.grass_on or render3d_st.grass_force) or render3d_st.ter_reflect or render3d_st.grass_prog == 0 { return }
|
||||
var p = render3d_st.grass_prog
|
||||
if grass_mesh_live(render3d_st) { p = render3d_st.grass_mesh_prog }
|
||||
gpu_use_program(render3d_st, p)
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ function prof_slot(render3d_st: mut Render3dState, name: pointer) -> int {
|
|||
|
||||
function prof_begin(render3d_st: mut Render3dState, name: pointer) -> void {
|
||||
ds_set_pass(render3d_st, name)
|
||||
if render3d_st.gvk_labels { gvk_label_begin(render3d_st, name) }
|
||||
if not render3d_st.prof_on { return }
|
||||
if render3d_st.prof_active >= 0 { return } # GL_TIME_ELAPSED queries cannot nest
|
||||
let s = prof_slot(render3d_st, name)
|
||||
|
|
@ -56,6 +57,7 @@ function prof_begin(render3d_st: mut Render3dState, name: pointer) -> void {
|
|||
|
||||
function prof_end(render3d_st: mut Render3dState) -> void {
|
||||
ds_set_pass(render3d_st, render3d_st.ds_outside)
|
||||
if render3d_st.gvk_labels { gvk_label_end(render3d_st) }
|
||||
if not render3d_st.prof_on { return }
|
||||
if render3d_st.prof_active < 0 { return }
|
||||
gpu_query_end(render3d_st)
|
||||
|
|
|
|||
|
|
@ -56,6 +56,8 @@ function r3d_env_flags(render3d_st: mut Render3dState) -> void {
|
|||
if r3d_env_has(render3d_st, "R3D_NOGI") { render3d_st.post_gi_strength = 0.0; render3d_st.post_ao_strength = 0.0; render3d_st.post_no_gi = true }
|
||||
if r3d_env_has(render3d_st, "R3D_MSAA") { render3d_st.post_ms_samples = Text.to_int(r3d_env(render3d_st, "R3D_MSAA")) }
|
||||
render3d_st.sc_skip_blade = r3d_env_has(render3d_st, "R3D_NOBLADES")
|
||||
render3d_st.sc_skip_grass = r3d_env_has(render3d_st, "R3D_NOGRASS")
|
||||
render3d_st.grass_force = r3d_env_has(render3d_st, "R3D_BLADES")
|
||||
render3d_st.sc_skip_card = r3d_env_has(render3d_st, "R3D_NOCARDS")
|
||||
render3d_st.sc_dbg_lod = r3d_env_has(render3d_st, "R3D_LODDBG")
|
||||
if r3d_env_has(render3d_st, "R3D_ANISO") {
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ property Layer {
|
|||
last_cam: floats,
|
||||
rough: float = 0.0,
|
||||
blade: bool = false,
|
||||
grass: bool = false, # ground-cover grass the game may hand to the GPU blades (R3D_NOGRASS skips it)
|
||||
flower: bool = false,
|
||||
card: bool = false,
|
||||
cheap: bool = false, # distant cover: no shadows, no wind, flat lighting
|
||||
|
|
@ -1134,6 +1135,7 @@ function scatter_draw_depth(render3d_st: mut Render3dState) -> void {
|
|||
for i in 0 .. len(render3d_st.sc_layers) {
|
||||
let l = render3d_st.sc_layers[i]
|
||||
if not l.foliage or l.blade or l.flower { continue }
|
||||
if render3d_st.sc_skip_grass and l.grass { continue }
|
||||
if render3d_st.r3d_no_trees and l.imp != null { continue }
|
||||
layer_update(render3d_st, l)
|
||||
if l.n_lods > 1 and l.g_on {
|
||||
|
|
@ -1155,6 +1157,7 @@ function scatter_draw(render3d_st: mut Render3dState) -> void {
|
|||
if render3d_st.sc_skip_blade and l.blade { continue }
|
||||
if render3d_st.sc_skip_flower and l.flower { continue }
|
||||
if render3d_st.sc_skip_card and l.card { continue }
|
||||
if render3d_st.sc_skip_grass and l.grass { continue }
|
||||
if render3d_st.r3d_no_trees and l.imp != null and not l.card { continue }
|
||||
layer_update(render3d_st, l)
|
||||
layer_draw_near(render3d_st, l, false, null, false)
|
||||
|
|
@ -1172,6 +1175,7 @@ function scatter_draw_casters(render3d_st: mut Render3dState, light_vp: floats)
|
|||
let l = render3d_st.sc_layers[i]
|
||||
if render3d_st.sc_skip_blade and l.blade { continue }
|
||||
if render3d_st.sc_skip_card and l.card { continue }
|
||||
if render3d_st.sc_skip_grass and l.grass { continue }
|
||||
if render3d_st.r3d_no_trees and l.imp != null and not l.card { continue }
|
||||
layer_update(render3d_st, l)
|
||||
if l.imp != null {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue