831 lines
25 KiB
C++
831 lines
25 KiB
C++
#include "renderer_api/recording_renderer.h"
|
|
|
|
#include <new>
|
|
#include <utility>
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
template <typename Resource, typename Interface, typename... Args>
|
|
[[nodiscard]] pp::foundation::Result<std::unique_ptr<Interface>> make_recording_resource(
|
|
Args&&... args) noexcept
|
|
{
|
|
auto resource = std::unique_ptr<Resource>(new (std::nothrow) Resource(std::forward<Args>(args)...));
|
|
if (!resource) {
|
|
return pp::foundation::Result<std::unique_ptr<Interface>>::failure(
|
|
pp::foundation::Status::out_of_range("renderer resource allocation failed"));
|
|
}
|
|
|
|
std::unique_ptr<Interface> erased = std::move(resource);
|
|
return pp::foundation::Result<std::unique_ptr<Interface>>::success(std::move(erased));
|
|
}
|
|
|
|
}
|
|
|
|
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,
|
|
RenderPassDesc desc) noexcept
|
|
{
|
|
if (in_render_pass_) {
|
|
return pp::foundation::Status::invalid_argument("render pass is already active");
|
|
}
|
|
|
|
active_target_ = target.color_desc();
|
|
if (!has_texture_usage(active_target_.usage, TextureUsage::render_target)) {
|
|
return pp::foundation::Status::invalid_argument("render target texture must allow render_target usage");
|
|
}
|
|
|
|
const auto size_status = texture_byte_size(active_target_);
|
|
if (!size_status.ok()) {
|
|
return size_status.status();
|
|
}
|
|
|
|
const auto render_pass_status = validate_render_pass_desc(desc);
|
|
if (!render_pass_status.ok()) {
|
|
return render_pass_status;
|
|
}
|
|
|
|
in_render_pass_ = true;
|
|
shader_bound_ = false;
|
|
mesh_bound_ = false;
|
|
active_mesh_ = MeshDesc {};
|
|
push_command(commands_, RecordedRenderCommand {
|
|
.kind = RecordedRenderCommandKind::begin_render_pass,
|
|
.target_desc = active_target_,
|
|
.clear_color_enabled = desc.clear_color_enabled,
|
|
.clear_color = desc.clear_color,
|
|
.clear_depth_enabled = desc.clear_depth_enabled,
|
|
.clear_depth = desc.clear_depth,
|
|
.clear_stencil_enabled = desc.clear_stencil_enabled,
|
|
.clear_stencil = desc.clear_stencil,
|
|
});
|
|
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::set_shader_uniform(
|
|
const char* name,
|
|
std::span<const std::byte> bytes) 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 setting uniforms");
|
|
}
|
|
|
|
const auto status = validate_shader_uniform_write(name, bytes);
|
|
if (!status.ok()) {
|
|
return status;
|
|
}
|
|
|
|
push_command(commands_, RecordedRenderCommand {
|
|
.kind = RecordedRenderCommandKind::set_shader_uniform,
|
|
.uniform_bytes = static_cast<std::uint64_t>(bytes.size()),
|
|
.name = 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();
|
|
if (!has_texture_usage(desc.usage, TextureUsage::sampled)) {
|
|
return pp::foundation::Status::invalid_argument("bound texture must allow sampled usage");
|
|
}
|
|
|
|
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;
|
|
active_mesh_ = desc;
|
|
push_command(commands_, RecordedRenderCommand {
|
|
.kind = RecordedRenderCommandKind::bind_mesh,
|
|
.mesh_desc = desc,
|
|
});
|
|
return pp::foundation::Status::success();
|
|
}
|
|
|
|
pp::foundation::Status RecordingCommandContext::draw(DrawDesc desc) 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");
|
|
}
|
|
|
|
const auto draw_status = validate_draw_desc(active_mesh_, desc);
|
|
if (!draw_status.ok()) {
|
|
return draw_status;
|
|
}
|
|
|
|
push_command(commands_, RecordedRenderCommand {
|
|
.kind = RecordedRenderCommandKind::draw,
|
|
.mesh_desc = active_mesh_,
|
|
.draw_desc = desc,
|
|
});
|
|
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();
|
|
if (!has_texture_usage(desc.usage, TextureUsage::readback_source)) {
|
|
return pp::foundation::Status::invalid_argument("readback texture must allow readback_source usage");
|
|
}
|
|
|
|
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();
|
|
if (!has_texture_usage(desc.usage, TextureUsage::upload_destination)) {
|
|
return pp::foundation::Status::invalid_argument("texture upload destination must allow upload_destination usage");
|
|
}
|
|
|
|
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::generate_mipmaps(ITexture2D& texture) noexcept
|
|
{
|
|
if (in_render_pass_) {
|
|
return pp::foundation::Status::invalid_argument("mipmap generation must be outside a render pass");
|
|
}
|
|
|
|
const auto desc = texture.desc();
|
|
const auto desc_status = validate_mipmap_generation_desc(desc);
|
|
if (!desc_status.ok()) {
|
|
return desc_status;
|
|
}
|
|
|
|
const auto bytes = texture_byte_size(desc);
|
|
if (!bytes.ok()) {
|
|
return bytes.status();
|
|
}
|
|
|
|
push_command(commands_, RecordedRenderCommand {
|
|
.kind = RecordedRenderCommandKind::generate_mipmaps,
|
|
.texture_desc = desc,
|
|
.generated_mip_levels = desc.mip_levels,
|
|
.generated_mip_bytes = bytes.value(),
|
|
});
|
|
return pp::foundation::Status::success();
|
|
}
|
|
|
|
pp::foundation::Status RecordingCommandContext::transition_texture(
|
|
ITexture2D& texture,
|
|
TextureState before,
|
|
TextureState after) noexcept
|
|
{
|
|
if (in_render_pass_) {
|
|
return pp::foundation::Status::invalid_argument("texture transition must be outside a render pass");
|
|
}
|
|
|
|
const auto desc = texture.desc();
|
|
const auto desc_status = validate_texture_transition_desc(desc, before, after);
|
|
if (!desc_status.ok()) {
|
|
return desc_status;
|
|
}
|
|
|
|
push_command(commands_, RecordedRenderCommand {
|
|
.kind = RecordedRenderCommandKind::transition_texture,
|
|
.texture_desc = desc,
|
|
.before_state = before,
|
|
.after_state = after,
|
|
});
|
|
return pp::foundation::Status::success();
|
|
}
|
|
|
|
pp::foundation::Status RecordingCommandContext::copy_texture(
|
|
ITexture2D& source,
|
|
ReadbackRegion source_region,
|
|
ITexture2D& destination,
|
|
ReadbackRegion destination_region) noexcept
|
|
{
|
|
if (in_render_pass_) {
|
|
return pp::foundation::Status::invalid_argument("texture copy must be outside a render pass");
|
|
}
|
|
|
|
const auto source_desc = source.desc();
|
|
const auto destination_desc = destination.desc();
|
|
const auto desc_status = validate_texture_copy_descs(
|
|
source_desc,
|
|
source_region,
|
|
destination_desc,
|
|
destination_region);
|
|
if (!desc_status.ok()) {
|
|
return desc_status;
|
|
}
|
|
|
|
const auto source_bytes = readback_byte_size(source_desc, source_region);
|
|
if (!source_bytes.ok()) {
|
|
return source_bytes.status();
|
|
}
|
|
|
|
const auto destination_bytes = readback_byte_size(destination_desc, destination_region);
|
|
if (!destination_bytes.ok()) {
|
|
return destination_bytes.status();
|
|
}
|
|
|
|
push_command(commands_, RecordedRenderCommand {
|
|
.kind = RecordedRenderCommandKind::copy_texture,
|
|
.source_desc = source_desc,
|
|
.destination_desc = destination_desc,
|
|
.source_region = source_region,
|
|
.destination_region = destination_region,
|
|
.copy_source_bytes = source_bytes.value(),
|
|
.copy_destination_bytes = destination_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;
|
|
active_mesh_ = MeshDesc {};
|
|
}
|
|
|
|
bool RecordingCommandContext::in_render_pass() const noexcept
|
|
{
|
|
return in_render_pass_;
|
|
}
|
|
|
|
void RecordingCommandContext::reset() noexcept
|
|
{
|
|
active_target_ = TextureDesc {};
|
|
active_mesh_ = MeshDesc {};
|
|
in_render_pass_ = false;
|
|
shader_bound_ = false;
|
|
mesh_bound_ = false;
|
|
}
|
|
|
|
RecordingRenderTrace::RecordingRenderTrace(std::vector<RecordedRenderCommand>& commands) noexcept
|
|
: commands_(&commands)
|
|
{
|
|
}
|
|
|
|
pp::foundation::Status RecordingRenderTrace::marker(const char* component, const char* name) noexcept
|
|
{
|
|
const auto status = validate_trace_label(component, name);
|
|
if (!status.ok()) {
|
|
return status;
|
|
}
|
|
|
|
push_command(commands_, RecordedRenderCommand {
|
|
.kind = RecordedRenderCommandKind::trace_marker,
|
|
.component = non_null_name(component),
|
|
.name = non_null_name(name),
|
|
});
|
|
return pp::foundation::Status::success();
|
|
}
|
|
|
|
pp::foundation::Status RecordingRenderTrace::begin_scope(const char* component, const char* name) noexcept
|
|
{
|
|
const auto status = validate_trace_label(component, name);
|
|
if (!status.ok()) {
|
|
return status;
|
|
}
|
|
|
|
push_command(commands_, RecordedRenderCommand {
|
|
.kind = RecordedRenderCommandKind::trace_begin_scope,
|
|
.component = non_null_name(component),
|
|
.name = non_null_name(name),
|
|
});
|
|
++scope_depth_;
|
|
return pp::foundation::Status::success();
|
|
}
|
|
|
|
pp::foundation::Status RecordingRenderTrace::end_scope() noexcept
|
|
{
|
|
if (scope_depth_ == 0U) {
|
|
return pp::foundation::Status::invalid_argument("trace scope has not begun");
|
|
}
|
|
|
|
push_command(commands_, RecordedRenderCommand {
|
|
.kind = RecordedRenderCommandKind::trace_end_scope,
|
|
});
|
|
--scope_depth_;
|
|
return pp::foundation::Status::success();
|
|
}
|
|
|
|
void RecordingRenderTrace::reset() noexcept
|
|
{
|
|
scope_depth_ = 0U;
|
|
}
|
|
|
|
RecordingRenderDevice::RecordingRenderDevice() noexcept
|
|
: context_(commands_)
|
|
, trace_(commands_)
|
|
{
|
|
}
|
|
|
|
const char* RecordingRenderDevice::backend_name() const noexcept
|
|
{
|
|
return "recording";
|
|
}
|
|
|
|
RenderDeviceFeatures RecordingRenderDevice::features() const noexcept
|
|
{
|
|
return RenderDeviceFeatures {
|
|
.explicit_texture_transitions = true,
|
|
.texture_copy = true,
|
|
.render_target_blit = true,
|
|
.frame_capture = true,
|
|
};
|
|
}
|
|
|
|
pp::foundation::Result<std::unique_ptr<ITexture2D>> RecordingRenderDevice::create_texture(
|
|
TextureDesc desc) noexcept
|
|
{
|
|
const auto desc_status = validate_texture_desc(desc);
|
|
if (!desc_status.ok()) {
|
|
return pp::foundation::Result<std::unique_ptr<ITexture2D>>::failure(desc_status);
|
|
}
|
|
|
|
const auto bytes = texture_byte_size(desc);
|
|
if (!bytes.ok()) {
|
|
return pp::foundation::Result<std::unique_ptr<ITexture2D>>::failure(bytes.status());
|
|
}
|
|
|
|
return make_recording_resource<RecordingTexture2D, ITexture2D>(desc);
|
|
}
|
|
|
|
pp::foundation::Result<std::unique_ptr<IRenderTarget>> RecordingRenderDevice::create_render_target(
|
|
TextureDesc color_desc) noexcept
|
|
{
|
|
if (!has_texture_usage(color_desc.usage, TextureUsage::render_target)) {
|
|
return pp::foundation::Result<std::unique_ptr<IRenderTarget>>::failure(
|
|
pp::foundation::Status::invalid_argument("render target texture must allow render_target usage"));
|
|
}
|
|
|
|
const auto desc_status = validate_texture_desc(color_desc);
|
|
if (!desc_status.ok()) {
|
|
return pp::foundation::Result<std::unique_ptr<IRenderTarget>>::failure(desc_status);
|
|
}
|
|
|
|
const auto bytes = texture_byte_size(color_desc);
|
|
if (!bytes.ok()) {
|
|
return pp::foundation::Result<std::unique_ptr<IRenderTarget>>::failure(bytes.status());
|
|
}
|
|
|
|
return make_recording_resource<RecordingRenderTarget, IRenderTarget>(color_desc);
|
|
}
|
|
|
|
pp::foundation::Result<std::unique_ptr<IShaderProgram>> RecordingRenderDevice::create_shader_program(
|
|
ShaderProgramDesc desc) noexcept
|
|
{
|
|
const auto status = validate_shader_program_desc(desc);
|
|
if (!status.ok()) {
|
|
return pp::foundation::Result<std::unique_ptr<IShaderProgram>>::failure(status);
|
|
}
|
|
|
|
return make_recording_resource<RecordingShaderProgram, IShaderProgram>(desc.debug_name);
|
|
}
|
|
|
|
pp::foundation::Result<std::unique_ptr<IMesh>> RecordingRenderDevice::create_mesh(
|
|
MeshDesc desc) noexcept
|
|
{
|
|
const auto status = validate_mesh_desc(desc);
|
|
if (!status.ok()) {
|
|
return pp::foundation::Result<std::unique_ptr<IMesh>>::failure(status);
|
|
}
|
|
|
|
return make_recording_resource<RecordingMesh, IMesh>(desc);
|
|
}
|
|
|
|
pp::foundation::Result<std::unique_ptr<IReadbackBuffer>> RecordingRenderDevice::create_readback_buffer(
|
|
std::uint64_t size_bytes) noexcept
|
|
{
|
|
if (size_bytes == 0U) {
|
|
return pp::foundation::Result<std::unique_ptr<IReadbackBuffer>>::failure(
|
|
pp::foundation::Status::invalid_argument("readback buffer size must be greater than zero"));
|
|
}
|
|
|
|
if (size_bytes > max_texture_bytes) {
|
|
return pp::foundation::Result<std::unique_ptr<IReadbackBuffer>>::failure(
|
|
pp::foundation::Status::out_of_range("readback buffer size exceeds the configured limit"));
|
|
}
|
|
|
|
return make_recording_resource<RecordingReadbackBuffer, IReadbackBuffer>(size_bytes);
|
|
}
|
|
|
|
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();
|
|
context_.reset();
|
|
trace_.reset();
|
|
}
|
|
|
|
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::set_shader_uniform:
|
|
return "set_shader_uniform";
|
|
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::generate_mipmaps:
|
|
return "generate_mipmaps";
|
|
case RecordedRenderCommandKind::transition_texture:
|
|
return "transition_texture";
|
|
case RecordedRenderCommandKind::copy_texture:
|
|
return "copy_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";
|
|
case RecordedRenderCommandKind::trace_begin_scope:
|
|
return "trace_begin_scope";
|
|
case RecordedRenderCommandKind::trace_end_scope:
|
|
return "trace_end_scope";
|
|
}
|
|
|
|
return "unknown";
|
|
}
|
|
|
|
}
|