406 lines
13 KiB
C++
406 lines
13 KiB
C++
/**
|
|
******************************************************************************
|
|
* 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<uintptr_t>(active_draw_.command_allocation.host_ptr);
|
|
auto state_host_ptr =
|
|
reinterpret_cast<uintptr_t>(active_draw_.state_allocation.host_ptr);
|
|
active_draw_.header = reinterpret_cast<CommonHeader*>(state_host_ptr);
|
|
// active_draw_.float_consts =
|
|
// reinterpret_cast<float4*>(state_host_ptr +
|
|
// batch_state_.float_consts_offset);
|
|
// active_draw_.bool_consts =
|
|
// reinterpret_cast<uint32_t*>(state_host_ptr +
|
|
// batch_state_.bool_consts_offset);
|
|
// active_draw_.loop_consts =
|
|
// reinterpret_cast<uint32_t*>(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<void*>(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<void*>(
|
|
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
|