fix: corregir límite de sprites en SHAPE mode con muchas bolas

GpuSpriteBatch::init() ahora acepta capacidad dinámica para soportar
--custom-balls N con N > 200000. El buffer se dimensiona a (N+1) quads,
reservando siempre un slot para el overlay. addFullscreenOverlay() calcula
overlay_index_count_ desde el delta real de indices_ en lugar de fijarlo
a 6 incondicionalmente. Engine calcula la capacidad correcta al init.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-21 00:14:28 +01:00
parent 46b24bf075
commit 74d954df1e
3 changed files with 23 additions and 12 deletions

View File

@@ -231,8 +231,14 @@ bool Engine::initialize(int width, int height, int zoom, bool fullscreen, AppMod
success = false; success = false;
} }
// Calcular capacidad: background (1) + bolas del escenario máximo + overlay (+1 interno en init)
int sprite_capacity = BALL_COUNT_SCENARIOS[DEMO_AUTO_MAX_SCENARIO];
if (custom_scenario_enabled_ && custom_scenario_balls_ > sprite_capacity)
sprite_capacity = custom_scenario_balls_;
sprite_capacity += 1; // +1 por el background
sprite_batch_ = std::make_unique<GpuSpriteBatch>(); sprite_batch_ = std::make_unique<GpuSpriteBatch>();
if (!sprite_batch_->init(gpu_ctx_->device())) { if (!sprite_batch_->init(gpu_ctx_->device(), sprite_capacity)) {
std::cerr << "ERROR: No se pudo crear el sprite batch GPU" << std::endl; std::cerr << "ERROR: No se pudo crear el sprite batch GPU" << std::endl;
success = false; success = false;
} }

View File

@@ -7,10 +7,12 @@
// Public interface // Public interface
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
bool GpuSpriteBatch::init(SDL_GPUDevice* device) { bool GpuSpriteBatch::init(SDL_GPUDevice* device, int max_sprites) {
// Pre-allocate GPU buffers large enough for MAX_SPRITES quads. max_sprites_ = max_sprites;
Uint32 max_verts = static_cast<Uint32>(MAX_SPRITES) * 4; // Pre-allocate GPU buffers large enough for (max_sprites_ + 1) quads.
Uint32 max_indices = static_cast<Uint32>(MAX_SPRITES) * 6; // The +1 reserves a guaranteed slot for the fullscreen overlay.
Uint32 max_verts = static_cast<Uint32>(max_sprites_ + 1) * 4;
Uint32 max_indices = static_cast<Uint32>(max_sprites_ + 1) * 6;
Uint32 vb_size = max_verts * sizeof(GpuVertex); Uint32 vb_size = max_verts * sizeof(GpuVertex);
Uint32 ib_size = max_indices * sizeof(uint32_t); Uint32 ib_size = max_indices * sizeof(uint32_t);
@@ -53,8 +55,8 @@ bool GpuSpriteBatch::init(SDL_GPUDevice* device) {
return false; return false;
} }
vertices_.reserve(MAX_SPRITES * 4); vertices_.reserve(static_cast<size_t>(max_sprites_ + 1) * 4);
indices_.reserve(MAX_SPRITES * 6); indices_.reserve(static_cast<size_t>(max_sprites_ + 1) * 6);
return true; return true;
} }
@@ -129,8 +131,9 @@ void GpuSpriteBatch::addSprite(float x, float y, float w, float h,
void GpuSpriteBatch::addFullscreenOverlay() { void GpuSpriteBatch::addFullscreenOverlay() {
overlay_index_offset_ = static_cast<int>(indices_.size()); overlay_index_offset_ = static_cast<int>(indices_.size());
size_t before = indices_.size();
pushQuad(-1.0f, 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f); pushQuad(-1.0f, 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f);
overlay_index_count_ = 6; overlay_index_count_ = static_cast<int>(indices_.size() - before); // 0 si pushQuad falló, 6 si ok
} }
bool GpuSpriteBatch::uploadBatch(SDL_GPUDevice* device, SDL_GPUCommandBuffer* cmd_buf) { bool GpuSpriteBatch::uploadBatch(SDL_GPUDevice* device, SDL_GPUCommandBuffer* cmd_buf) {
@@ -179,7 +182,7 @@ void GpuSpriteBatch::toNDC(float px, float py,
void GpuSpriteBatch::pushQuad(float ndx0, float ndy0, float ndx1, float ndy1, void GpuSpriteBatch::pushQuad(float ndx0, float ndy0, float ndx1, float ndy1,
float u0, float v0, float u1, float v1, float u0, float v0, float u1, float v1,
float r, float g, float b, float a) { float r, float g, float b, float a) {
if (vertices_.size() + 4 > static_cast<size_t>(MAX_SPRITES) * 4) return; if (vertices_.size() + 4 > static_cast<size_t>(max_sprites_) * 4) return;
uint32_t vi = static_cast<uint32_t>(vertices_.size()); uint32_t vi = static_cast<uint32_t>(vertices_.size());
// TL, TR, BR, BL // TL, TR, BR, BL

View File

@@ -26,10 +26,10 @@ struct GpuVertex {
// ============================================================================ // ============================================================================
class GpuSpriteBatch { class GpuSpriteBatch {
public: public:
// Maximum sprites (background + UI overlay each count as one sprite) // Default maximum sprites (background + UI overlay each count as one sprite)
static constexpr int MAX_SPRITES = 200000; static constexpr int DEFAULT_MAX_SPRITES = 200000;
bool init(SDL_GPUDevice* device); bool init(SDL_GPUDevice* device, int max_sprites = DEFAULT_MAX_SPRITES);
void destroy(SDL_GPUDevice* device); void destroy(SDL_GPUDevice* device);
void beginFrame(); void beginFrame();
@@ -83,4 +83,6 @@ private:
int sprite_index_count_ = 0; int sprite_index_count_ = 0;
int overlay_index_offset_ = 0; int overlay_index_offset_ = 0;
int overlay_index_count_ = 0; int overlay_index_count_ = 0;
int max_sprites_ = DEFAULT_MAX_SPRITES;
}; };