517 lines
14 KiB
C++
517 lines
14 KiB
C++
#include "renderer_api/recording_renderer.h"
|
|
|
|
namespace pp::renderer {
|
|
|
|
namespace {
|
|
|
|
[[nodiscard]] const char* non_null_name(const char* name) noexcept
|
|
{
|
|
return name == nullptr ? "" : name;
|
|
}
|
|
|
|
void push_command(
|
|
std::vector<RecordedRenderCommand>* commands,
|
|
RecordedRenderCommand command)
|
|
{
|
|
if (commands != nullptr) {
|
|
commands->push_back(command);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
RecordingTexture2D::RecordingTexture2D(TextureDesc desc) noexcept
|
|
: desc_(desc)
|
|
{
|
|
}
|
|
|
|
TextureDesc RecordingTexture2D::desc() const noexcept
|
|
{
|
|
return desc_;
|
|
}
|
|
|
|
RecordingRenderTarget::RecordingRenderTarget(TextureDesc color_desc) noexcept
|
|
: color_desc_(color_desc)
|
|
{
|
|
}
|
|
|
|
TextureDesc RecordingRenderTarget::color_desc() const noexcept
|
|
{
|
|
return color_desc_;
|
|
}
|
|
|
|
RecordingShaderProgram::RecordingShaderProgram(const char* debug_name) noexcept
|
|
: debug_name_(non_null_name(debug_name))
|
|
{
|
|
}
|
|
|
|
const char* RecordingShaderProgram::debug_name() const noexcept
|
|
{
|
|
return debug_name_;
|
|
}
|
|
|
|
RecordingMesh::RecordingMesh(MeshDesc desc) noexcept
|
|
: desc_(desc)
|
|
{
|
|
}
|
|
|
|
MeshDesc RecordingMesh::desc() const noexcept
|
|
{
|
|
return desc_;
|
|
}
|
|
|
|
RecordingReadbackBuffer::RecordingReadbackBuffer(std::uint64_t size_bytes) noexcept
|
|
: size_bytes_(size_bytes)
|
|
{
|
|
}
|
|
|
|
std::uint64_t RecordingReadbackBuffer::size_bytes() const noexcept
|
|
{
|
|
return size_bytes_;
|
|
}
|
|
|
|
RecordingCommandContext::RecordingCommandContext(std::vector<RecordedRenderCommand>& commands) noexcept
|
|
: commands_(&commands)
|
|
{
|
|
}
|
|
|
|
pp::foundation::Status RecordingCommandContext::begin_render_pass(
|
|
IRenderTarget& target,
|
|
ClearColor clear_color) noexcept
|
|
{
|
|
if (in_render_pass_) {
|
|
return pp::foundation::Status::invalid_argument("render pass is already active");
|
|
}
|
|
|
|
active_target_ = target.color_desc();
|
|
if (!active_target_.render_target) {
|
|
return pp::foundation::Status::invalid_argument("render target texture must be flagged as render_target");
|
|
}
|
|
|
|
const auto size_status = texture_byte_size(active_target_);
|
|
if (!size_status.ok()) {
|
|
return size_status.status();
|
|
}
|
|
|
|
in_render_pass_ = true;
|
|
shader_bound_ = false;
|
|
mesh_bound_ = false;
|
|
push_command(commands_, RecordedRenderCommand {
|
|
.kind = RecordedRenderCommandKind::begin_render_pass,
|
|
.target_desc = active_target_,
|
|
.clear_color = clear_color,
|
|
});
|
|
return pp::foundation::Status::success();
|
|
}
|
|
|
|
pp::foundation::Status RecordingCommandContext::set_viewport(Viewport viewport) noexcept
|
|
{
|
|
if (!in_render_pass_) {
|
|
return pp::foundation::Status::invalid_argument("render pass has not begun");
|
|
}
|
|
|
|
const auto status = validate_viewport(viewport, active_target_.extent);
|
|
if (!status.ok()) {
|
|
return status;
|
|
}
|
|
|
|
push_command(commands_, RecordedRenderCommand {
|
|
.kind = RecordedRenderCommandKind::set_viewport,
|
|
.viewport = viewport,
|
|
});
|
|
return pp::foundation::Status::success();
|
|
}
|
|
|
|
pp::foundation::Status RecordingCommandContext::set_scissor(ScissorRect scissor) noexcept
|
|
{
|
|
if (!in_render_pass_) {
|
|
return pp::foundation::Status::invalid_argument("render pass has not begun");
|
|
}
|
|
|
|
const auto status = validate_scissor(scissor, active_target_.extent);
|
|
if (!status.ok()) {
|
|
return status;
|
|
}
|
|
|
|
push_command(commands_, RecordedRenderCommand {
|
|
.kind = RecordedRenderCommandKind::set_scissor,
|
|
.scissor = scissor,
|
|
});
|
|
return pp::foundation::Status::success();
|
|
}
|
|
|
|
pp::foundation::Status RecordingCommandContext::set_blend_state(BlendState state) noexcept
|
|
{
|
|
if (!in_render_pass_) {
|
|
return pp::foundation::Status::invalid_argument("render pass has not begun");
|
|
}
|
|
|
|
const auto status = validate_blend_state(state);
|
|
if (!status.ok()) {
|
|
return status;
|
|
}
|
|
|
|
push_command(commands_, RecordedRenderCommand {
|
|
.kind = RecordedRenderCommandKind::set_blend_state,
|
|
.blend_state = state,
|
|
});
|
|
return pp::foundation::Status::success();
|
|
}
|
|
|
|
pp::foundation::Status RecordingCommandContext::set_depth_state(DepthState state) noexcept
|
|
{
|
|
if (!in_render_pass_) {
|
|
return pp::foundation::Status::invalid_argument("render pass has not begun");
|
|
}
|
|
|
|
const auto status = validate_depth_state(state);
|
|
if (!status.ok()) {
|
|
return status;
|
|
}
|
|
|
|
push_command(commands_, RecordedRenderCommand {
|
|
.kind = RecordedRenderCommandKind::set_depth_state,
|
|
.depth_state = state,
|
|
});
|
|
return pp::foundation::Status::success();
|
|
}
|
|
|
|
pp::foundation::Status RecordingCommandContext::bind_shader(IShaderProgram& shader) noexcept
|
|
{
|
|
if (!in_render_pass_) {
|
|
return pp::foundation::Status::invalid_argument("render pass has not begun");
|
|
}
|
|
|
|
shader_bound_ = true;
|
|
push_command(commands_, RecordedRenderCommand {
|
|
.kind = RecordedRenderCommandKind::bind_shader,
|
|
.name = shader.debug_name(),
|
|
});
|
|
return pp::foundation::Status::success();
|
|
}
|
|
|
|
pp::foundation::Status RecordingCommandContext::bind_texture(
|
|
std::uint32_t slot,
|
|
ITexture2D& texture) noexcept
|
|
{
|
|
if (!in_render_pass_) {
|
|
return pp::foundation::Status::invalid_argument("render pass has not begun");
|
|
}
|
|
|
|
const auto slot_status = validate_texture_slot(slot);
|
|
if (!slot_status.ok()) {
|
|
return slot_status;
|
|
}
|
|
|
|
const auto desc = texture.desc();
|
|
const auto size_status = texture_byte_size(desc);
|
|
if (!size_status.ok()) {
|
|
return size_status.status();
|
|
}
|
|
|
|
push_command(commands_, RecordedRenderCommand {
|
|
.kind = RecordedRenderCommandKind::bind_texture,
|
|
.texture_desc = desc,
|
|
.texture_slot = slot,
|
|
});
|
|
return pp::foundation::Status::success();
|
|
}
|
|
|
|
pp::foundation::Status RecordingCommandContext::bind_sampler(
|
|
std::uint32_t slot,
|
|
SamplerDesc sampler) noexcept
|
|
{
|
|
if (!in_render_pass_) {
|
|
return pp::foundation::Status::invalid_argument("render pass has not begun");
|
|
}
|
|
|
|
const auto slot_status = validate_texture_slot(slot);
|
|
if (!slot_status.ok()) {
|
|
return slot_status;
|
|
}
|
|
|
|
const auto sampler_status = validate_sampler_desc(sampler);
|
|
if (!sampler_status.ok()) {
|
|
return sampler_status;
|
|
}
|
|
|
|
push_command(commands_, RecordedRenderCommand {
|
|
.kind = RecordedRenderCommandKind::bind_sampler,
|
|
.sampler_desc = sampler,
|
|
.sampler_slot = slot,
|
|
});
|
|
return pp::foundation::Status::success();
|
|
}
|
|
|
|
pp::foundation::Status RecordingCommandContext::bind_mesh(IMesh& mesh) noexcept
|
|
{
|
|
if (!in_render_pass_) {
|
|
return pp::foundation::Status::invalid_argument("render pass has not begun");
|
|
}
|
|
|
|
const auto desc = mesh.desc();
|
|
const auto status = validate_mesh_desc(desc);
|
|
if (!status.ok()) {
|
|
return status;
|
|
}
|
|
|
|
mesh_bound_ = true;
|
|
push_command(commands_, RecordedRenderCommand {
|
|
.kind = RecordedRenderCommandKind::bind_mesh,
|
|
.mesh_desc = desc,
|
|
});
|
|
return pp::foundation::Status::success();
|
|
}
|
|
|
|
pp::foundation::Status RecordingCommandContext::draw() noexcept
|
|
{
|
|
if (!in_render_pass_) {
|
|
return pp::foundation::Status::invalid_argument("render pass has not begun");
|
|
}
|
|
if (!shader_bound_) {
|
|
return pp::foundation::Status::invalid_argument("shader must be bound before draw");
|
|
}
|
|
if (!mesh_bound_) {
|
|
return pp::foundation::Status::invalid_argument("mesh must be bound before draw");
|
|
}
|
|
|
|
push_command(commands_, RecordedRenderCommand {
|
|
.kind = RecordedRenderCommandKind::draw,
|
|
});
|
|
return pp::foundation::Status::success();
|
|
}
|
|
|
|
pp::foundation::Status RecordingCommandContext::read_texture(
|
|
ITexture2D& texture,
|
|
ReadbackRegion region,
|
|
IReadbackBuffer& destination) noexcept
|
|
{
|
|
if (in_render_pass_) {
|
|
return pp::foundation::Status::invalid_argument("readback must be outside a render pass");
|
|
}
|
|
|
|
const auto desc = texture.desc();
|
|
const auto bytes = readback_byte_size(desc, region);
|
|
if (!bytes) {
|
|
return bytes.status();
|
|
}
|
|
|
|
if (destination.size_bytes() < bytes.value()) {
|
|
return pp::foundation::Status::out_of_range("readback buffer is too small");
|
|
}
|
|
|
|
push_command(commands_, RecordedRenderCommand {
|
|
.kind = RecordedRenderCommandKind::read_texture,
|
|
.texture_desc = desc,
|
|
.readback_region = region,
|
|
.readback_bytes = bytes.value(),
|
|
});
|
|
return pp::foundation::Status::success();
|
|
}
|
|
|
|
pp::foundation::Status RecordingCommandContext::upload_texture(
|
|
ITexture2D& texture,
|
|
ReadbackRegion region,
|
|
std::span<const std::byte> rgba_or_channel_bytes) noexcept
|
|
{
|
|
if (in_render_pass_) {
|
|
return pp::foundation::Status::invalid_argument("texture upload must be outside a render pass");
|
|
}
|
|
|
|
const auto desc = texture.desc();
|
|
const auto bytes = readback_byte_size(desc, region);
|
|
if (!bytes) {
|
|
return bytes.status();
|
|
}
|
|
|
|
if (rgba_or_channel_bytes.size() != bytes.value()) {
|
|
return pp::foundation::Status::invalid_argument("texture upload byte size does not match the region");
|
|
}
|
|
|
|
push_command(commands_, RecordedRenderCommand {
|
|
.kind = RecordedRenderCommandKind::upload_texture,
|
|
.texture_desc = desc,
|
|
.readback_region = region,
|
|
.upload_bytes = bytes.value(),
|
|
});
|
|
return pp::foundation::Status::success();
|
|
}
|
|
|
|
pp::foundation::Status RecordingCommandContext::capture_frame(
|
|
IRenderTarget& target,
|
|
IReadbackBuffer& destination) noexcept
|
|
{
|
|
if (in_render_pass_) {
|
|
return pp::foundation::Status::invalid_argument("frame capture must be outside a render pass");
|
|
}
|
|
|
|
const auto desc = target.color_desc();
|
|
const auto bytes = frame_capture_byte_size(desc);
|
|
if (!bytes) {
|
|
return bytes.status();
|
|
}
|
|
|
|
if (destination.size_bytes() < bytes.value()) {
|
|
return pp::foundation::Status::out_of_range("frame capture buffer is too small");
|
|
}
|
|
|
|
push_command(commands_, RecordedRenderCommand {
|
|
.kind = RecordedRenderCommandKind::capture_frame,
|
|
.target_desc = desc,
|
|
.capture_bytes = bytes.value(),
|
|
});
|
|
return pp::foundation::Status::success();
|
|
}
|
|
|
|
pp::foundation::Status RecordingCommandContext::blit_render_target(
|
|
IRenderTarget& source,
|
|
ReadbackRegion source_region,
|
|
IRenderTarget& destination,
|
|
ReadbackRegion destination_region,
|
|
BlitFilter filter) noexcept
|
|
{
|
|
if (in_render_pass_) {
|
|
return pp::foundation::Status::invalid_argument("render target blit must be outside a render pass");
|
|
}
|
|
|
|
const auto source_desc = source.color_desc();
|
|
const auto destination_desc = destination.color_desc();
|
|
const auto desc_status = validate_blit_descs(source_desc, destination_desc);
|
|
if (!desc_status.ok()) {
|
|
return desc_status;
|
|
}
|
|
|
|
const auto filter_status = validate_blit_filter(filter);
|
|
if (!filter_status.ok()) {
|
|
return filter_status;
|
|
}
|
|
|
|
const auto source_bytes = readback_byte_size(source_desc, source_region);
|
|
if (!source_bytes) {
|
|
return source_bytes.status();
|
|
}
|
|
|
|
const auto destination_bytes = readback_byte_size(destination_desc, destination_region);
|
|
if (!destination_bytes) {
|
|
return destination_bytes.status();
|
|
}
|
|
|
|
push_command(commands_, RecordedRenderCommand {
|
|
.kind = RecordedRenderCommandKind::blit_render_target,
|
|
.source_desc = source_desc,
|
|
.destination_desc = destination_desc,
|
|
.source_region = source_region,
|
|
.destination_region = destination_region,
|
|
.blit_filter = filter,
|
|
.blit_source_bytes = source_bytes.value(),
|
|
.blit_destination_bytes = destination_bytes.value(),
|
|
});
|
|
return pp::foundation::Status::success();
|
|
}
|
|
|
|
void RecordingCommandContext::end_render_pass() noexcept
|
|
{
|
|
if (!in_render_pass_) {
|
|
return;
|
|
}
|
|
|
|
push_command(commands_, RecordedRenderCommand {
|
|
.kind = RecordedRenderCommandKind::end_render_pass,
|
|
});
|
|
in_render_pass_ = false;
|
|
shader_bound_ = false;
|
|
mesh_bound_ = false;
|
|
}
|
|
|
|
bool RecordingCommandContext::in_render_pass() const noexcept
|
|
{
|
|
return in_render_pass_;
|
|
}
|
|
|
|
RecordingRenderTrace::RecordingRenderTrace(std::vector<RecordedRenderCommand>& commands) noexcept
|
|
: commands_(&commands)
|
|
{
|
|
}
|
|
|
|
void RecordingRenderTrace::marker(const char* component, const char* name) noexcept
|
|
{
|
|
push_command(commands_, RecordedRenderCommand {
|
|
.kind = RecordedRenderCommandKind::trace_marker,
|
|
.component = non_null_name(component),
|
|
.name = non_null_name(name),
|
|
});
|
|
}
|
|
|
|
RecordingRenderDevice::RecordingRenderDevice() noexcept
|
|
: context_(commands_)
|
|
, trace_(commands_)
|
|
{
|
|
}
|
|
|
|
const char* RecordingRenderDevice::backend_name() const noexcept
|
|
{
|
|
return "recording";
|
|
}
|
|
|
|
ICommandContext& RecordingRenderDevice::immediate_context() noexcept
|
|
{
|
|
return context_;
|
|
}
|
|
|
|
IRenderTrace* RecordingRenderDevice::trace() noexcept
|
|
{
|
|
return &trace_;
|
|
}
|
|
|
|
std::span<const RecordedRenderCommand> RecordingRenderDevice::commands() const noexcept
|
|
{
|
|
return commands_;
|
|
}
|
|
|
|
void RecordingRenderDevice::clear() noexcept
|
|
{
|
|
commands_.clear();
|
|
}
|
|
|
|
const char* recorded_render_command_kind_name(RecordedRenderCommandKind kind) noexcept
|
|
{
|
|
switch (kind) {
|
|
case RecordedRenderCommandKind::begin_render_pass:
|
|
return "begin_render_pass";
|
|
case RecordedRenderCommandKind::set_viewport:
|
|
return "set_viewport";
|
|
case RecordedRenderCommandKind::set_scissor:
|
|
return "set_scissor";
|
|
case RecordedRenderCommandKind::set_blend_state:
|
|
return "set_blend_state";
|
|
case RecordedRenderCommandKind::set_depth_state:
|
|
return "set_depth_state";
|
|
case RecordedRenderCommandKind::bind_shader:
|
|
return "bind_shader";
|
|
case RecordedRenderCommandKind::bind_texture:
|
|
return "bind_texture";
|
|
case RecordedRenderCommandKind::bind_sampler:
|
|
return "bind_sampler";
|
|
case RecordedRenderCommandKind::bind_mesh:
|
|
return "bind_mesh";
|
|
case RecordedRenderCommandKind::draw:
|
|
return "draw";
|
|
case RecordedRenderCommandKind::upload_texture:
|
|
return "upload_texture";
|
|
case RecordedRenderCommandKind::read_texture:
|
|
return "read_texture";
|
|
case RecordedRenderCommandKind::capture_frame:
|
|
return "capture_frame";
|
|
case RecordedRenderCommandKind::blit_render_target:
|
|
return "blit_render_target";
|
|
case RecordedRenderCommandKind::end_render_pass:
|
|
return "end_render_pass";
|
|
case RecordedRenderCommandKind::trace_marker:
|
|
return "trace_marker";
|
|
}
|
|
|
|
return "unknown";
|
|
}
|
|
|
|
}
|