Files
test2/source/blender/gpu/intern/gpu_batch.cc
2025-09-15 15:11:02 +02:00

546 lines
17 KiB
C++

/* SPDX-FileCopyrightText: 2016 by Mike Erwin. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*
* GPU geometry batch
* Contains VAOs + VBOs + Shader representing a drawable entity.
*/
#include "BLI_math_base.h"
#include "BLI_utildefines.h"
#include "GPU_batch.hh"
#include "GPU_batch_presets.hh"
#include "GPU_shader.hh"
#include "GPU_index_buffer.hh"
#include "GPU_vertex_buffer.hh"
#include "gpu_backend.hh"
#include "gpu_context_private.hh"
#include "gpu_debug_private.hh"
#include "gpu_shader_private.hh"
#include <cstring>
using namespace blender::gpu;
/* -------------------------------------------------------------------- */
/** \name Creation & Deletion
* \{ */
void GPU_batch_zero(Batch *batch)
{
std::fill_n(batch->verts, ARRAY_SIZE(batch->verts), nullptr);
batch->elem = nullptr;
batch->flag = GPUBatchFlag(0);
batch->prim_type = GPUPrimType(0);
batch->shader = nullptr;
batch->procedural_vertices = -1;
}
Batch *GPU_batch_calloc()
{
Batch *batch = GPUBackend::get()->batch_alloc();
GPU_batch_zero(batch);
return batch;
}
Batch *GPU_batch_create_ex(GPUPrimType primitive_type,
VertBuf *vertex_buf,
IndexBuf *index_buf,
GPUBatchFlag owns_flag)
{
Batch *batch = GPU_batch_calloc();
GPU_batch_init_ex(batch, primitive_type, vertex_buf, index_buf, owns_flag);
return batch;
}
void GPU_batch_init_ex(Batch *batch,
GPUPrimType primitive_type,
VertBuf *vertex_buf,
IndexBuf *index_buf,
GPUBatchFlag owns_flag)
{
/* Do not pass any other flag */
BLI_assert((owns_flag & ~(GPU_BATCH_OWNS_VBO | GPU_BATCH_OWNS_INDEX)) == 0);
/* Batch needs to be in cleared state. */
BLI_assert((batch->flag & GPU_BATCH_INIT) == 0);
batch->verts[0] = vertex_buf;
for (int v = 1; v < GPU_BATCH_VBO_MAX_LEN; v++) {
batch->verts[v] = nullptr;
}
batch->elem = index_buf;
batch->prim_type = primitive_type;
batch->flag = owns_flag | GPU_BATCH_INIT | GPU_BATCH_DIRTY;
batch->shader = nullptr;
batch->procedural_vertices = -1;
}
Batch *GPU_batch_create_procedural(GPUPrimType primitive_type, int32_t vertex_count)
{
Batch *batch = GPU_batch_calloc();
for (auto &v : batch->verts) {
v = nullptr;
}
batch->elem = nullptr;
batch->prim_type = primitive_type;
batch->flag = GPU_BATCH_INIT | GPU_BATCH_DIRTY;
batch->shader = nullptr;
batch->procedural_vertices = vertex_count;
return batch;
}
void GPU_batch_copy(Batch *batch_dst, Batch *batch_src)
{
GPU_batch_clear(batch_dst);
GPU_batch_init_ex(
batch_dst, GPU_PRIM_POINTS, batch_src->verts[0], batch_src->elem, GPU_BATCH_INVALID);
batch_dst->prim_type = batch_src->prim_type;
for (int v = 1; v < GPU_BATCH_VBO_MAX_LEN; v++) {
batch_dst->verts[v] = batch_src->verts[v];
}
batch_dst->procedural_vertices = batch_src->procedural_vertices;
}
void GPU_batch_clear(Batch *batch)
{
if (batch->flag & GPU_BATCH_OWNS_INDEX) {
GPU_indexbuf_discard(batch->elem);
}
if (batch->flag & GPU_BATCH_OWNS_VBO_ANY) {
for (int v = 0; (v < GPU_BATCH_VBO_MAX_LEN) && batch->verts[v]; v++) {
if (batch->flag & (GPU_BATCH_OWNS_VBO << v)) {
GPU_VERTBUF_DISCARD_SAFE(batch->verts[v]);
}
}
}
batch->flag = GPU_BATCH_INVALID;
batch->procedural_vertices = -1;
}
void GPU_batch_discard(Batch *batch)
{
GPU_batch_clear(batch);
delete batch;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Buffers Management
* \{ */
void GPU_batch_elembuf_set(Batch *batch, blender::gpu::IndexBuf *index_buf, bool own_ibo)
{
BLI_assert(index_buf);
batch->flag |= GPU_BATCH_DIRTY;
if (batch->elem && (batch->flag & GPU_BATCH_OWNS_INDEX)) {
GPU_indexbuf_discard(batch->elem);
}
batch->elem = index_buf;
SET_FLAG_FROM_TEST(batch->flag, own_ibo, GPU_BATCH_OWNS_INDEX);
}
int GPU_batch_vertbuf_add(Batch *batch, VertBuf *vertex_buf, bool own_vbo)
{
BLI_assert(vertex_buf);
batch->flag |= GPU_BATCH_DIRTY;
for (uint v = 0; v < GPU_BATCH_VBO_MAX_LEN; v++) {
if (batch->verts[v] == nullptr) {
/* for now all VertexBuffers must have same vertex_len */
if (batch->verts[0] != nullptr) {
/* This is an issue for the HACK inside DRW_vbo_request(). */
// BLI_assert(verts->vertex_len == batch->verts[0]->vertex_len);
}
batch->verts[v] = vertex_buf;
SET_FLAG_FROM_TEST(batch->flag, own_vbo, (GPUBatchFlag)(GPU_BATCH_OWNS_VBO << v));
return v;
}
}
/* we only make it this far if there is no room for another VertBuf */
BLI_assert_msg(0, "Not enough VBO slot in batch");
return -1;
}
bool GPU_batch_vertbuf_has(const Batch *batch, const VertBuf *vertex_buf)
{
for (uint v = 0; v < GPU_BATCH_VBO_MAX_LEN; v++) {
if (batch->verts[v] == vertex_buf) {
return true;
}
}
return false;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Uniform setters
*
* \{ */
void GPU_batch_set_shader(Batch *batch,
blender::gpu::Shader *shader,
const shader::SpecializationConstants *constants_state)
{
batch->shader = shader;
GPU_shader_bind(batch->shader, constants_state);
}
static uint16_t bind_attribute_as_ssbo(const ShaderInterface *interface,
blender::gpu::Shader *shader,
VertBuf *vbo)
{
const GPUVertFormat *format = &vbo->format;
/* We need to support GPU OpenSubdiv meshes. This assert can be enabled back after we refactor
* our OpenSubdiv implementation to output the same layout as the regular mesh extraction. */
// BLI_assert_msg(format->attr_len == 1, "Multi attribute buffers are not supported for now");
char uniform_name[] = "gpu_attr_0";
uint stride = format->stride;
uint offset = 0;
uint16_t bound_attr = 0u;
for (uint a_idx = 0; a_idx < format->attr_len; a_idx++) {
const GPUVertAttr *a = &format->attrs[a_idx];
if (format->deinterleaved) {
offset += ((a_idx == 0) ? 0 : format->attrs[a_idx - 1].type.size()) * vbo->vertex_len;
stride = a->type.size();
}
else {
offset = a->offset;
}
for (uint n_idx = 0; n_idx < a->name_len; n_idx++) {
const char *name = GPU_vertformat_attr_name_get(format, a, n_idx);
const ShaderInput *input = interface->ssbo_get(name);
if (input == nullptr || input->location == -1) {
continue;
}
GPU_vertbuf_bind_as_ssbo(vbo, input->location);
bound_attr |= (1 << input->location);
/* WORKAROUND: This is to support complex format. But ideally this should not be supported.
*/
uniform_name[9] = '0' + input->location;
/* Only support 4byte aligned attributes. */
BLI_assert((stride % 4) == 0);
BLI_assert((offset % 4) == 0);
int descriptor[2] = {int(stride) / 4, int(offset) / 4};
GPU_shader_uniform_2iv(shader, uniform_name, descriptor);
/* WORKAROUND: Fix for polyline workaround. Ideally should be fused with `gpu_attr_0`.
* But for now, changes are a bit too invasive. Will need to be revisited later on. */
char uniform_name_len[] = "gpu_attr_0_len";
uniform_name_len[9] = '0' + input->location;
GPU_shader_uniform_1i(shader, uniform_name_len, a->type.comp_len());
}
}
return bound_attr;
}
void GPU_batch_bind_as_resources(Batch *batch,
blender::gpu::Shader *shader,
const shader::SpecializationConstants *constants)
{
const ShaderInterface *interface = shader->interface;
if (interface->ssbo_attr_mask_ == 0) {
return;
}
uint16_t ssbo_attributes = interface->ssbo_attr_mask_;
if (ssbo_attributes & (1 << GPU_SSBO_INDEX_BUF_SLOT)) {
/* Ensure binding for setting uniforms. This is required by the OpenGL backend. */
GPU_shader_bind(shader, constants);
if (batch->elem) {
GPU_indexbuf_bind_as_ssbo(batch->elem, GPU_SSBO_INDEX_BUF_SLOT);
GPU_shader_uniform_1b(shader, "gpu_index_no_buffer", false);
GPU_shader_uniform_1b(shader, "gpu_index_16bit", !batch->elem->is_32bit());
GPU_shader_uniform_1i(shader, "gpu_index_base_index", batch->elem->index_base_get());
}
else {
/* Still fulfill the binding requirements even if the buffer will not be read. */
GPU_vertbuf_bind_as_ssbo(batch->verts[0], GPU_SSBO_INDEX_BUF_SLOT);
GPU_shader_uniform_1b(shader, "gpu_index_no_buffer", true);
}
ssbo_attributes &= ~(1 << GPU_SSBO_INDEX_BUF_SLOT);
}
/* Reverse order so first VBO'S have more prevalence (in term of attribute override). */
for (int v = GPU_BATCH_VBO_MAX_LEN - 1; v > -1; v--) {
VertBuf *vbo = batch->verts_(v);
if (vbo) {
ssbo_attributes &= ~bind_attribute_as_ssbo(interface, shader, vbo);
}
}
BLI_assert_msg(ssbo_attributes == 0, "Not all attribute storage buffer fulfilled");
UNUSED_VARS_NDEBUG(ssbo_attributes);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Drawing / Draw-call functions
* \{ */
void GPU_batch_draw_parameter_get(Batch *batch,
int *r_vertex_count,
int *r_vertex_first,
int *r_base_index,
int *r_instance_count)
{
if (batch->procedural_vertices >= 0) {
*r_vertex_count = batch->procedural_vertices;
*r_vertex_first = 0;
*r_base_index = -1;
}
else if (batch->elem) {
*r_vertex_count = batch->elem_()->index_len_get();
*r_vertex_first = batch->elem_()->index_start_get();
*r_base_index = batch->elem_()->index_base_get();
}
else {
*r_vertex_count = batch->verts_(0)->vertex_len;
*r_vertex_first = 0;
*r_base_index = -1;
}
*r_instance_count = 1;
}
blender::IndexRange GPU_batch_draw_expanded_parameter_get(GPUPrimType input_prim_type,
GPUPrimType output_prim_type,
int vertex_count,
int vertex_first,
int output_primitive_cout)
{
int vert_per_original_primitive = indices_per_primitive(input_prim_type);
int vert_per_expanded_primitive = indices_per_primitive(output_prim_type);
int prim_first = vertex_first / vert_per_original_primitive;
int prim_len = vertex_count / vert_per_original_primitive;
BLI_assert_msg(vert_per_original_primitive != -1,
"Primitive expansion only works for primitives with known amount of vertices");
/* WORKAROUND: Needed for polyline_draw_workaround. */
if (input_prim_type == GPU_PRIM_LINE_STRIP) {
prim_len = vertex_count - 1;
}
int out_vertex_first = prim_first * vert_per_expanded_primitive * output_primitive_cout;
int out_vertex_count = prim_len * vert_per_expanded_primitive * output_primitive_cout;
return blender::IndexRange(out_vertex_first, out_vertex_count);
}
static void polyline_draw_workaround(
Batch *batch, int vertex_first, int vertex_count, int instance_first, int instance_count)
{
/* Early out as this can cause crashes on some backend (see #136831). */
if (vertex_count == 0) {
return;
}
/* Check compatible input primitive. */
BLI_assert(ELEM(batch->prim_type, GPU_PRIM_LINES, GPU_PRIM_LINE_STRIP, GPU_PRIM_LINE_LOOP));
GPU_batch_bind_as_resources(batch, batch->shader);
blender::IndexRange range = GPU_batch_draw_expanded_parameter_get(
batch->prim_type, GPU_PRIM_TRIS, vertex_count, vertex_first, 2);
Batch *tri_batch = Context::get()->procedural_triangles_batch_get();
GPU_batch_set_shader(tri_batch, batch->shader);
int vert_stride_count[3] = {(batch->prim_type == GPU_PRIM_LINES) ? 2 : 1, vertex_count, 0};
GPU_shader_uniform_3iv(batch->shader, "gpu_vert_stride_count_offset", vert_stride_count);
/* Assume GPU_FETCH_FLOAT for now. A bit cumbersome to assert for this or to find the correct
* attribute. */
GPU_shader_uniform_1b(batch->shader, "gpu_attr_0_fetch_int", false);
/* Allow byte color fetch. */
const GPUVertFormat *format = GPU_vertbuf_get_format(batch->verts[0]);
int id = GPU_vertformat_attr_id_get(format, "color");
if (id != -1) {
const GPUVertAttr &attr = format->attrs[id];
const bool is_unorm8 = attr.type.format == blender::gpu::VertAttrType::UNORM_8_8_8_8;
BLI_assert_msg(is_unorm8 || attr.type.fetch_mode() == GPU_FETCH_FLOAT,
"color attribute for polylines can only use GPU_FETCH_INT_TO_FLOAT_UNIT or "
"GPU_FETCH_FLOAT");
GPU_shader_uniform_1b(batch->shader, "gpu_attr_1_fetch_unorm8", is_unorm8);
}
GPU_batch_draw_advanced(tri_batch, range.start(), range.size(), instance_first, instance_count);
}
void GPU_batch_draw(Batch *batch)
{
BLI_assert(batch != nullptr);
GPU_shader_bind(batch->shader);
if (batch->shader->is_polyline) {
polyline_draw_workaround(batch, 0, batch->vertex_count_get(), 0, 0);
}
else {
GPU_batch_draw_advanced(batch, 0, 0, 0, 0);
}
}
void GPU_batch_draw_range(Batch *batch, int vertex_first, int vertex_count)
{
BLI_assert(batch != nullptr);
GPU_shader_bind(batch->shader);
if (batch->shader->is_polyline) {
polyline_draw_workaround(batch, vertex_first, vertex_count, 0, 0);
}
else {
GPU_batch_draw_advanced(batch, vertex_first, vertex_count, 0, 0);
}
}
void GPU_batch_draw_instance_range(Batch *batch, int instance_first, int instance_count)
{
BLI_assert(batch != nullptr);
/* Not polyline shaders support instancing. */
BLI_assert(batch->shader->is_polyline == false);
GPU_shader_bind(batch->shader);
GPU_batch_draw_advanced(batch, 0, 0, instance_first, instance_count);
}
void GPU_batch_draw_advanced(
Batch *batch, int vertex_first, int vertex_count, int instance_first, int instance_count)
{
BLI_assert(batch != nullptr);
BLI_assert(Context::get()->shader != nullptr);
Context::get()->assert_framebuffer_shader_compatibility(Context::get()->shader);
if (vertex_count == 0) {
if (batch->procedural_vertices > 0) {
vertex_count = batch->procedural_vertices;
}
else if (batch->elem) {
vertex_count = batch->elem_()->index_len_get();
}
else {
vertex_count = batch->verts_(0)->vertex_len;
}
}
if (instance_count == 0) {
instance_count = 1;
}
if (vertex_count == 0 || instance_count == 0) {
/* Nothing to draw. */
return;
}
#ifndef NDEBUG
debug_validate_binding_image_format();
#endif
batch->draw(vertex_first, vertex_count, instance_first, instance_count);
}
void GPU_batch_draw_indirect(Batch *batch, blender::gpu::StorageBuf *indirect_buf, intptr_t offset)
{
BLI_assert(batch != nullptr);
BLI_assert(indirect_buf != nullptr);
BLI_assert(Context::get()->shader != nullptr);
Context::get()->assert_framebuffer_shader_compatibility(Context::get()->shader);
#ifndef NDEBUG
debug_validate_binding_image_format();
#endif
batch->draw_indirect(indirect_buf, offset);
}
void GPU_batch_multi_draw_indirect(Batch *batch,
blender::gpu::StorageBuf *indirect_buf,
int count,
intptr_t offset,
intptr_t stride)
{
BLI_assert(batch != nullptr);
BLI_assert(indirect_buf != nullptr);
BLI_assert(Context::get()->shader != nullptr);
Context::get()->assert_framebuffer_shader_compatibility(Context::get()->shader);
#ifndef NDEBUG
debug_validate_binding_image_format();
#endif
batch->multi_draw_indirect(indirect_buf, count, offset, stride);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Utilities
* \{ */
void GPU_batch_program_set_builtin_with_config(Batch *batch,
GPUBuiltinShader shader_id,
GPUShaderConfig sh_cfg)
{
blender::gpu::Shader *shader = GPU_shader_get_builtin_shader_with_config(shader_id, sh_cfg);
GPU_batch_set_shader(batch, shader);
}
void GPU_batch_program_set_builtin(Batch *batch, GPUBuiltinShader shader_id)
{
GPU_batch_program_set_builtin_with_config(batch, shader_id, GPU_SHADER_CFG_DEFAULT);
}
void GPU_batch_program_set_imm_shader(Batch *batch)
{
GPU_batch_set_shader(batch, immGetShader());
}
blender::gpu::Batch *GPU_batch_procedural_points_get()
{
return blender::gpu::Context::get()->procedural_points_batch_get();
}
blender::gpu::Batch *GPU_batch_procedural_lines_get()
{
return blender::gpu::Context::get()->procedural_lines_batch_get();
}
blender::gpu::Batch *GPU_batch_procedural_triangles_get()
{
return blender::gpu::Context::get()->procedural_triangles_batch_get();
}
blender::gpu::Batch *GPU_batch_procedural_triangle_strips_get()
{
return blender::gpu::Context::get()->procedural_triangle_strips_batch_get();
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Init/Exit
* \{ */
void gpu_batch_init()
{
gpu_batch_presets_init();
}
void gpu_batch_exit()
{
gpu_batch_presets_exit();
}
/** \} */