#include "renderer_api/renderer_api.h" #include #include namespace pp::renderer { namespace { [[nodiscard]] bool is_empty_c_string(const char* text) noexcept { return text == nullptr || text[0] == '\0'; } [[nodiscard]] pp::foundation::Status validate_shader_stage_source( ShaderStageSource source, const char* stage_name) noexcept { if (is_empty_c_string(source.entry_point)) { return pp::foundation::Status::invalid_argument(stage_name); } if (source.source == nullptr || source.source_size == 0U) { return pp::foundation::Status::invalid_argument("shader source must not be empty"); } if (source.source_size > max_shader_source_bytes) { return pp::foundation::Status::out_of_range("shader source exceeds the configured limit"); } return pp::foundation::Status::success(); } } std::uint32_t bytes_per_pixel(TextureFormat format) noexcept { switch (format) { case TextureFormat::rgba8: return 4; case TextureFormat::r8: return 1; case TextureFormat::depth24_stencil8: return 4; } return 0; } pp::foundation::Status validate_extent(Extent2D extent) noexcept { if (extent.width == 0 || extent.height == 0) { return pp::foundation::Status::invalid_argument("texture extent must be greater than zero"); } if (extent.width > max_texture_dimension || extent.height > max_texture_dimension) { return pp::foundation::Status::out_of_range("texture extent exceeds the configured limit"); } return pp::foundation::Status::success(); } pp::foundation::Result texture_byte_size(TextureDesc desc) noexcept { const auto extent_status = validate_extent(desc.extent); if (!extent_status.ok()) { return pp::foundation::Result::failure(extent_status); } const auto bpp = static_cast(bytes_per_pixel(desc.format)); if (bpp == 0) { return pp::foundation::Result::failure( pp::foundation::Status::invalid_argument("texture format is not supported")); } const auto width = static_cast(desc.extent.width); const auto height = static_cast(desc.extent.height); if (width > std::numeric_limits::max() / height) { return pp::foundation::Result::failure( pp::foundation::Status::out_of_range("texture size overflows uint64")); } const auto pixels = width * height; if (pixels > std::numeric_limits::max() / bpp) { return pp::foundation::Result::failure( pp::foundation::Status::out_of_range("texture byte size overflows uint64")); } const auto bytes = pixels * bpp; if (bytes > max_texture_bytes) { return pp::foundation::Result::failure( pp::foundation::Status::out_of_range("texture byte size exceeds the configured limit")); } return pp::foundation::Result::success(bytes); } pp::foundation::Status validate_viewport(Viewport viewport, Extent2D target_extent) noexcept { const auto extent_status = validate_extent(target_extent); if (!extent_status.ok()) { return extent_status; } if (viewport.x < 0 || viewport.y < 0) { return pp::foundation::Status::invalid_argument("viewport origin must be non-negative"); } if (viewport.width == 0 || viewport.height == 0) { return pp::foundation::Status::invalid_argument("viewport size must be greater than zero"); } if (!std::isfinite(viewport.min_depth) || !std::isfinite(viewport.max_depth)) { return pp::foundation::Status::invalid_argument("viewport depth range must be finite"); } if (viewport.min_depth < 0.0F || viewport.max_depth > 1.0F || viewport.min_depth > viewport.max_depth) { return pp::foundation::Status::out_of_range("viewport depth range must be within 0..1 and ordered"); } const auto x = static_cast(viewport.x); const auto y = static_cast(viewport.y); if (x > target_extent.width || y > target_extent.height) { return pp::foundation::Status::out_of_range("viewport origin is outside the render target"); } if (viewport.width > target_extent.width - x || viewport.height > target_extent.height - y) { return pp::foundation::Status::out_of_range("viewport exceeds render target bounds"); } return pp::foundation::Status::success(); } pp::foundation::Status validate_mesh_desc(MeshDesc desc) noexcept { if (desc.vertex_count == 0) { return pp::foundation::Status::invalid_argument("mesh must contain at least one vertex"); } if (desc.vertex_count > max_mesh_vertices || desc.index_count > max_mesh_vertices) { return pp::foundation::Status::out_of_range("mesh vertex or index count exceeds the configured limit"); } switch (desc.topology) { case PrimitiveTopology::triangles: case PrimitiveTopology::triangle_strip: case PrimitiveTopology::lines: return pp::foundation::Status::success(); } return pp::foundation::Status::invalid_argument("mesh topology is not supported"); } pp::foundation::Status validate_shader_program_desc(ShaderProgramDesc desc) noexcept { if (desc.debug_name == nullptr) { return pp::foundation::Status::invalid_argument("shader debug name must not be null"); } const auto vertex_status = validate_shader_stage_source( desc.vertex, "vertex shader entry point must not be empty"); if (!vertex_status.ok()) { return vertex_status; } const auto fragment_status = validate_shader_stage_source( desc.fragment, "fragment shader entry point must not be empty"); if (!fragment_status.ok()) { return fragment_status; } return pp::foundation::Status::success(); } pp::foundation::Status validate_readback_region(TextureDesc desc, ReadbackRegion region) noexcept { const auto extent_status = validate_extent(desc.extent); if (!extent_status.ok()) { return extent_status; } if (region.width == 0 || region.height == 0) { return pp::foundation::Status::invalid_argument("readback region must be greater than zero"); } if (region.x > desc.extent.width || region.y > desc.extent.height) { return pp::foundation::Status::out_of_range("readback origin is outside the texture"); } if (region.width > desc.extent.width - region.x || region.height > desc.extent.height - region.y) { return pp::foundation::Status::out_of_range("readback region exceeds texture bounds"); } return pp::foundation::Status::success(); } pp::foundation::Result readback_byte_size(TextureDesc desc, ReadbackRegion region) noexcept { const auto region_status = validate_readback_region(desc, region); if (!region_status.ok()) { return pp::foundation::Result::failure(region_status); } const auto bpp = static_cast(bytes_per_pixel(desc.format)); if (bpp == 0) { return pp::foundation::Result::failure( pp::foundation::Status::invalid_argument("texture format is not supported")); } const auto width = static_cast(region.width); const auto height = static_cast(region.height); if (width > std::numeric_limits::max() / height) { return pp::foundation::Result::failure( pp::foundation::Status::out_of_range("readback pixel count overflows uint64")); } const auto pixels = width * height; if (pixels > std::numeric_limits::max() / bpp) { return pp::foundation::Result::failure( pp::foundation::Status::out_of_range("readback byte size overflows uint64")); } const auto bytes = pixels * bpp; if (bytes > max_texture_bytes) { return pp::foundation::Result::failure( pp::foundation::Status::out_of_range("readback byte size exceeds the configured limit")); } return pp::foundation::Result::success(bytes); } pp::foundation::Result frame_capture_byte_size(TextureDesc desc) noexcept { if (!desc.render_target) { return pp::foundation::Result::failure( pp::foundation::Status::invalid_argument("frame capture source must be a render target")); } return texture_byte_size(desc); } pp::foundation::Status validate_blit_filter(BlitFilter filter) noexcept { switch (filter) { case BlitFilter::nearest: case BlitFilter::linear: return pp::foundation::Status::success(); } return pp::foundation::Status::invalid_argument("blit filter is not supported"); } pp::foundation::Status validate_blit_descs(TextureDesc source, TextureDesc destination) noexcept { if (!source.render_target || !destination.render_target) { return pp::foundation::Status::invalid_argument("blit endpoints must be render targets"); } if (source.format != destination.format) { return pp::foundation::Status::invalid_argument("blit endpoints must use matching texture formats"); } const auto source_status = texture_byte_size(source); if (!source_status.ok()) { return source_status.status(); } const auto destination_status = texture_byte_size(destination); if (!destination_status.ok()) { return destination_status.status(); } return pp::foundation::Status::success(); } const char* texture_format_name(TextureFormat format) noexcept { switch (format) { case TextureFormat::rgba8: return "rgba8"; case TextureFormat::r8: return "r8"; case TextureFormat::depth24_stencil8: return "depth24_stencil8"; } return "unknown"; } const char* primitive_topology_name(PrimitiveTopology topology) noexcept { switch (topology) { case PrimitiveTopology::triangles: return "triangles"; case PrimitiveTopology::triangle_strip: return "triangle_strip"; case PrimitiveTopology::lines: return "lines"; } return "unknown"; } const char* blit_filter_name(BlitFilter filter) noexcept { switch (filter) { case BlitFilter::nearest: return "nearest"; case BlitFilter::linear: return "linear"; } return "unknown"; } }