jetbrains/renderdoc-service/src/RenderDocMeshPreviewService.cpp (420 lines of code) (raw):
#include "RenderDocMeshPreviewService.h"
#include "RenderDocCaptureContext.h"
#include "RenderDocModel/RdcVertexStageInOutputs.Generated.h"
#include "RenderDocVertexResolver.h"
#include "util/StringUtils.h"
namespace jetbrains::renderdoc {
RenderDocMeshPreviewService::RenderDocMeshPreviewService(IReplayController *controller, const std::shared_ptr<RenderDocCaptureContext> &capture_context) : controller(controller), capture_context(capture_context) {}
uint32_t RenderDocMeshPreviewService::calculate_index(const BufferData &data, uint32_t vertex_id, int32_t base_vertex, uint32_t prim_restart) {
const auto it = data.buffer.data() + vertex_id * sizeof(uint32_t);
if(it + sizeof(uint32_t) > data.buffer.end())
return ~0U;
uint32_t idx = *reinterpret_cast<const uint32_t *>(it);
// check for primitive restart *before* adding base vertex
if(prim_restart && idx == prim_restart)
return idx;
// apply base vertex but clamp to 0 if subtracting
if(base_vertex < 0)
{
auto subtract = static_cast<uint32_t>(-base_vertex);
if(idx < subtract)
idx = 0;
else
idx -= subtract;
}
else if(base_vertex > 0)
{
idx += static_cast<uint32_t>(base_vertex);
}
return idx;
}
void RenderDocMeshPreviewService::calculate_input_rows(const PipeState &pipe_state, const ActionDescription *action, BufferConfig &config) const {
config.rows_num = action->numIndices;
config.expanded_rows_num = 0;
uint32_t rows_upper_bound = 0;
if (action->flags & ActionFlags::Indexed) {
const auto &i_buffer = pipe_state.GetIBuffer();
uint32_t available_bytes = i_buffer.byteSize;
if (available_bytes == ~0U) {
if (const auto it = capture_context->try_get_buffer(i_buffer.resourceId); it != nullptr) {
uint64_t offset = i_buffer.byteOffset + action->indexOffset * i_buffer.byteStride;
if(offset > it->length)
available_bytes = 0;
else
available_bytes = it->length - offset;
}
else
available_bytes = 0;
}
rows_upper_bound = available_bytes / std::max(1U, i_buffer.byteStride);
} else {
const auto &v_buffers = pipe_state.GetVBuffers();
for (const auto &buf: v_buffers) {
if (buf.byteStride == 0) continue;
uint32_t available_bytes = buf.byteSize;
if (available_bytes == ~0U) {
if (const auto it = capture_context->try_get_buffer(buf.resourceId); it != nullptr) {
if(buf.byteOffset > it->length)
available_bytes = 0;
else
available_bytes = it->length - buf.byteOffset;
}
else
available_bytes = 0;
}
rows_upper_bound = std::max(rows_upper_bound, available_bytes / std::max(1U, buf.byteStride));
}
}
if (rows_upper_bound != ~0U && rows_upper_bound + 100 < config.rows_num) {
config.expanded_rows_num = config.rows_num;
config.rows_num = rows_upper_bound + 100;
}
if(action->flags & ActionFlags::Instanced && action->numInstances == 0)
{
config.rows_num = config.expanded_rows_num = 0;
}
}
void RenderDocMeshPreviewService::calculate_output_rows(const MeshFormat &post, const BufferConfig &in_config, BufferConfig &out_config) {
if(post.numIndices <= in_config.rows_num)
{
out_config.rows_num = post.numIndices;
out_config.expanded_rows_num = 0;
}
else
{
out_config.rows_num = in_config.rows_num;
out_config.expanded_rows_num = in_config.expanded_rows_num;
}
}
void RenderDocMeshPreviewService::collect_input_columns(const PipeState &pipe_state, BufferConfig &config) {
const auto &inputs = pipe_state.GetVertexInputs();
for (const VertexInputAttribute &in : inputs) {
if (!in.used)
continue;
ShaderConstant column;
column.name = in.name;
column.byteOffset = in.byteOffset;
column.type.columns = in.format.compCount;
column.type.rows = 1;
column.type.arrayByteStride = column.type.matrixByteStride = in.format.ElementSize();
BufferElementProperties prop;
prop.buffer = in.vertexBuffer;
prop.perinstance = in.perInstance;
prop.instancerate = in.instanceRate;
prop.floatCastWrong = in.floatCastWrong;
prop.format = in.format;
config.columns.push_back(column);
config.properties.push_back(prop);
}
}
void RenderDocMeshPreviewService::collect_output_columns(const PipeState &pipe_state, BufferConfig &config) {
const ShaderReflection *reflection = pipe_state.GetShaderReflection(ShaderStage::Vertex);
if (reflection == nullptr) {
return;
}
for (const SigParameter ¶m: reflection->outputSignature) {
if (param.stream != 0 || param.systemValue == ShaderBuiltin::OutputIndices)
continue;
ShaderConstant column;
column.name = !param.varName.isEmpty() ? param.varName : param.semanticIdxName;
if (param.perPrimitiveRate)
column.name += " (per primitive)";
column.type.rows = 1;
column.type.columns = param.compCount;
BufferElementProperties prop;
prop.buffer = 0;
prop.perinstance = false;
prop.perprimitive = param.perPrimitiveRate;
prop.instancerate = 1;
prop.systemValue = param.systemValue;
prop.format.type = ResourceFormatType::Regular;
prop.format.compByteWidth = std::max<uint32_t>(sizeof(float), VarTypeByteSize(param.varType));
prop.format.compCount = param.compCount;
prop.format.compType = VarTypeCompType(param.varType);
column.type.arrayByteStride = prop.format.compByteWidth * prop.format.compCount;
if(param.systemValue == ShaderBuiltin::Position) {
config.columns.insert(config.columns.begin(), column);
config.properties.insert(config.properties.begin(), prop);
}
else {
config.columns.push_back(column);
config.properties.push_back(prop);
}
}
std::size_t n = config.columns.size();
uint32_t perPrimOffset = 0, perVertOffset = 0;
for(std::size_t i = 0; i < n; i++) {
auto &constant = config.columns[i];
const auto &prop = config.properties[i];
uint8_t type_columns = constant.type.columns;
uint8_t size = prop.format.compByteWidth > 4 ? 8U : 4U;
MeshDataStage outStage = MeshDataStage::VSOut;
switch(reflection->stage)
{
case ShaderStage::Vertex: outStage = MeshDataStage::VSOut; break;
case ShaderStage::Hull:
case ShaderStage::Domain:
case ShaderStage::Geometry: outStage = MeshDataStage::GSOut; break;
case ShaderStage::Task: outStage = MeshDataStage::TaskOut; break;
case ShaderStage::Mesh: outStage = MeshDataStage::MeshOut; break;
default: break;
}
uint32_t &offset = prop.perprimitive ? perPrimOffset : perVertOffset;
if(type_columns >= 2 && pipe_state.HasAlignedPostVSData(outStage))
{
unsigned int mult = type_columns == 2 ? 2U : 4U;
offset = utils::align_up(offset, mult * size);
}
constant.byteOffset = offset;
offset += type_columns * size;
}
}
void RenderDocMeshPreviewService::fetch_buffers(const ActionDescription *action, const PipeState &pipe_state, const MeshFormat &post, BufferConfig &in_config, BufferConfig &out_config) const {
auto i_buffer = pipe_state.GetIBuffer();
uint32_t indices_num = action->numIndices;
bytebuf data;
const auto is_indexed = action->flags & ActionFlags::Indexed;
if (i_buffer.resourceId != ResourceId::Null() && is_indexed) {
uint32_t offset = action->indexOffset * i_buffer.byteStride;
uint64_t bytes = i_buffer.byteSize > offset ? std::min<uint64_t>(i_buffer.byteSize - offset, indices_num * i_buffer.byteStride) : 0;
if (bytes > 0) {
data = controller->GetBufferData(i_buffer.resourceId, i_buffer.byteOffset + offset, bytes);
}
}
uint32_t mult = 0;
if (i_buffer.byteStride != 0 && !data.isEmpty())
mult = std::min(indices_num, (static_cast<uint32_t>(data.size()) + i_buffer.byteStride - 1) / i_buffer.byteStride);
else if (is_indexed)
mult = 1;
if (mult > 0) {
in_config.indices.buffer.resize(sizeof(uint32_t) * mult);
}
auto *indices = reinterpret_cast<uint32_t *>(in_config.indices.buffer.begin());
uint32_t max_index = std::max(1U, indices_num) - 1;
if (!data.isEmpty()) {
max_index = 0;
if (i_buffer.byteStride == 1) {
uint8_t prim_restart = in_config.prim_restart & 0xff;
for (std::size_t i = 0; i < std::min<std::size_t>(data.size(), indices_num); ++i) {
indices[i] = static_cast<uint32_t>(data[i]);
if(prim_restart && indices[i] == prim_restart) continue;
max_index = std::max(max_index, indices[i]);
}
} else if (i_buffer.byteStride == 2) {
uint16_t prim_restart = in_config.prim_restart & 0xffff;
auto *src = reinterpret_cast<uint16_t *>(data.data());
for (std::size_t i = 0; i < std::min<std::size_t>(data.size() / sizeof(uint16_t), indices_num); ++i) {
indices[i] = static_cast<uint32_t>(src[i]);
if(prim_restart && indices[i] == prim_restart) continue;
max_index = std::max(max_index, indices[i]);
}
} else if (i_buffer.byteStride == 4) {
uint32_t prim_restart = in_config.prim_restart;
memcpy(indices, data.data(), std::min<std::size_t>(data.size(), indices_num * sizeof(uint32_t)));
for(uint32_t i = 0; i < std::min<std::size_t>(data.size() / sizeof(uint32_t), indices_num); i++)
{
if(prim_restart && indices[i] == prim_restart) continue;
max_index = std::max(max_index, indices[i]);
}
}
}
const auto v_buffers = pipe_state.GetVBuffers();
int v_buffer_idx = 0;
for (const auto &v_buf : v_buffers) {
bool used = false;
bool per_inst = false;
bool per_vert = false;
uint32_t max_attr_offset = 0;
for (int64_t i = 0; i < in_config.columns.size(); ++i) {
const auto &column = in_config.columns[i];
const auto &prop = in_config.properties[i];
if (prop.buffer == v_buffer_idx) {
used = true;
max_attr_offset = std::max(max_attr_offset, column.byteOffset);
if (prop.perinstance)
per_inst = true;
else
per_vert = true;
}
}
++v_buffer_idx;
uint32_t max_idx = 0;
uint32_t offset = 0;
if (used) {
if (per_inst) {
max_idx = std::max(1U, action->numInstances) - 1;
offset = action->instanceOffset;
}
if (per_vert) {
max_idx = std::max(max_idx, max_index);
offset = action->vertexOffset;
if (uint32_t base_vertex = action->baseVertex; base_vertex > 0)
max_idx = std::max(max_idx, max_idx + base_vertex);
}
}
BufferData buf;
if (used) {
uint64_t bytes_num = std::max(max_idx, max_idx + 1) * v_buf.byteStride + max_attr_offset;
if (v_buf.byteStride == 0)
bytes_num += 16;
offset *= v_buf.byteStride;
if (v_buf.byteSize > offset)
bytes_num = std::min(v_buf.byteSize - offset, bytes_num);
else
bytes_num = 0;
if (bytes_num > 0)
buf.buffer = controller->GetBufferData(v_buf.resourceId, v_buf.byteOffset + offset, bytes_num);
buf.stride = v_buf.byteStride;
}
in_config.buffers.push_back(buf);
}
if (post.indexResourceId != ResourceId::Null() && is_indexed)
data = controller->GetBufferData(post.indexResourceId, post.indexByteOffset, indices_num * post.indexByteStride);
indices = nullptr;
if (i_buffer.byteStride != 0 && !data.isEmpty()) {
out_config.indices.buffer.resize(sizeof(uint32_t) * indices_num);
indices = reinterpret_cast<uint32_t *>(out_config.indices.buffer.begin());
if (i_buffer.byteStride == 1) {
for (std::size_t i = 0; i < std::min<std::size_t>(data.size(), indices_num); ++i)
indices[i] = static_cast<uint32_t>(data[i]);
}
else if (i_buffer.byteStride == 2) {
const auto src = reinterpret_cast<uint16_t *>(data.data());
for (std::size_t i = 0; i < std::min<std::size_t>(data.size() / sizeof(uint16_t), indices_num); ++i)
indices[i] = static_cast<uint32_t>(src[i]);
}
else if (i_buffer.byteStride == 4) {
memcpy(indices, data.data(), std::min(data.size(), indices_num * sizeof(uint32_t)));
}
}
if (post.vertexResourceId != ResourceId::Null()) {
BufferData post_vs;
post_vs.buffer = controller->GetBufferData(post.vertexResourceId, post.vertexByteOffset, 0);
post_vs.stride = post.vertexByteStride;
out_config.buffers.push_back(post_vs);
}
}
std::vector<std::vector<std::vector<float>>> RenderDocMeshPreviewService::translate_buffers_to_floats(const BufferConfig &config, std::vector<rd::Wrapper<std::wstring>> &columns, std::vector<uint32_t> &indices, uint32_t inst) {
struct ColumnInfo {
uint32_t inst_index = 0;
std::size_t stride = 0;
const byte * data = nullptr;
const byte * end = nullptr;
};
const std::size_t row_num = config.rows_num;
const std::size_t col_num = config.columns.size();
std::vector<std::vector<std::vector<float>>> result(row_num);
columns.resize(col_num);
std::vector<ColumnInfo> column_info(col_num);
for (std::size_t col = 0; col < col_num; ++col) {
auto &info = column_info[col];
const auto &column = config.columns[col];
const auto &prop = config.properties[col];
columns[col] = { StringUtils::Utf8ToWide(column.name) };
info.inst_index = prop.instancerate > 0 ? inst / prop.instancerate : 0;
if (prop.buffer < config.buffers.size()) {
info.data = config.buffers[prop.buffer].buffer.data() + column.byteOffset;
info.end = config.buffers[prop.buffer].buffer.end();
info.stride = config.buffers[prop.buffer].stride;
if (prop.perinstance)
info.data += info.stride * info.inst_index;
}
if(prop.perprimitive) info.end = info.data;
}
for (std::size_t row = 0; row < row_num; ++row) {
uint32_t idx = row;
if (!config.indices.buffer.isEmpty()) {
idx = calculate_index(config.indices, row, config.base_vertex, config.prim_restart);
if (idx == ~0U || config.prim_restart && idx == config.prim_restart)
continue;
}
indices.push_back(idx);
result[row].resize(col_num);
for (std::size_t col = 0; col < col_num; ++col) {
const auto &info = column_info[col];
const auto &column = config.columns[col];
const auto &prop = config.properties[col];
if(info.data)
{
const byte *bytes = info.data;
if(!prop.perinstance)
bytes += info.stride * idx;
const auto list = RenderDocVertexResolver::get_variables(prop.format, column, bytes, info.end);
const std::size_t comp_num = std::min<std::size_t>(list.size(), 4);
result[row][col].resize(comp_num);
for(std::size_t comp = 0; comp < comp_num; comp++)
{
const RenderDocVertexResolver::VertexVar &v = list[comp];
float f_val = 0.0f;
using Type = RenderDocVertexResolver::VertexVar::Type;
if (v.type == Type::Double)
f_val = static_cast<float>(v.value.d);
else if (v.type == Type::Float)
f_val = v.value.f;
else if (v.type == Type::UInt32 || v.type == Type::UInt16 || v.type == Type::UInt8)
f_val = static_cast<float>(v.value.ui32);
else if (v.type == Type::Int32 || v.type == Type::Int16 || v.type == Type::Int8)
f_val = static_cast<float>(v.value.i32);
else
continue;
result[row][col][comp] = f_val;
}
}
}
}
return result;
}
void RenderDocMeshPreviewService::calculate_vertices(const ActionDescription *action) {
const auto &pipe_state = controller->GetPipelineState();
// TODO: support buffer preview for MeshDispatch actions
auto is_mesh_dispatch = action->flags & ActionFlags::MeshDispatch;
auto is_drawcall = action->flags & ActionFlags::Drawcall;
if (!is_drawcall) {
last_stage_info = std::make_pair(action->eventId, rd::Wrapper<model::RdcVertexStageInOutputs>(nullptr));
return;
}
BufferConfig input_config;
BufferConfig output_config;
if (pipe_state.IsRestartEnabled() && action->flags & ActionFlags::Indexed) {
input_config.prim_restart = pipe_state.GetRestartIndex();
const auto byte_stride = pipe_state.GetIBuffer().byteStride;
if(byte_stride == 1)
input_config.prim_restart &= 0xff;
else if(byte_stride == 2)
input_config.prim_restart &= 0xffff;
output_config.prim_restart = input_config.prim_restart;
}
input_config.base_vertex = action->baseVertex;
calculate_input_rows(pipe_state, action, input_config);
collect_input_columns(pipe_state, input_config);
collect_output_columns(pipe_state, output_config);
constexpr int instance = 0; // TODO: handle multiple instances
auto post_data = controller->GetPostVSData(instance, 0, MeshDataStage::VSOut);
output_config.base_vertex = post_data.baseVertex;
calculate_output_rows(post_data, input_config, output_config);
fetch_buffers(action, pipe_state,post_data, input_config, output_config);
std::vector<uint32_t> in_indices, out_indices;
std::vector<rd::Wrapper<std::wstring>> in_columns, out_columns;
last_stage_info = std::make_pair(action->eventId, rd::wrapper::make_wrapper<model::RdcVertexStageInOutputs>(
translate_buffers_to_floats(input_config, in_columns, in_indices, instance),
in_columns,
in_indices,
translate_buffers_to_floats(output_config, out_columns, out_indices, instance),
out_columns,
out_indices
));
}
uint32_t RenderDocMeshPreviewService::get_vertex_index(const ActionDescription *action, uint32_t vertex_id) {
if (last_stage_info.first != action->eventId) {
calculate_vertices(action);
}
const auto &stage_info = last_stage_info.second;
const auto &indices = stage_info ? stage_info->get_input_indices() : std::vector<uint32_t>();
if (vertex_id >= indices.size())
return ~0U;
return stage_info->get_input_indices().at(vertex_id);
}
rd::Wrapper<model::RdcVertexStageInOutputs> RenderDocMeshPreviewService::get_vertices(const ActionDescription *action) {
if (last_stage_info.first != action->eventId) {
calculate_vertices(action);
}
return last_stage_info.second;
}
} // namespace jetbrains::renderdoc