/** ****************************************************************************** * Xenia : Xbox 360 Emulator Research Project * ****************************************************************************** * Copyright 2014 Ben Vanik. All rights reserved. * * Released under the BSD license - see LICENSE in the root for more details. * ****************************************************************************** */ #include "xenia/gpu/gl4/draw_batcher.h" #include "poly/cxx_compat.h" #include "poly/math.h" #include "xenia/gpu/gl4/gl4_gpu-private.h" #include "xenia/gpu/gpu-private.h" namespace xe { namespace gpu { namespace gl4 { using namespace xe::gpu::xenos; extern "C" GLEWContext* glewGetContext(); const size_t kCommandBufferCapacity = 16 * (1024 * 1024); const size_t kCommandBufferAlignment = 4; const size_t kStateBufferCapacity = 64 * (1024 * 1024); const size_t kStateBufferAlignment = 256; DrawBatcher::DrawBatcher(RegisterFile* register_file) : register_file_(register_file), command_buffer_(kCommandBufferCapacity, kCommandBufferAlignment), state_buffer_(kStateBufferCapacity, kStateBufferAlignment), array_data_buffer_(nullptr), has_bindless_mdi_(false), draw_open_(false) { std::memset(&batch_state_, 0, sizeof(batch_state_)); batch_state_.needs_reconfigure = true; batch_state_.command_range_start = batch_state_.state_range_start = UINTPTR_MAX; std::memset(&active_draw_, 0, sizeof(active_draw_)); } bool DrawBatcher::Initialize(CircularBuffer* array_data_buffer) { array_data_buffer_ = array_data_buffer; if (!command_buffer_.Initialize()) { return false; } if (!state_buffer_.Initialize()) { return false; } glBindBuffer(GL_DRAW_INDIRECT_BUFFER, command_buffer_.handle()); if (FLAGS_vendor_gl_extensions && GLEW_NV_bindless_multi_draw_indirect) { has_bindless_mdi_ = true; } return true; } void DrawBatcher::Shutdown() { command_buffer_.Shutdown(); state_buffer_.Shutdown(); } bool DrawBatcher::ReconfigurePipeline(GL4Shader* vertex_shader, GL4Shader* pixel_shader, GLuint pipeline) { if (batch_state_.pipeline == pipeline) { // No-op. return true; } if (!Flush(FlushMode::kReconfigure)) { return false; } batch_state_.vertex_shader = vertex_shader; batch_state_.pixel_shader = pixel_shader; batch_state_.pipeline = pipeline; return true; } bool DrawBatcher::BeginDrawArrays(PrimitiveType prim_type, uint32_t index_count) { assert_false(draw_open_); if (batch_state_.prim_type != prim_type || batch_state_.indexed) { if (!Flush(FlushMode::kReconfigure)) { return false; } } batch_state_.prim_type = prim_type; batch_state_.indexed = false; if (!BeginDraw()) { return false; } auto cmd = active_draw_.draw_arrays_cmd; cmd->base_instance = 0; cmd->instance_count = 1; cmd->count = index_count; cmd->first_index = 0; return true; } bool DrawBatcher::BeginDrawElements(PrimitiveType prim_type, uint32_t index_count, IndexFormat index_format) { assert_false(draw_open_); GLenum index_type = index_format == IndexFormat::kInt32 ? GL_UNSIGNED_INT : GL_UNSIGNED_SHORT; if (batch_state_.prim_type != prim_type || !batch_state_.indexed || batch_state_.index_type != index_type) { if (!Flush(FlushMode::kReconfigure)) { return false; } } batch_state_.prim_type = prim_type; batch_state_.indexed = true; batch_state_.index_type = index_type; if (!BeginDraw()) { return false; } uint32_t start_index = register_file_->values[XE_GPU_REG_VGT_INDX_OFFSET].u32; assert_zero(start_index); auto cmd = active_draw_.draw_elements_cmd; cmd->base_instance = 0; cmd->instance_count = 1; cmd->count = index_count; cmd->first_index = start_index; cmd->base_vertex = 0; if (has_bindless_mdi_) { auto bindless_cmd = active_draw_.draw_elements_bindless_cmd; bindless_cmd->reserved_zero = 0; } return true; } bool DrawBatcher::BeginDraw() { draw_open_ = true; if (batch_state_.needs_reconfigure) { batch_state_.needs_reconfigure = false; // Have been reconfigured since last draw - need to compute state size. // Layout: // [draw command] // [common header] // [consts] // Padded to max. GLsizei command_size = 0; if (has_bindless_mdi_) { if (batch_state_.indexed) { command_size = sizeof(DrawElementsIndirectBindlessCommandNV); } else { command_size = sizeof(DrawArraysIndirectBindlessCommandNV); } } else { if (batch_state_.indexed) { command_size = sizeof(DrawElementsIndirectCommand); } else { command_size = sizeof(DrawArraysIndirectCommand); } } batch_state_.command_stride = poly::round_up(command_size, GLsizei(kCommandBufferAlignment)); GLsizei header_size = sizeof(CommonHeader); // TODO(benvanik); consts sizing. // GLsizei float_consts_size = sizeof(float4) * 512; // GLsizei bool_consts_size = sizeof(uint32_t) * 8; // GLsizei loop_consts_size = sizeof(uint32_t) * 32; // GLsizei consts_size = // float_consts_size + bool_consts_size + loop_consts_size; // batch_state_.float_consts_offset = batch_state_.header_offset + // header_size; // batch_state_.bool_consts_offset = // batch_state_.float_consts_offset + float_consts_size; // batch_state_.loop_consts_offset = // batch_state_.bool_consts_offset + bool_consts_size; GLsizei consts_size = 0; batch_state_.state_stride = header_size + consts_size; } // Allocate a command data block. // We should treat it as write-only. if (!command_buffer_.CanAcquire(batch_state_.command_stride)) { Flush(FlushMode::kMakeCoherent); } active_draw_.command_allocation = command_buffer_.Acquire(batch_state_.command_stride); assert_not_null(active_draw_.command_allocation.host_ptr); // Allocate a state data block. // We should treat it as write-only. if (!state_buffer_.CanAcquire(batch_state_.state_stride)) { Flush(FlushMode::kMakeCoherent); } active_draw_.state_allocation = state_buffer_.Acquire(batch_state_.state_stride); assert_not_null(active_draw_.state_allocation.host_ptr); active_draw_.command_address = reinterpret_cast(active_draw_.command_allocation.host_ptr); auto state_host_ptr = reinterpret_cast(active_draw_.state_allocation.host_ptr); active_draw_.header = reinterpret_cast(state_host_ptr); // active_draw_.float_consts = // reinterpret_cast(state_host_ptr + // batch_state_.float_consts_offset); // active_draw_.bool_consts = // reinterpret_cast(state_host_ptr + // batch_state_.bool_consts_offset); // active_draw_.loop_consts = // reinterpret_cast(state_host_ptr + // batch_state_.loop_consts_offset); return true; } void DrawBatcher::DiscardDraw() { if (!draw_open_) { // No-op. return; } draw_open_ = false; command_buffer_.Discard(std::move(active_draw_.command_allocation)); state_buffer_.Discard(std::move(active_draw_.state_allocation)); } bool DrawBatcher::CommitDraw() { assert_true(draw_open_); draw_open_ = false; // Copy over required constants. CopyConstants(); if (batch_state_.state_range_start == UINTPTR_MAX) { batch_state_.command_range_start = active_draw_.command_allocation.offset; batch_state_.state_range_start = active_draw_.state_allocation.offset; } batch_state_.command_range_length += active_draw_.command_allocation.aligned_length; batch_state_.state_range_length += active_draw_.state_allocation.aligned_length; command_buffer_.Commit(std::move(active_draw_.command_allocation)); state_buffer_.Commit(std::move(active_draw_.state_allocation)); ++batch_state_.draw_count; return true; } bool DrawBatcher::Flush(FlushMode mode) { if (batch_state_.draw_count) { #if FINE_GRAINED_DRAW_SCOPES SCOPE_profile_cpu_f("gpu"); #endif // FINE_GRAINED_DRAW_SCOPES assert_not_zero(batch_state_.command_stride); assert_not_zero(batch_state_.state_stride); // Flush pending buffer changes. command_buffer_.Flush(); state_buffer_.Flush(); array_data_buffer_->Flush(); // State data is indexed by draw ID. glBindBufferRange(GL_SHADER_STORAGE_BUFFER, 0, state_buffer_.handle(), batch_state_.state_range_start, batch_state_.state_range_length); GLenum prim_type = 0; switch (batch_state_.prim_type) { case PrimitiveType::kPointList: prim_type = GL_POINTS; break; case PrimitiveType::kLineList: prim_type = GL_LINES; break; case PrimitiveType::kLineStrip: prim_type = GL_LINE_STRIP; break; case PrimitiveType::kLineLoop: prim_type = GL_LINE_LOOP; break; case PrimitiveType::kTriangleList: prim_type = GL_TRIANGLES; break; case PrimitiveType::kTriangleStrip: prim_type = GL_TRIANGLE_STRIP; break; case PrimitiveType::kTriangleFan: prim_type = GL_TRIANGLE_FAN; break; case PrimitiveType::kRectangleList: prim_type = GL_TRIANGLE_STRIP; // Rect lists aren't culled. There may be other things they skip too. // assert_true((register_file_->values[XE_GPU_REG_PA_SU_SC_MODE_CNTL].u32 // & // 0x3) == 0); break; case PrimitiveType::kQuadList: prim_type = GL_LINES_ADJACENCY; break; default: case PrimitiveType::kUnknown0x07: prim_type = GL_POINTS; XELOGE("unsupported primitive type %d", batch_state_.prim_type); assert_unhandled_case(batch_state_.prim_type); DiscardDraw(); return false; } // Fast path for single draws. void* indirect_offset = reinterpret_cast(batch_state_.command_range_start); if (has_bindless_mdi_) { int vertex_buffer_count = batch_state_.vertex_shader->buffer_inputs().total_elements_count; assert_true(vertex_buffer_count < 8); if (batch_state_.indexed) { glMultiDrawElementsIndirectBindlessNV( prim_type, batch_state_.index_type, indirect_offset, batch_state_.draw_count, batch_state_.command_stride, vertex_buffer_count); } else { glMultiDrawArraysIndirectBindlessNV( prim_type, indirect_offset, batch_state_.draw_count, batch_state_.command_stride, vertex_buffer_count); } } else { if (batch_state_.draw_count == 1) { // Fast path for one draw. Removes MDI overhead when not required. if (batch_state_.indexed) { auto& cmd = active_draw_.draw_elements_cmd; glDrawElementsInstancedBaseVertexBaseInstance( prim_type, cmd->count, batch_state_.index_type, reinterpret_cast( uintptr_t(cmd->first_index) * (batch_state_.index_type == GL_UNSIGNED_SHORT ? 2 : 4)), cmd->instance_count, cmd->base_vertex, cmd->base_instance); } else { auto& cmd = active_draw_.draw_arrays_cmd; glDrawArraysInstancedBaseInstance(prim_type, cmd->first_index, cmd->count, cmd->instance_count, cmd->base_instance); } } else { // Full multi-draw. if (batch_state_.indexed) { glMultiDrawElementsIndirect(prim_type, batch_state_.index_type, indirect_offset, batch_state_.draw_count, batch_state_.command_stride); } else { glMultiDrawArraysIndirect(prim_type, indirect_offset, batch_state_.draw_count, batch_state_.command_stride); } } } batch_state_.command_range_start = UINTPTR_MAX; batch_state_.command_range_length = 0; batch_state_.state_range_start = UINTPTR_MAX; batch_state_.state_range_length = 0; batch_state_.draw_count = 0; } if (mode == FlushMode::kReconfigure) { // Reset - we'll update it as soon as we have all the information. batch_state_.needs_reconfigure = true; } return true; } void DrawBatcher::CopyConstants() { // TODO(benvanik): partial updates, etc. We could use shader constant access // knowledge that we get at compile time to only upload those constants // required. If we did this as a variable length then we could really cut // down on state block sizes. std::memcpy(active_draw_.header->float_consts, ®ister_file_->values[XE_GPU_REG_SHADER_CONSTANT_000_X].f32, sizeof(active_draw_.header->float_consts)); std::memcpy( active_draw_.header->bool_consts, ®ister_file_->values[XE_GPU_REG_SHADER_CONSTANT_BOOL_000_031].f32, sizeof(active_draw_.header->bool_consts)); std::memcpy(active_draw_.header->loop_consts, ®ister_file_->values[XE_GPU_REG_SHADER_CONSTANT_LOOP_00].f32, sizeof(active_draw_.header->loop_consts)); } } // namespace gl4 } // namespace gpu } // namespace xe