Files
panopainter/src/renderer_api/recording_renderer.cpp

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";
}
}