fix(render3d): nothing draws into a BC7 texture - sampler binding binds, and compressed images get no mips
Metal aborted a net.sh guest ("MTLPixelFormatBC7_RGBAUnorm is not color renderable"): the crash
report's main thread was in vkQueueSubmit from gvk_once_end, MoltenVK encoding a vkCmdBlitImage
through a render pipeline. water.ludic bound its reflection by sampler name and then asked the
BOUND texture for mips - on Vulkan the bound texture was not the reflection but the last one
bound, since 23.3 a BC7 kit texture. gpu_bind_sampler now makes its texture the bound one, as
OpenGL did (and water binds it explicitly); gvk_tex_mips and gvk_mips_now skip compressed images;
gvk_pass_begin leaves out a compressed colour attachment and says so.
tests/net.sh passes (main e515bd87 built against it; host and guest agree on 196 animals, the
people, the board and the lost hiker).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
f1049953c4
commit
1447304dd7
6 changed files with 40 additions and 0 deletions
9
changes/render3d-bc-not-a-target.md
Normal file
9
changes/render3d-bc-not-a-target.md
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
bump: patch
|
||||
type: fix
|
||||
**A compressed texture is never drawn into.** On Vulkan, binding a texture to a sampler by name
|
||||
did not make it the bound texture, as it did on OpenGL, so a following `gpu_tex_param` or
|
||||
`gpu_tex_mips` acted on whatever was bound last. The water's reflection asked for its mip chain
|
||||
that way and, once kit textures came as BC7 (23.3), built one into a kit texture: MoltenVK blits
|
||||
through a render pass, and Metal aborted ("BC7 is not color renderable"). Binding for a sampler
|
||||
now binds, as OpenGL's did; mip generation leaves a compressed texture alone (its levels came with
|
||||
it); and a pass that would have a compressed image as a colour attachment leaves it out and says so.
|
||||
|
|
@ -200,6 +200,7 @@ export state Render3dState {
|
|||
gvk_labels: bool = false # R3D_VK_LABELS: each profiled pass is a debug label (Metal System Trace)
|
||||
gvk_inflight: int = -1
|
||||
gvk_nopool: bool = false
|
||||
gvk_bc_said: int = 0 # attachments refused for being compressed, said so far
|
||||
gvk_has_bc: bool = false # textureCompressionBC: .dds textures are uploaded compressed
|
||||
# R3D_VK_PROF: Vulkan objects made and destroyed, reported every 120 frames
|
||||
gvk_mk_img: int = 0
|
||||
|
|
|
|||
|
|
@ -608,6 +608,10 @@ function gpu_tex_free(render3d_st: mut Render3dState, tex: int) -> void {
|
|||
# a texture on a unit for a program's sampler, by the sampler's name
|
||||
function gpu_bind_sampler(render3d_st: mut Render3dState, prog: int, name: string, unit: int, kind: int, tex: int) -> void {
|
||||
gvk_bind_texture(render3d_st, prog, name, tex)
|
||||
# as OpenGL's did, binding a texture for a sampler makes it the one a following gpu_tex_param or
|
||||
# gpu_tex_mips acts on: render3d was written that way, and without it they reached whatever was
|
||||
# bound last (a BC7 kit texture, which a mip blit then drew into)
|
||||
if kind == GPU_TEX2D_ARRAY { render3d_st.gpu_bound_array = tex } else { render3d_st.gpu_bound_2d = tex }
|
||||
}
|
||||
|
||||
# ---- render targets and passes -------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -1094,10 +1094,29 @@ function gvk_pass_collect(render3d_st: mut Render3dState, fb: int, rec: words, r
|
|||
render3d_st.gvk_pass_dep[1] = rec[rec_o + 3]
|
||||
}
|
||||
|
||||
# A block-compressed image cannot be drawn into (Metal aborts, "BC7 is not color renderable"):
|
||||
# an attachment that is one is dropped from the pass, and said, with what it was
|
||||
function gvk_pass_no_compressed(render3d_st: mut Render3dState) -> void {
|
||||
var w = 0
|
||||
for c in 0 .. render3d_st.gvk_pass_ncolor {
|
||||
let tex = render3d_st.gvk_pass_col[c * 2]
|
||||
if tex > 0 and tex < len(render3d_st.gvk_tex_glfmt) and gvk_is_compressed(render3d_st.gvk_tex_glfmt[tex]) {
|
||||
if render3d_st.gvk_bc_said < 8 {
|
||||
render3d_st.gvk_bc_said += 1
|
||||
print(`r3d: vulkan: framebuffer {render3d_st.gvk_fb_cur} would draw into compressed texture {tex} ({gvk_tex_w(render3d_st, tex)}x{gvk_tex_h(render3d_st, tex)}); left out`)
|
||||
}
|
||||
} else {
|
||||
render3d_st.gvk_pass_col[w * 2] = tex; render3d_st.gvk_pass_col[w * 2 + 1] = render3d_st.gvk_pass_col[c * 2 + 1]
|
||||
w += 1
|
||||
}
|
||||
}
|
||||
render3d_st.gvk_pass_ncolor = w
|
||||
}
|
||||
function gvk_pass_begin(render3d_st: mut Render3dState, rec: words, rec_o: int) -> void {
|
||||
if render3d_st.gvk_in_pass { return }
|
||||
let cb = gvk_frame_cb(render3d_st)
|
||||
gvk_pass_collect(render3d_st, render3d_st.gvk_fb_cur, rec, rec_o)
|
||||
gvk_pass_no_compressed(render3d_st)
|
||||
let aw = VkRenderingAttachmentInfo_sizeof
|
||||
let catt = gvk_tmp(render3d_st, aw * 3)
|
||||
Vk.zero(catt, aw * 3)
|
||||
|
|
@ -1818,6 +1837,7 @@ function gvk_resize_check(render3d_st: mut Render3dState) -> bool {
|
|||
# smallest level every frame): recorded into the open frame after its pass, so they cost no submit.
|
||||
# A chain that has to grow first still goes through its one-shot path.
|
||||
function gvk_mips_now(render3d_st: mut Render3dState, tex: int, w: int, h: int) -> void {
|
||||
if gvk_is_compressed(render3d_st.gvk_tex_glfmt[tex]) { return } # its levels came with it
|
||||
if render3d_st.gvk_cb == null or render3d_st.gvk_tex_levels[tex] <= 1 { gvk_flush(render3d_st); gvk_tex_mips(render3d_st, tex, w, h); return }
|
||||
gvk_pass_end(render3d_st)
|
||||
gvk_tex_mips_into(render3d_st, render3d_st.gvk_cb, tex, w, h)
|
||||
|
|
|
|||
|
|
@ -356,6 +356,8 @@ function gvk_tex_grow_mips(render3d_st: mut Render3dState, tex: int, w: int, h:
|
|||
}
|
||||
|
||||
function gvk_tex_mips(render3d_st: mut Render3dState, tex: int, w: int, h: int) -> bool {
|
||||
# a block-compressed texture brought every level with it, and cannot be blitted into
|
||||
if gvk_is_compressed(render3d_st.gvk_tex_glfmt[tex]) { return true }
|
||||
if render3d_st.gvk_tex_levels[tex] <= 1 and (w > 1 or h > 1) and not gvk_is_depth(render3d_st.gvk_tex_glfmt[tex]) {
|
||||
if not gvk_tex_grow_mips(render3d_st, tex, w, h) { return false }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -324,6 +324,10 @@ function water_draw(render3d_st: mut Render3dState, depth_tex: int) -> void {
|
|||
# bind on its own unit first: generating the mip chain re-binds the texture on the active unit,
|
||||
# and it must not displace the depth texture the shader reads for the shore
|
||||
r3d_bind_2d(render3d_st, p, "u_refl", 1, render3d_st.water_refl.color); ron = 1.0
|
||||
# the parameter and the chain are the BOUND texture's, and binding a sampler by name does not
|
||||
# bind it: without this they went to whatever was bound last - a BC7 kit texture since 23.3,
|
||||
# which the mip blits then drew into (Metal: "BC7 is not color renderable")
|
||||
gpu_tex_bind(render3d_st, GPU_TEX2D, render3d_st.water_refl.color)
|
||||
gpu_tex_param(render3d_st, GPU_TEX2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR)
|
||||
gpu_tex_mips(render3d_st, GPU_TEX2D)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue