Indirectly transformed

- Indirect rendering supports TransformedType
 - Use single object to manage all ssbos
This commit is contained in:
Jozufozu 2022-08-13 21:28:39 -07:00
parent e7339dc7ef
commit 56ded52193
17 changed files with 259 additions and 159 deletions

View file

@ -9,7 +9,7 @@ minecraft_version = 1.18.2
forge_version = 40.1.68
# build dependency versions
forgegradle_version = 5.1.+
forgegradle_version = 5.1.53
mixingradle_version = 0.7-SNAPSHOT
mixin_version = 0.8.5
librarian_version = 1.+

View file

@ -38,6 +38,8 @@ public interface StructType<S extends InstancedPart> {
StorageBufferWriter<S> getStorageBufferWriter();
FileResolution getIndirectShader();
public interface VertexTransformer<S extends InstancedPart> {
void transform(MutableVertexList vertexList, S struct, ClientLevel level);
}

View file

@ -54,8 +54,7 @@ public class Backend {
}
public static void refresh() {
// TODO: Revert when done testing
TYPE = BackendType.INDIRECT; // chooseEngine();
TYPE = chooseEngine();
}
public static boolean isOn() {

View file

@ -39,7 +39,7 @@ public class GlCompat {
instancedArrays = getLatest(InstancedArrays.class, caps);
bufferStorage = getLatest(BufferStorage.class, caps);
supportsIndirect = caps.OpenGL46;
supportsIndirect = true;
amd = _isAmdWindows();
}

View file

@ -0,0 +1,134 @@
package com.jozufozu.flywheel.backend.instancing.indirect;
import static org.lwjgl.opengl.GL46.*;
import org.lwjgl.system.MemoryUtil;
import org.lwjgl.system.Pointer;
public class IndirectBuffers {
public static final int BUFFER_COUNT = 4;
public static final long INT_SIZE = Integer.BYTES;
public static final long PTR_SIZE = Pointer.POINTER_SIZE;
// DRAW COMMAND
public static final long DRAW_COMMAND_STRIDE = 36;
public static final long DRAW_COMMAND_OFFSET = 0;
// BITS
private static final int SUB_DATA_BITS = GL_DYNAMIC_STORAGE_BIT;
private static final int PERSISTENT_BITS = GL_MAP_PERSISTENT_BIT | GL_MAP_WRITE_BIT;
private static final int MAP_BITS = PERSISTENT_BITS | GL_MAP_FLUSH_EXPLICIT_BIT;
private static final int GPU_ONLY_BITS = 0;
// OFFSETS
private static final long OFFSET_OFFSET = BUFFER_COUNT * INT_SIZE;
private static final long SIZE_OFFSET = OFFSET_OFFSET + BUFFER_COUNT * PTR_SIZE;
private static final long BUFFERS_SIZE_BYTES = SIZE_OFFSET + BUFFER_COUNT * PTR_SIZE;
private static final long OBJECT_SIZE_OFFSET = SIZE_OFFSET;
private static final long TARGET_SIZE_OFFSET = OBJECT_SIZE_OFFSET + PTR_SIZE;
private static final long BATCH_SIZE_OFFSET = TARGET_SIZE_OFFSET + PTR_SIZE;
private static final long DRAW_SIZE_OFFSET = BATCH_SIZE_OFFSET + PTR_SIZE;
final long buffers;
final long objectStride;
int object;
int target;
int batch;
int draw;
long objectPtr;
long batchPtr;
long drawPtr;
int maxObjectCount;
int maxDrawCount;
IndirectBuffers(long objectStride) {
this.objectStride = objectStride;
this.buffers = MemoryUtil.nmemAlloc(BUFFERS_SIZE_BYTES);
if (this.buffers == MemoryUtil.NULL) {
throw new OutOfMemoryError();
}
MemoryUtil.memSet(this.buffers, 0, BUFFERS_SIZE_BYTES);
}
void createBuffers() {
nglCreateBuffers(4, buffers);
object = MemoryUtil.memGetInt(buffers);
target = MemoryUtil.memGetInt(buffers + 4);
batch = MemoryUtil.memGetInt(buffers + 8);
draw = MemoryUtil.memGetInt(buffers + 12);
}
void updateCounts(int objectCount, int drawCount) {
if (objectCount > maxObjectCount) {
createObjectStorage(objectCount);
}
if (drawCount > maxDrawCount) {
createDrawStorage(drawCount);
}
long objectSize = objectStride * objectCount;
long targetSize = INT_SIZE * objectCount;
long drawSize = DRAW_COMMAND_STRIDE * drawCount;
MemoryUtil.memPutAddress(buffers + OBJECT_SIZE_OFFSET, objectSize);
MemoryUtil.memPutAddress(buffers + TARGET_SIZE_OFFSET, targetSize);
MemoryUtil.memPutAddress(buffers + BATCH_SIZE_OFFSET, targetSize);
MemoryUtil.memPutAddress(buffers + DRAW_SIZE_OFFSET, drawSize);
}
void createObjectStorage(int objectCount) {
var objectSize = objectStride * objectCount;
var targetSize = INT_SIZE * objectCount;
glNamedBufferStorage(object, objectSize, PERSISTENT_BITS);
glNamedBufferStorage(target, targetSize, GPU_ONLY_BITS);
glNamedBufferStorage(batch, targetSize, PERSISTENT_BITS);
objectPtr = nglMapNamedBufferRange(object, 0, objectSize, MAP_BITS);
batchPtr = nglMapNamedBufferRange(batch, 0, targetSize, MAP_BITS);
maxObjectCount = objectCount;
}
void createDrawStorage(int drawCount) {
var drawSize = DRAW_COMMAND_STRIDE * drawCount;
glNamedBufferStorage(draw, drawSize, SUB_DATA_BITS);
drawPtr = MemoryUtil.nmemAlloc(drawSize);
// drawPtr = nglMapNamedBufferRange(draw, 0, drawSize, MAP_BITS);
maxDrawCount = drawCount;
}
public void bindAll() {
bindN(BUFFER_COUNT);
}
public void bindObjectAndTarget() {
bindN(2);
}
private void bindN(int bufferCount) {
nglBindBuffersRange(GL_SHADER_STORAGE_BUFFER, 0, bufferCount, buffers, buffers + OFFSET_OFFSET, buffers + SIZE_OFFSET);
}
void bindIndirectBuffer() {
glBindBuffer(GL_DRAW_INDIRECT_BUFFER, draw);
}
void flushBatchIDs(long length) {
glFlushMappedNamedBufferRange(batch, 0, length);
}
void flushObjects(long length) {
glFlushMappedNamedBufferRange(object, 0, length);
}
void flushDrawCommands(long length) {
nglNamedBufferSubData(draw, 0, length, drawPtr);
// glFlushMappedNamedBufferRange(this.draw, 0, length);
}
}

View file

@ -70,12 +70,14 @@ public class IndirectEngine implements Engine {
@Override
public void renderStage(TaskEngine taskEngine, RenderContext context, RenderStage stage) {
var groups = renderLists.get(stage);
if (stage != RenderStage.AFTER_SOLID_TERRAIN) {
return;
}
setup();
for (var group : groups) {
group.submit();
for (IndirectList<?> list : renderLists.lists.values()) {
list.submit(stage);
}
}

View file

@ -41,30 +41,6 @@ public class IndirectInstancer<D extends InstancedPart> extends AbstractInstance
anyToRemove = false;
}
void writeAll(final StructWriter<D> writer) {
anyToUpdate = false;
for (var instance : data) {
writer.write(instance);
}
}
void writeChanged(final StructWriter<D> writer) {
if (!anyToUpdate) {
return;
}
anyToUpdate = false;
final int size = data.size();
if (size == 0) {
return;
}
writeChangedUnchecked(writer);
}
@Override
public void delete() {
// noop

View file

@ -5,12 +5,15 @@ import static org.lwjgl.opengl.GL46.*;
import java.util.ArrayList;
import java.util.List;
import org.lwjgl.system.MemoryStack;
import org.jetbrains.annotations.NotNull;
import org.lwjgl.system.MemoryUtil;
import com.jozufozu.flywheel.api.RenderStage;
import com.jozufozu.flywheel.api.instancer.InstancedPart;
import com.jozufozu.flywheel.api.material.Material;
import com.jozufozu.flywheel.api.struct.StorageBufferWriter;
import com.jozufozu.flywheel.api.struct.StructType;
import com.jozufozu.flywheel.api.vertex.VertexType;
import com.jozufozu.flywheel.backend.gl.shader.GlProgram;
import com.jozufozu.flywheel.backend.instancing.PipelineCompiler;
import com.jozufozu.flywheel.core.Components;
@ -18,76 +21,37 @@ import com.jozufozu.flywheel.core.Materials;
import com.jozufozu.flywheel.core.QuadConverter;
import com.jozufozu.flywheel.core.model.Mesh;
import com.jozufozu.flywheel.core.uniform.UniformBuffer;
import com.jozufozu.flywheel.core.vertex.Formats;
public class IndirectList<T extends InstancedPart> {
private static final long DRAW_COMMAND_STRIDE = 36;
private static final long DRAW_COMMAND_OFFSET = 0;
final StorageBufferWriter<T> storageBufferWriter;
final GlProgram compute;
final GlProgram draw;
private final StructType<T> type;
private final long maxObjectCount;
private final StructType<T> structType;
private final VertexType vertexType;
private final long objectStride;
private final int maxBatchCount;
private final long objectClientStorage;
private final long batchIDClientStorage;
private final int elementBuffer;
/**
* Stores raw instance data per-object.
*/
int objectBuffer;
int targetBuffer;
int batchBuffer;
/**
* Stores drawIndirect structs.
*/
int drawBuffer;
int debugBuffer;
final IndirectBuffers buffers;
final IndirectMeshPool meshPool;
private final int elementBuffer;
int vertexArray;
final int[] shaderStorageBuffers = new int[5];
final List<Batch<T>> batches = new ArrayList<>();
IndirectList(StructType<T> structType) {
type = structType;
storageBufferWriter = type.getStorageBufferWriter();
if (storageBufferWriter == null) {
throw new NullPointerException();
}
glCreateBuffers(shaderStorageBuffers);
objectBuffer = shaderStorageBuffers[0];
targetBuffer = shaderStorageBuffers[1];
batchBuffer = shaderStorageBuffers[2];
drawBuffer = shaderStorageBuffers[3];
debugBuffer = shaderStorageBuffers[4];
meshPool = new IndirectMeshPool(Formats.BLOCK, 1024);
// FIXME: Resizable buffers
maxObjectCount = 64 * 64 * 64;
maxBatchCount = 64;
IndirectList(StructType<T> structType, VertexType vertexType) {
this.structType = structType;
this.vertexType = vertexType;
storageBufferWriter = this.structType.getStorageBufferWriter();
objectStride = storageBufferWriter.getAlignment();
int persistentBits = GL_MAP_PERSISTENT_BIT | GL_MAP_WRITE_BIT;
glNamedBufferStorage(objectBuffer, objectStride * maxObjectCount, persistentBits);
glNamedBufferStorage(targetBuffer, 4 * maxObjectCount, 0);
glNamedBufferStorage(batchBuffer, 4 * maxObjectCount, persistentBits);
glNamedBufferStorage(drawBuffer, DRAW_COMMAND_STRIDE * maxBatchCount, GL_DYNAMIC_STORAGE_BIT);
glNamedBufferStorage(debugBuffer, 4 * maxObjectCount, 0);
buffers = new IndirectBuffers(objectStride);
buffers.createBuffers();
buffers.createObjectStorage(64 * 64 * 64);
buffers.createDrawStorage(64);
int mapBits = persistentBits | GL_MAP_FLUSH_EXPLICIT_BIT;
objectClientStorage = nglMapNamedBufferRange(objectBuffer, 0, objectStride * maxObjectCount, mapBits);
batchIDClientStorage = nglMapNamedBufferRange(batchBuffer, 0, 4 * maxObjectCount, mapBits);
meshPool = new IndirectMeshPool(vertexType, 1024);
vertexArray = glCreateVertexArrays();
@ -95,14 +59,15 @@ public class IndirectList<T extends InstancedPart> {
.quads2Tris(2048).buffer.handle();
setupVertexArray();
compute = ComputeCullerCompiler.INSTANCE.get(Components.Files.ORIENTED_INDIRECT);
draw = PipelineCompiler.INSTANCE.get(new PipelineCompiler.Context(Formats.BLOCK, Materials.BELL, Components.Files.ORIENTED_INDIRECT, Components.WORLD, Components.INDIRECT));
var indirectShader = this.structType.getIndirectShader();
compute = ComputeCullerCompiler.INSTANCE.get(indirectShader);
draw = PipelineCompiler.INSTANCE.get(new PipelineCompiler.Context(vertexType, Materials.CHEST, indirectShader, Components.WORLD, Components.INDIRECT));
}
private void setupVertexArray() {
glVertexArrayElementBuffer(vertexArray, elementBuffer);
var meshLayout = Formats.BLOCK.getLayout();
var meshLayout = vertexType.getLayout();
var meshAttribs = meshLayout.getAttributeCount();
var attributes = meshLayout.getAttributes();
@ -118,17 +83,23 @@ public class IndirectList<T extends InstancedPart> {
}
}
public void add(Mesh mesh, IndirectInstancer<T> instancer) {
batches.add(new Batch<>(instancer, meshPool.alloc(mesh)));
public void add(IndirectInstancer<T> instancer, Material material, Mesh mesh) {
batches.add(new Batch<>(instancer, material, meshPool.alloc(mesh)));
}
void submit() {
void submit(RenderStage stage) {
if (batches.isEmpty()) {
return;
}
int instanceCountThisFrame = calculateTotalInstanceCount();
if (instanceCountThisFrame == 0) {
return;
}
// TODO: Sort meshes by material and draw many contiguous sections of the draw indirect buffer,
// adjusting uniforms/textures accordingly
buffers.updateCounts(instanceCountThisFrame, batches.size());
meshPool.uploadAll();
uploadInstanceData();
uploadIndirectCommands();
@ -136,39 +107,38 @@ public class IndirectList<T extends InstancedPart> {
UniformBuffer.getInstance().sync();
dispatchCompute(instanceCountThisFrame);
issueMemoryBarrier();
glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);
dispatchDraw();
}
private void dispatchDraw() {
draw.bind();
Materials.BELL.setup();
glVertexArrayElementBuffer(vertexArray, elementBuffer);
glBindVertexArray(vertexArray);
glBindBuffer(GL_DRAW_INDIRECT_BUFFER, drawBuffer);
glMultiDrawElementsIndirect(GL_TRIANGLES, GL_UNSIGNED_INT, DRAW_COMMAND_OFFSET, batches.size(), (int) DRAW_COMMAND_STRIDE);
Materials.BELL.clear();
}
buffers.bindIndirectBuffer();
private static void issueMemoryBarrier() {
glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);
final int stride = (int) IndirectBuffers.DRAW_COMMAND_STRIDE;
long offset = 0;
for (Batch<T> batch : batches) {
batch.material.setup();
glMultiDrawElementsIndirect(GL_TRIANGLES, GL_UNSIGNED_INT, offset, 1, stride);
batch.material.clear();
offset += stride;
}
}
private void dispatchCompute(int instanceCount) {
compute.bind();
glBindBufferRange(GL_SHADER_STORAGE_BUFFER, 0, objectBuffer, 0, instanceCount * objectStride);
glBindBufferRange(GL_SHADER_STORAGE_BUFFER, 1, targetBuffer, 0, instanceCount * 4L);
glBindBufferRange(GL_SHADER_STORAGE_BUFFER, 2, batchBuffer, 0, instanceCount * 4L);
glBindBufferRange(GL_SHADER_STORAGE_BUFFER, 3, drawBuffer, 0, batches.size() * DRAW_COMMAND_STRIDE);
glBindBufferRange(GL_SHADER_STORAGE_BUFFER, 4, debugBuffer, 0, instanceCount * 4L);
buffers.bindAll();
var groupCount = (instanceCount + 31) >> 5; // ceil(totalInstanceCount / 32)
var groupCount = (instanceCount + 31) >> 5; // ceil(instanceCount / 32)
glDispatchCompute(groupCount, 1, 1);
}
private void uploadInstanceData() {
long objectPtr = objectClientStorage;
long batchIDPtr = batchIDClientStorage;
long objectPtr = buffers.objectPtr;
long batchIDPtr = buffers.batchPtr;
int baseInstance = 0;
int batchID = 0;
for (var batch : batches) {
@ -181,27 +151,23 @@ public class IndirectList<T extends InstancedPart> {
// write batchID
MemoryUtil.memPutInt(batchIDPtr, batchID);
batchIDPtr += 4;
batchIDPtr += IndirectBuffers.INT_SIZE;
}
baseInstance += batch.instancer.instanceCount;
batchID++;
}
glFlushMappedNamedBufferRange(objectBuffer, 0, objectPtr - objectClientStorage);
glFlushMappedNamedBufferRange(batchBuffer, 0, batchIDPtr - batchIDClientStorage);
buffers.flushObjects(objectPtr - buffers.objectPtr);
buffers.flushBatchIDs(batchIDPtr - buffers.batchPtr);
}
private void uploadIndirectCommands() {
try (var stack = MemoryStack.stackPush()) {
long size = batches.size() * DRAW_COMMAND_STRIDE;
long basePtr = stack.nmalloc((int) size);
long writePtr = basePtr;
for (Batch<T> batch : batches) {
batch.writeIndirectCommand(writePtr);
writePtr += DRAW_COMMAND_STRIDE;
}
nglNamedBufferSubData(drawBuffer, 0, size, basePtr);
long writePtr = buffers.drawPtr;
for (Batch<T> batch : batches) {
batch.writeIndirectCommand(writePtr);
writePtr += IndirectBuffers.DRAW_COMMAND_STRIDE;
}
buffers.flushDrawCommands(writePtr - buffers.drawPtr);
}
private int calculateTotalInstanceCount() {
@ -216,10 +182,12 @@ public class IndirectList<T extends InstancedPart> {
private static final class Batch<T extends InstancedPart> {
final IndirectInstancer<T> instancer;
final IndirectMeshPool.BufferedMesh mesh;
int baseInstance;
final Material material;
int baseInstance = -1;
private Batch(IndirectInstancer<T> instancer, IndirectMeshPool.BufferedMesh mesh) {
private Batch(IndirectInstancer<T> instancer, Material material, IndirectMeshPool.BufferedMesh mesh) {
this.instancer = instancer;
this.material = material;
this.mesh = mesh;
}

View file

@ -77,6 +77,8 @@ public class IndirectMeshPool {
baseVertex += model.mesh.getVertexCount();
}
clientStorage.rewind();
glNamedBufferSubData(vbo, 0, clientStorage);
}

View file

@ -21,7 +21,7 @@ public class InstancedModel<D extends InstancedPart> {
for (var entry : materialMeshMap.entrySet()) {
var material = entry.getKey();
var mesh = entry.getValue();
renderLists.add(material.getRenderStage(), type, mesh, instancer);
renderLists.add(instancer, material, mesh);
return; // TODO: support multiple meshes per model
}

View file

@ -1,40 +1,24 @@
package com.jozufozu.flywheel.backend.instancing.indirect;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ListMultimap;
import com.jozufozu.flywheel.api.RenderStage;
import com.jozufozu.flywheel.api.instancer.InstancedPart;
import com.jozufozu.flywheel.api.material.Material;
import com.jozufozu.flywheel.api.struct.StructType;
import com.jozufozu.flywheel.api.vertex.VertexType;
import com.jozufozu.flywheel.core.model.Mesh;
import com.jozufozu.flywheel.util.Pair;
public class RenderLists {
public final Map<RenderStage, Map<StructType<?>, IndirectList<?>>> renderLists = new EnumMap<>(RenderStage.class);
public Collection<IndirectList<?>> get(RenderStage stage) {
var renderList = renderLists.get(stage);
if (renderList == null) {
return Collections.emptyList();
}
return renderList.values();
}
public final Map<Pair<StructType<?>, VertexType>, IndirectList<?>> lists = new HashMap<>();
@SuppressWarnings("unchecked")
public <D extends InstancedPart> void add(RenderStage stage, StructType<D> type, Mesh mesh, IndirectInstancer<D> instancer) {
var indirectList = (IndirectList<D>) renderLists.computeIfAbsent(stage, $ -> new HashMap<>())
.computeIfAbsent(type, IndirectList::new);
public <D extends InstancedPart> void add(IndirectInstancer<D> instancer, Material material, Mesh mesh) {
var indirectList = (IndirectList<D>) lists.computeIfAbsent(Pair.of(instancer.structType, mesh.getVertexType()),
p -> new IndirectList<>(p.first(), p.second()));
indirectList.add(mesh, instancer);
indirectList.add(instancer, material, mesh);
}
}

View file

@ -0,0 +1,31 @@
package com.jozufozu.flywheel.core.structs.model;
import org.lwjgl.system.MemoryUtil;
import com.jozufozu.flywheel.api.struct.StorageBufferWriter;
import com.jozufozu.flywheel.util.extension.MatrixExtension;
public class TransformedStorageWriter implements StorageBufferWriter<TransformedPart> {
public static final TransformedStorageWriter INSTANCE = new TransformedStorageWriter();
private TransformedStorageWriter() {
}
@Override
public void write(long ptr, TransformedPart instance) {
MatrixExtension.writeUnsafe(instance.model, ptr);
MatrixExtension.writeUnsafe(instance.normal, ptr + 64);
MemoryUtil.memPutByte(ptr + 112, instance.r);
MemoryUtil.memPutByte(ptr + 113, instance.g);
MemoryUtil.memPutByte(ptr + 114, instance.b);
MemoryUtil.memPutByte(ptr + 115, instance.a);
MemoryUtil.memPutShort(ptr + 116, instance.skyLight);
MemoryUtil.memPutShort(ptr + 118, instance.blockLight);
}
@Override
public int getAlignment() {
return 128;
}
}

View file

@ -36,7 +36,7 @@ public class TransformedType implements StructType<TransformedPart> {
@Override
public StorageBufferWriter<TransformedPart> getStorageBufferWriter() {
return null; // TODO
return TransformedStorageWriter.INSTANCE;
}
@Override
@ -44,6 +44,11 @@ public class TransformedType implements StructType<TransformedPart> {
return Components.Files.TRANSFORMED;
}
@Override
public FileResolution getIndirectShader() {
return Components.Files.TRANSFORMED_INDIRECT;
}
@Override
public VertexTransformer<? extends TransformedPart> getVertexTransformer() {
return (vertexList, struct, level) -> {

View file

@ -47,6 +47,11 @@ public class OrientedType implements StructType<OrientedPart> {
return Components.Files.ORIENTED;
}
@Override
public FileResolution getIndirectShader() {
return Components.Files.ORIENTED_INDIRECT;
}
@Override
public VertexTransformer<? extends OrientedPart> getVertexTransformer() {
return (vertexList, struct, level) -> {

View file

@ -2,6 +2,7 @@ package com.jozufozu.flywheel.util.extension;
import java.nio.ByteBuffer;
import com.mojang.math.Matrix3f;
import com.mojang.math.Matrix4f;
/**
@ -24,4 +25,8 @@ public interface MatrixExtension {
static void writeUnsafe(Matrix4f matrix, long ptr) {
((MatrixExtension) (Object) matrix).flywheel$writeUnsafe(ptr);
}
static void writeUnsafe(Matrix3f matrix, long ptr) {
((MatrixExtension) (Object) matrix).flywheel$writeUnsafe(ptr);
}
}

View file

@ -1,6 +1,6 @@
#use "flywheel:api/vertex.glsl"
#define FLW_INSTANCE_STRUCT Instance
#define FLW_INSTANCE_STRUCT Instance
struct Instance {
mat4 pose;
mat3 normal;

View file

@ -31,24 +31,11 @@ layout(std430, binding = 3) restrict buffer DrawCommands {
MeshDrawCommand drawCommands[];
};
layout(std430, binding = 4) restrict writeonly buffer DebugVisibility {
uint objectVisibilityBits[];
};
// 83 - 27 = 56 spirv instruction results
bool testSphere(vec3 center, float radius) {
bvec4 xyInside = greaterThanEqual(fma(flw_planes.xyX, center.xxxx, fma(flw_planes.xyY, center.yyyy, fma(flw_planes.xyZ, center.zzzz, flw_planes.xyW))), -radius.xxxx);
bvec2 zInside = greaterThanEqual(fma(flw_planes.zX, center.xx, fma(flw_planes.zY, center.yy, fma(flw_planes.zZ, center.zz, flw_planes.zW))), -radius.xx);
uint debug = uint(xyInside.x);
debug |= uint(xyInside.y) << 1;
debug |= uint(xyInside.z) << 2;
debug |= uint(xyInside.w) << 3;
debug |= uint(zInside.x) << 4;
debug |= uint(zInside.y) << 5;
objectVisibilityBits[flw_objectID] = debug;
return all(xyInside) && all(zInside);
}