Extract renderer shader catalog
This commit is contained in:
@@ -150,7 +150,8 @@ target_link_libraries(pp_document
|
||||
pp_project_warnings)
|
||||
|
||||
add_library(pp_renderer_api STATIC
|
||||
src/renderer_api/renderer_api.cpp)
|
||||
src/renderer_api/renderer_api.cpp
|
||||
src/renderer_api/shader_catalog.cpp)
|
||||
target_include_directories(pp_renderer_api
|
||||
PUBLIC
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/src")
|
||||
@@ -209,6 +210,7 @@ if(PP_BUILD_APP)
|
||||
PUBLIC
|
||||
pp_project_options
|
||||
PRIVATE
|
||||
pp_renderer_api
|
||||
pp_project_warnings)
|
||||
|
||||
target_include_directories(pp_legacy_app
|
||||
|
||||
@@ -119,6 +119,9 @@ Known local toolchain state:
|
||||
- `panopainter_validate_shaders` validates the current combined GLSL shader
|
||||
files for one vertex stage marker, one fragment stage marker, valid marker
|
||||
order, and existing relative includes.
|
||||
- `pp_renderer_api` owns the canonical PanoPainter shader catalog consumed by
|
||||
the legacy OpenGL app initialization path; `pp_renderer_api_tests` validates
|
||||
catalog size, key entries, duplicate rejection, and bad path rejection.
|
||||
- `windows-msvc-vcpkg-headless` validates manifest install/configure/build/test
|
||||
for the current headless component matrix; see DEBT-0007 for remaining app
|
||||
and platform triplet migration.
|
||||
|
||||
@@ -383,8 +383,9 @@ adding new backends.
|
||||
Status: started. `pp_renderer_api` exists as a headless renderer-neutral target
|
||||
with texture descriptor, byte-size, viewport, mesh, readback bounds, command
|
||||
context, render device, shader program descriptor, mesh, render target,
|
||||
readback, and trace interface validation. OpenGL classes are not yet behind
|
||||
these interfaces.
|
||||
readback, trace interface validation, and the canonical PanoPainter shader
|
||||
catalog now consumed by the legacy OpenGL app initialization path. OpenGL
|
||||
classes are not yet behind these interfaces.
|
||||
|
||||
Implementation tasks:
|
||||
|
||||
@@ -592,7 +593,8 @@ Results:
|
||||
- `pp_document_ppi_import_tests` passed, including decoded PPI dirty-face
|
||||
payload attachment to `pp_document` layer/frame storage and out-of-range
|
||||
payload rejection.
|
||||
- `pp_renderer_api_tests` passed, including shader descriptor validation.
|
||||
- `pp_renderer_api_tests` passed, including shader descriptor validation,
|
||||
PanoPainter shader catalog validation, and invalid catalog rejection.
|
||||
- `pp_paint_renderer_compositor_tests` passed.
|
||||
- `pp_ui_core_color_tests` passed.
|
||||
- `pp_ui_core_layout_value_tests` passed.
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
#include "pch.h"
|
||||
#include "app.h"
|
||||
#include "renderer_api/shader_catalog.h"
|
||||
#include "shader.h"
|
||||
|
||||
void App::initShaders()
|
||||
{
|
||||
#ifdef _DEBUG
|
||||
if (!check_uniform_uniqueness())
|
||||
std::logic_error("check_uniform_uniqueness() failed");
|
||||
LOG("check_uniform_uniqueness() failed");
|
||||
#endif // _DEBUG
|
||||
|
||||
render_task([] {
|
||||
@@ -45,56 +46,19 @@ void App::initShaders()
|
||||
LOG("Shader Extension shader_framebuffer_fetch: %s", ShaderManager::ext_framebuffer_fetch ? "enabled" : "disabled");
|
||||
|
||||
LOG("initializing shaders");
|
||||
if (!ShaderManager::load(kShader::Texture, "data/shaders/texture.glsl"))
|
||||
LOG("Failed to create shader Texture");
|
||||
if (!ShaderManager::load(kShader::TextureAlpha, "data/shaders/texture-alpha.glsl"))
|
||||
LOG("Failed to create shader TextureAlpha");
|
||||
if (!ShaderManager::load(kShader::TextureMask, "data/shaders/texture-mask.glsl"))
|
||||
LOG("Failed to create shader TextureMask");
|
||||
if (!ShaderManager::load(kShader::TextureColorize, "data/shaders/texture-colorize.glsl"))
|
||||
LOG("Failed to create shader TextureColorize");
|
||||
if (!ShaderManager::load(kShader::TextureBlend, "data/shaders/texture-blend.glsl"))
|
||||
LOG("Failed to create shader TextureBlend");
|
||||
if (!ShaderManager::load(kShader::StrokePreview, "data/shaders/stroke-preview.glsl"))
|
||||
LOG("Failed to create shader StrokePreview");
|
||||
if (!ShaderManager::load(kShader::CompErase, "data/shaders/comp-erase.glsl"))
|
||||
LOG("Failed to create shader CompErase");
|
||||
if (!ShaderManager::load(kShader::CompDraw, "data/shaders/comp-draw.glsl"))
|
||||
LOG("Failed to create shader CompDraw");
|
||||
if (!ShaderManager::load(kShader::Color, "data/shaders/color.glsl"))
|
||||
LOG("Failed to create shader Color");
|
||||
if (!ShaderManager::load(kShader::ColorQuad, "data/shaders/color-quad.glsl"))
|
||||
LOG("Failed to create shader ColorQuad");
|
||||
if (!ShaderManager::load(kShader::ColorTri, "data/shaders/color-tri.glsl"))
|
||||
LOG("Failed to create shader ColorTri");
|
||||
if (!ShaderManager::load(kShader::ColorHue, "data/shaders/color-hue.glsl"))
|
||||
LOG("Failed to create shader ColorHue");
|
||||
if (!ShaderManager::load(kShader::UVs, "data/shaders/uvs.glsl"))
|
||||
LOG("Failed to create shader UVs");
|
||||
if (!ShaderManager::load(kShader::Font, "data/shaders/font.glsl"))
|
||||
LOG("Failed to create shader Font");
|
||||
if (!ShaderManager::load(kShader::Atlas, "data/shaders/atlas.glsl"))
|
||||
LOG("Failed to create shader Atlas");
|
||||
if (!ShaderManager::load(kShader::Stroke, "data/shaders/stroke.glsl"))
|
||||
LOG("Failed to create shader Stroke");
|
||||
if (!ShaderManager::load(kShader::StrokePad, "data/shaders/stroke-pad.glsl"))
|
||||
LOG("Failed to create shader StrokePad");
|
||||
if (!ShaderManager::load(kShader::StrokeDilate, "data/shaders/stroke-dilate.glsl"))
|
||||
LOG("Failed to create shader StrokeDilate");
|
||||
if (!ShaderManager::load(kShader::Checkerboard, "data/shaders/checkerboard.glsl"))
|
||||
LOG("Failed to create shader Checkerboard");
|
||||
if (!ShaderManager::load(kShader::Equirect, "data/shaders/equirect.glsl"))
|
||||
LOG("Failed to create shader Equirect");
|
||||
if (!ShaderManager::load(kShader::BrushStroke, "data/shaders/stroke-instanced.glsl"))
|
||||
LOG("Failed to create shader BrushStroke");
|
||||
if (!ShaderManager::load(kShader::VertexColor, "data/shaders/vertex-color.glsl"))
|
||||
LOG("Failed to create shader VertexColor");
|
||||
if (!ShaderManager::load(kShader::Lambert, "data/shaders/lambert.glsl"))
|
||||
LOG("Failed to create shader Lambert");
|
||||
if (!ShaderManager::load(kShader::LambertLightmap, "data/shaders/lightmap.glsl"))
|
||||
LOG("Failed to create shader LambertLightmap");
|
||||
if (!ShaderManager::load(kShader::BakeUV, "data/shaders/bake-uv.glsl"))
|
||||
LOG("Failed to create shader BakeUV");
|
||||
const auto shader_catalog = pp::renderer::panopainter_shader_catalog();
|
||||
const auto catalog_status = pp::renderer::validate_shader_catalog(shader_catalog);
|
||||
if (!catalog_status.ok())
|
||||
{
|
||||
LOG("Shader catalog validation failed: %s", catalog_status.message);
|
||||
return;
|
||||
}
|
||||
|
||||
for (const auto& shader : shader_catalog)
|
||||
{
|
||||
if (!ShaderManager::load(static_cast<kShader>(const_hash(shader.name)), shader.path))
|
||||
LOG("Failed to create shader %s", shader.name);
|
||||
}
|
||||
LOG("shaders initialized");
|
||||
}
|
||||
|
||||
|
||||
91
src/renderer_api/shader_catalog.cpp
Normal file
91
src/renderer_api/shader_catalog.cpp
Normal file
@@ -0,0 +1,91 @@
|
||||
#include "renderer_api/shader_catalog.h"
|
||||
|
||||
#include <array>
|
||||
#include <string_view>
|
||||
|
||||
namespace pp::renderer {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr std::array<ShaderCatalogEntry, 25> pano_catalog {
|
||||
ShaderCatalogEntry { .name = "texture", .path = "data/shaders/texture.glsl" },
|
||||
ShaderCatalogEntry { .name = "texture-alpha", .path = "data/shaders/texture-alpha.glsl" },
|
||||
ShaderCatalogEntry { .name = "texture-mask", .path = "data/shaders/texture-mask.glsl" },
|
||||
ShaderCatalogEntry { .name = "texture-colorize", .path = "data/shaders/texture-colorize.glsl" },
|
||||
ShaderCatalogEntry { .name = "texture-blend", .path = "data/shaders/texture-blend.glsl" },
|
||||
ShaderCatalogEntry { .name = "stroke-preview", .path = "data/shaders/stroke-preview.glsl" },
|
||||
ShaderCatalogEntry { .name = "comp-erase", .path = "data/shaders/comp-erase.glsl" },
|
||||
ShaderCatalogEntry { .name = "comp-draw", .path = "data/shaders/comp-draw.glsl" },
|
||||
ShaderCatalogEntry { .name = "color", .path = "data/shaders/color.glsl" },
|
||||
ShaderCatalogEntry { .name = "color-quad", .path = "data/shaders/color-quad.glsl" },
|
||||
ShaderCatalogEntry { .name = "color-tri", .path = "data/shaders/color-tri.glsl" },
|
||||
ShaderCatalogEntry { .name = "color-hue", .path = "data/shaders/color-hue.glsl" },
|
||||
ShaderCatalogEntry { .name = "uvs", .path = "data/shaders/uvs.glsl" },
|
||||
ShaderCatalogEntry { .name = "font", .path = "data/shaders/font.glsl" },
|
||||
ShaderCatalogEntry { .name = "atlas", .path = "data/shaders/atlas.glsl" },
|
||||
ShaderCatalogEntry { .name = "stroke", .path = "data/shaders/stroke.glsl" },
|
||||
ShaderCatalogEntry { .name = "stroke-pad", .path = "data/shaders/stroke-pad.glsl" },
|
||||
ShaderCatalogEntry { .name = "stroke-dilate", .path = "data/shaders/stroke-dilate.glsl" },
|
||||
ShaderCatalogEntry { .name = "checkerboard", .path = "data/shaders/checkerboard.glsl" },
|
||||
ShaderCatalogEntry { .name = "equirect", .path = "data/shaders/equirect.glsl" },
|
||||
ShaderCatalogEntry { .name = "brush-stroke", .path = "data/shaders/stroke-instanced.glsl" },
|
||||
ShaderCatalogEntry { .name = "vertex-color", .path = "data/shaders/vertex-color.glsl" },
|
||||
ShaderCatalogEntry { .name = "lambert", .path = "data/shaders/lambert.glsl" },
|
||||
ShaderCatalogEntry { .name = "lambert-lightmap", .path = "data/shaders/lightmap.glsl" },
|
||||
ShaderCatalogEntry { .name = "bakeuv", .path = "data/shaders/bake-uv.glsl" },
|
||||
};
|
||||
|
||||
[[nodiscard]] bool is_empty_c_string(const char* text) noexcept
|
||||
{
|
||||
return text == nullptr || text[0] == '\0';
|
||||
}
|
||||
|
||||
[[nodiscard]] bool has_shader_extension(std::string_view path) noexcept
|
||||
{
|
||||
constexpr std::string_view extension = ".glsl";
|
||||
return path.size() >= extension.size()
|
||||
&& path.substr(path.size() - extension.size()) == extension;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
std::span<const ShaderCatalogEntry> panopainter_shader_catalog() noexcept
|
||||
{
|
||||
return pano_catalog;
|
||||
}
|
||||
|
||||
pp::foundation::Status validate_shader_catalog(std::span<const ShaderCatalogEntry> catalog) noexcept
|
||||
{
|
||||
if (catalog.empty()) {
|
||||
return pp::foundation::Status::invalid_argument("shader catalog must not be empty");
|
||||
}
|
||||
|
||||
if (catalog.size() > max_shader_catalog_entries) {
|
||||
return pp::foundation::Status::out_of_range("shader catalog exceeds the configured limit");
|
||||
}
|
||||
|
||||
for (std::size_t i = 0; i < catalog.size(); ++i) {
|
||||
const auto& entry = catalog[i];
|
||||
if (is_empty_c_string(entry.name)) {
|
||||
return pp::foundation::Status::invalid_argument("shader catalog entry name must not be empty");
|
||||
}
|
||||
|
||||
if (is_empty_c_string(entry.path)) {
|
||||
return pp::foundation::Status::invalid_argument("shader catalog entry path must not be empty");
|
||||
}
|
||||
|
||||
if (!has_shader_extension(entry.path)) {
|
||||
return pp::foundation::Status::invalid_argument("shader catalog path must end with .glsl");
|
||||
}
|
||||
|
||||
for (std::size_t j = i + 1U; j < catalog.size(); ++j) {
|
||||
if (std::string_view(entry.name) == std::string_view(catalog[j].name)) {
|
||||
return pp::foundation::Status::invalid_argument("shader catalog entry name is duplicated");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
}
|
||||
21
src/renderer_api/shader_catalog.h
Normal file
21
src/renderer_api/shader_catalog.h
Normal file
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include "foundation/result.h"
|
||||
|
||||
#include <cstddef>
|
||||
#include <span>
|
||||
|
||||
namespace pp::renderer {
|
||||
|
||||
constexpr std::size_t max_shader_catalog_entries = 256;
|
||||
|
||||
struct ShaderCatalogEntry {
|
||||
const char* name = "";
|
||||
const char* path = "";
|
||||
};
|
||||
|
||||
[[nodiscard]] std::span<const ShaderCatalogEntry> panopainter_shader_catalog() noexcept;
|
||||
[[nodiscard]] pp::foundation::Status validate_shader_catalog(
|
||||
std::span<const ShaderCatalogEntry> catalog) noexcept;
|
||||
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
#include "renderer_api/renderer_api.h"
|
||||
#include "renderer_api/shader_catalog.h"
|
||||
#include "test_harness.h"
|
||||
|
||||
#include <array>
|
||||
#include <string_view>
|
||||
|
||||
using pp::foundation::StatusCode;
|
||||
@@ -22,12 +24,15 @@ using pp::renderer::TextureFormat;
|
||||
using pp::renderer::Viewport;
|
||||
using pp::renderer::max_shader_source_bytes;
|
||||
using pp::renderer::max_texture_dimension;
|
||||
using pp::renderer::panopainter_shader_catalog;
|
||||
using pp::renderer::primitive_topology_name;
|
||||
using pp::renderer::ShaderCatalogEntry;
|
||||
using pp::renderer::texture_byte_size;
|
||||
using pp::renderer::texture_format_name;
|
||||
using pp::renderer::validate_extent;
|
||||
using pp::renderer::validate_mesh_desc;
|
||||
using pp::renderer::validate_readback_region;
|
||||
using pp::renderer::validate_shader_catalog;
|
||||
using pp::renderer::validate_shader_program_desc;
|
||||
using pp::renderer::validate_viewport;
|
||||
|
||||
@@ -274,6 +279,65 @@ void validates_shader_program_descriptors(pp::tests::Harness& h)
|
||||
PP_EXPECT(h, excessive_source_status.code == StatusCode::out_of_range);
|
||||
}
|
||||
|
||||
void validates_panopainter_shader_catalog(pp::tests::Harness& h)
|
||||
{
|
||||
const auto catalog = panopainter_shader_catalog();
|
||||
|
||||
PP_EXPECT(h, catalog.size() == 25U);
|
||||
PP_EXPECT(h, validate_shader_catalog(catalog).ok());
|
||||
PP_EXPECT(h, catalog.front().name == std::string_view("texture"));
|
||||
PP_EXPECT(h, catalog.front().path == std::string_view("data/shaders/texture.glsl"));
|
||||
PP_EXPECT(h, catalog.back().name == std::string_view("bakeuv"));
|
||||
PP_EXPECT(h, catalog.back().path == std::string_view("data/shaders/bake-uv.glsl"));
|
||||
|
||||
bool found_stroke = false;
|
||||
bool found_brush_stroke = false;
|
||||
bool found_equirect = false;
|
||||
for (const auto& entry : catalog) {
|
||||
found_stroke = found_stroke || std::string_view(entry.name) == "stroke";
|
||||
found_brush_stroke = found_brush_stroke || std::string_view(entry.name) == "brush-stroke";
|
||||
found_equirect = found_equirect || std::string_view(entry.name) == "equirect";
|
||||
}
|
||||
|
||||
PP_EXPECT(h, found_stroke);
|
||||
PP_EXPECT(h, found_brush_stroke);
|
||||
PP_EXPECT(h, found_equirect);
|
||||
}
|
||||
|
||||
void rejects_invalid_shader_catalogs(pp::tests::Harness& h)
|
||||
{
|
||||
const std::array<ShaderCatalogEntry, 2> duplicated {
|
||||
ShaderCatalogEntry { .name = "texture", .path = "data/shaders/texture.glsl" },
|
||||
ShaderCatalogEntry { .name = "texture", .path = "data/shaders/texture-alpha.glsl" },
|
||||
};
|
||||
const std::array<ShaderCatalogEntry, 1> missing_name {
|
||||
ShaderCatalogEntry { .name = "", .path = "data/shaders/texture.glsl" },
|
||||
};
|
||||
const std::array<ShaderCatalogEntry, 1> missing_path {
|
||||
ShaderCatalogEntry { .name = "texture", .path = "" },
|
||||
};
|
||||
const std::array<ShaderCatalogEntry, 1> wrong_extension {
|
||||
ShaderCatalogEntry { .name = "texture", .path = "data/shaders/texture.txt" },
|
||||
};
|
||||
|
||||
const auto empty = validate_shader_catalog({});
|
||||
const auto duplicate = validate_shader_catalog(duplicated);
|
||||
const auto no_name = validate_shader_catalog(missing_name);
|
||||
const auto no_path = validate_shader_catalog(missing_path);
|
||||
const auto bad_extension = validate_shader_catalog(wrong_extension);
|
||||
|
||||
PP_EXPECT(h, !empty.ok());
|
||||
PP_EXPECT(h, empty.code == StatusCode::invalid_argument);
|
||||
PP_EXPECT(h, !duplicate.ok());
|
||||
PP_EXPECT(h, duplicate.code == StatusCode::invalid_argument);
|
||||
PP_EXPECT(h, !no_name.ok());
|
||||
PP_EXPECT(h, no_name.code == StatusCode::invalid_argument);
|
||||
PP_EXPECT(h, !no_path.ok());
|
||||
PP_EXPECT(h, no_path.code == StatusCode::invalid_argument);
|
||||
PP_EXPECT(h, !bad_extension.ok());
|
||||
PP_EXPECT(h, bad_extension.code == StatusCode::invalid_argument);
|
||||
}
|
||||
|
||||
void renderer_interfaces_support_backend_neutral_dispatch(pp::tests::Harness& h)
|
||||
{
|
||||
FakeRenderDevice device;
|
||||
@@ -310,6 +374,8 @@ int main()
|
||||
harness.run("validates_readback_bounds", validates_readback_bounds);
|
||||
harness.run("validates_viewports_and_mesh_descriptors", validates_viewports_and_mesh_descriptors);
|
||||
harness.run("validates_shader_program_descriptors", validates_shader_program_descriptors);
|
||||
harness.run("validates_panopainter_shader_catalog", validates_panopainter_shader_catalog);
|
||||
harness.run("rejects_invalid_shader_catalogs", rejects_invalid_shader_catalogs);
|
||||
harness.run("renderer_interfaces_support_backend_neutral_dispatch", renderer_interfaces_support_backend_neutral_dispatch);
|
||||
return harness.finish();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user