Route shader creation through renderer GL

This commit is contained in:
2026-06-03 07:03:50 +02:00
parent acdaf3bb8e
commit 1ae79ab3c1
6 changed files with 1123 additions and 67 deletions

View File

@@ -2,6 +2,7 @@
#include "renderer_gl/shader_bindings.h"
#include "test_harness.h"
#include <algorithm>
#include <array>
#include <cstdint>
#include <cstring>
@@ -111,6 +112,29 @@ struct RecordedOpenGlUniformScalarCall {
bool is_float = false;
};
struct RecordedOpenGlShaderSourceCall {
std::uint32_t shader = 0;
std::int32_t count = 0;
std::string_view source;
};
struct RecordedOpenGlProgramAttachCall {
std::uint32_t program = 0;
std::uint32_t shader = 0;
};
struct RecordedOpenGlAttribBindCall {
std::uint32_t program = 0;
std::uint32_t location = 0;
std::string_view name;
};
struct RecordedOpenGlActiveUniformCall {
std::uint32_t program = 0;
std::uint32_t index = 0;
std::int32_t capacity = 0;
};
std::vector<RecordedOpenGlStateCall> recorded_state_calls;
std::vector<std::uint32_t> recorded_string_queries;
std::vector<pp::renderer::gl::OpenGlDefaultClear> recorded_clear_calls;
@@ -138,9 +162,27 @@ std::vector<RecordedOpenGlUniformFloatVectorCall> recorded_uniform_vector_calls;
std::vector<RecordedOpenGlUniformScalarCall> recorded_uniform_scalar_calls;
std::vector<std::uint32_t> recorded_attrib_location_programs;
std::vector<std::string_view> recorded_attrib_location_names;
std::vector<std::uint32_t> recorded_created_shader_stages;
std::vector<RecordedOpenGlShaderSourceCall> recorded_shader_source_calls;
std::vector<std::uint32_t> recorded_compiled_shaders;
std::vector<std::uint32_t> recorded_deleted_shaders;
std::vector<std::uint32_t> recorded_shader_integer_queries;
std::vector<std::uint32_t> recorded_created_programs;
std::vector<RecordedOpenGlProgramAttachCall> recorded_program_attach_calls;
std::vector<std::uint32_t> recorded_linked_programs;
std::vector<RecordedOpenGlAttribBindCall> recorded_attrib_bind_calls;
std::vector<std::uint32_t> recorded_program_integer_queries;
std::vector<RecordedOpenGlActiveUniformCall> recorded_active_uniform_calls;
std::vector<std::uint32_t> recorded_uniform_location_programs;
std::vector<std::string_view> recorded_uniform_location_names;
std::uint32_t next_texture_id = 91U;
std::uint32_t next_framebuffer_id = 44U;
std::uint32_t next_sampler_id = 71U;
std::uint32_t next_shader_id = 301U;
std::uint32_t next_program_id = 401U;
std::int32_t configured_shader_compile_status = 1;
std::int32_t configured_program_link_status = 1;
std::int32_t configured_active_uniform_count = 2;
std::uint32_t configured_framebuffer_status = 0x8CD5U;
void record_enable(std::uint32_t state) noexcept
@@ -592,6 +634,164 @@ std::int32_t record_get_attrib_location(std::uint32_t program, const char* name)
return -1;
}
std::uint32_t record_create_shader(std::uint32_t stage) noexcept
{
recorded_created_shader_stages.push_back(stage);
return next_shader_id++;
}
void record_shader_source(std::uint32_t shader, std::int32_t count, const char* const* sources) noexcept
{
recorded_shader_source_calls.push_back(RecordedOpenGlShaderSourceCall {
.shader = shader,
.count = count,
.source = sources != nullptr && sources[0] != nullptr ? std::string_view { sources[0] } : std::string_view {},
});
}
void record_compile_shader(std::uint32_t shader) noexcept
{
recorded_compiled_shaders.push_back(shader);
}
void record_get_shader_integer(std::uint32_t shader, std::uint32_t query, std::int32_t* value) noexcept
{
recorded_shader_integer_queries.push_back(query);
if (value != nullptr) {
*value = configured_shader_compile_status;
}
(void)shader;
}
void record_get_shader_info_log(
std::uint32_t shader,
std::int32_t capacity,
std::int32_t* length,
char* info_log) noexcept
{
constexpr std::string_view log_text { "shader note" };
if (length != nullptr) {
*length = static_cast<std::int32_t>(log_text.size());
}
if (info_log != nullptr && capacity > 0) {
const auto copied = std::min<std::int32_t>(capacity - 1, static_cast<std::int32_t>(log_text.size()));
std::memcpy(info_log, log_text.data(), static_cast<std::size_t>(copied));
info_log[copied] = '\0';
}
(void)shader;
}
void record_delete_shader(std::uint32_t shader) noexcept
{
recorded_deleted_shaders.push_back(shader);
}
std::uint32_t record_create_program() noexcept
{
recorded_created_programs.push_back(next_program_id);
return next_program_id++;
}
void record_attach_shader(std::uint32_t program, std::uint32_t shader) noexcept
{
recorded_program_attach_calls.push_back(RecordedOpenGlProgramAttachCall {
.program = program,
.shader = shader,
});
}
void record_link_program(std::uint32_t program) noexcept
{
recorded_linked_programs.push_back(program);
}
void record_bind_attrib_location(std::uint32_t program, std::uint32_t location, const char* name) noexcept
{
recorded_attrib_bind_calls.push_back(RecordedOpenGlAttribBindCall {
.program = program,
.location = location,
.name = name == nullptr ? std::string_view {} : std::string_view { name },
});
}
void record_get_program_integer(std::uint32_t program, std::uint32_t query, std::int32_t* value) noexcept
{
recorded_program_integer_queries.push_back(query);
if (value == nullptr) {
return;
}
if (query == 0x8B82U) {
*value = configured_program_link_status;
} else if (query == 0x8B86U) {
*value = configured_active_uniform_count;
} else {
*value = -1;
}
(void)program;
}
void record_get_program_info_log(
std::uint32_t program,
std::int32_t capacity,
std::int32_t* length,
char* info_log) noexcept
{
constexpr std::string_view log_text { "program note" };
if (length != nullptr) {
*length = static_cast<std::int32_t>(log_text.size());
}
if (info_log != nullptr && capacity > 0) {
const auto copied = std::min<std::int32_t>(capacity - 1, static_cast<std::int32_t>(log_text.size()));
std::memcpy(info_log, log_text.data(), static_cast<std::size_t>(copied));
info_log[copied] = '\0';
}
(void)program;
}
void record_get_active_uniform(
std::uint32_t program,
std::uint32_t index,
std::int32_t capacity,
std::int32_t* length,
std::int32_t* size,
std::uint32_t* type,
char* name) noexcept
{
recorded_active_uniform_calls.push_back(RecordedOpenGlActiveUniformCall {
.program = program,
.index = index,
.capacity = capacity,
});
const std::string_view uniform_name = index == 0U ? std::string_view { "mvp" } : std::string_view { "tex" };
if (length != nullptr) {
*length = static_cast<std::int32_t>(uniform_name.size());
}
if (size != nullptr) {
*size = 1;
}
if (type != nullptr) {
*type = 0x8B5CU;
}
if (name != nullptr && capacity > 0) {
const auto copied = std::min<std::int32_t>(capacity - 1, static_cast<std::int32_t>(uniform_name.size()));
std::memcpy(name, uniform_name.data(), static_cast<std::size_t>(copied));
name[copied] = '\0';
}
}
std::int32_t record_get_uniform_location(std::uint32_t program, const char* name) noexcept
{
recorded_uniform_location_programs.push_back(program);
recorded_uniform_location_names.push_back(name == nullptr ? std::string_view {} : std::string_view { name });
if (name != nullptr && std::string_view { name } == std::string_view { "mvp" }) {
return 4;
}
if (name != nullptr && std::string_view { name } == std::string_view { "tex" }) {
return 7;
}
return -1;
}
void detects_common_extension_capabilities(pp::tests::Harness& h)
{
constexpr std::array<std::string_view, 2> extensions {
@@ -2256,6 +2456,321 @@ void rejects_invalid_program_uniform_dispatch(pp::tests::Harness& h)
PP_EXPECT(h, invalid_attrib_name.status().code == pp::foundation::StatusCode::invalid_argument);
}
void compiles_shader_source_through_dispatch(pp::tests::Harness& h)
{
recorded_created_shader_stages.clear();
recorded_shader_source_calls.clear();
recorded_compiled_shaders.clear();
recorded_shader_integer_queries.clear();
next_shader_id = 301U;
configured_shader_compile_status = 1;
std::array<char, 64> info_log {};
const auto shader = pp::renderer::gl::compile_opengl_shader_source(
0x8B31U,
"void main(){}",
info_log.data(),
static_cast<std::int32_t>(info_log.size()),
pp::renderer::gl::OpenGlShaderCompileDispatch {
.create_shader = record_create_shader,
.shader_source = record_shader_source,
.compile_shader = record_compile_shader,
.get_shader_integer = record_get_shader_integer,
.get_shader_info_log = record_get_shader_info_log,
});
PP_EXPECT(h, shader.ok());
PP_EXPECT(h, shader.value().shader_id == 301U);
PP_EXPECT(h, shader.value().compile_status == 1);
PP_EXPECT(h, shader.value().info_log_length == 11);
PP_EXPECT(h, std::string_view { info_log.data() } == std::string_view("shader note"));
PP_EXPECT(h, recorded_created_shader_stages.size() == 1U);
PP_EXPECT(h, recorded_created_shader_stages[0] == 0x8B31U);
PP_EXPECT(h, recorded_shader_source_calls.size() == 1U);
PP_EXPECT(h, recorded_shader_source_calls[0].shader == 301U);
PP_EXPECT(h, recorded_shader_source_calls[0].count == 1);
PP_EXPECT(h, recorded_shader_source_calls[0].source == std::string_view("void main(){}"));
PP_EXPECT(h, recorded_compiled_shaders.size() == 1U);
PP_EXPECT(h, recorded_compiled_shaders[0] == 301U);
PP_EXPECT(h, recorded_shader_integer_queries.size() == 1U);
PP_EXPECT(h, recorded_shader_integer_queries[0] == 0x8B81U);
}
void rejects_invalid_shader_compile_dispatch(pp::tests::Harness& h)
{
std::array<char, 16> info_log {};
const auto missing_dispatch = pp::renderer::gl::compile_opengl_shader_source(
0x8B31U,
"void main(){}",
info_log.data(),
static_cast<std::int32_t>(info_log.size()),
pp::renderer::gl::OpenGlShaderCompileDispatch {
.create_shader = record_create_shader,
.shader_source = record_shader_source,
});
const auto empty_source = pp::renderer::gl::compile_opengl_shader_source(
0x8B31U,
"",
info_log.data(),
static_cast<std::int32_t>(info_log.size()),
pp::renderer::gl::OpenGlShaderCompileDispatch {
.create_shader = record_create_shader,
.shader_source = record_shader_source,
.compile_shader = record_compile_shader,
.get_shader_integer = record_get_shader_integer,
.get_shader_info_log = record_get_shader_info_log,
});
const auto missing_log = pp::renderer::gl::compile_opengl_shader_source(
0x8B31U,
"void main(){}",
nullptr,
static_cast<std::int32_t>(info_log.size()),
pp::renderer::gl::OpenGlShaderCompileDispatch {
.create_shader = record_create_shader,
.shader_source = record_shader_source,
.compile_shader = record_compile_shader,
.get_shader_integer = record_get_shader_integer,
.get_shader_info_log = record_get_shader_info_log,
});
PP_EXPECT(h, !missing_dispatch.ok());
PP_EXPECT(h, missing_dispatch.status().code == pp::foundation::StatusCode::invalid_argument);
PP_EXPECT(h, !empty_source.ok());
PP_EXPECT(h, empty_source.status().code == pp::foundation::StatusCode::invalid_argument);
PP_EXPECT(h, !missing_log.ok());
PP_EXPECT(h, missing_log.status().code == pp::foundation::StatusCode::invalid_argument);
}
void deletes_shader_through_dispatch(pp::tests::Harness& h)
{
recorded_deleted_shaders.clear();
const auto delete_status = pp::renderer::gl::delete_opengl_shader(
31U,
pp::renderer::gl::OpenGlShaderDeleteDispatch {
.delete_shader = record_delete_shader,
});
const auto delete_zero_status = pp::renderer::gl::delete_opengl_shader(
0U,
pp::renderer::gl::OpenGlShaderDeleteDispatch {
.delete_shader = record_delete_shader,
});
PP_EXPECT(h, delete_status.ok());
PP_EXPECT(h, delete_zero_status.ok());
PP_EXPECT(h, recorded_deleted_shaders.size() == 1U);
PP_EXPECT(h, recorded_deleted_shaders[0] == 31U);
}
void links_shader_program_through_dispatch(pp::tests::Harness& h)
{
recorded_created_programs.clear();
recorded_program_attach_calls.clear();
recorded_deleted_shaders.clear();
recorded_linked_programs.clear();
recorded_attrib_location_programs.clear();
recorded_attrib_location_names.clear();
recorded_attrib_bind_calls.clear();
recorded_program_integer_queries.clear();
next_program_id = 401U;
configured_program_link_status = 1;
std::array<char, 64> info_log {};
const auto program = pp::renderer::gl::link_opengl_shader_program(
101U,
103U,
pp::renderer::gl::panopainter_shader_attribute_bindings(),
info_log.data(),
static_cast<std::int32_t>(info_log.size()),
pp::renderer::gl::OpenGlProgramLinkDispatch {
.create_program = record_create_program,
.attach_shader = record_attach_shader,
.delete_shader = record_delete_shader,
.link_program = record_link_program,
.get_attrib_location = record_get_attrib_location,
.bind_attrib_location = record_bind_attrib_location,
.get_program_integer = record_get_program_integer,
.get_program_info_log = record_get_program_info_log,
});
PP_EXPECT(h, program.ok());
PP_EXPECT(h, program.value().program_id == 401U);
PP_EXPECT(h, program.value().link_status == 1);
PP_EXPECT(h, program.value().info_log_length == 12);
PP_EXPECT(h, std::string_view { info_log.data() } == std::string_view("program note"));
PP_EXPECT(h, recorded_created_programs.size() == 1U);
PP_EXPECT(h, recorded_created_programs[0] == 401U);
PP_EXPECT(h, recorded_program_attach_calls.size() == 2U);
PP_EXPECT(h, recorded_program_attach_calls[0].shader == 101U);
PP_EXPECT(h, recorded_program_attach_calls[1].shader == 103U);
PP_EXPECT(h, recorded_deleted_shaders.size() == 2U);
PP_EXPECT(h, recorded_deleted_shaders[0] == 101U);
PP_EXPECT(h, recorded_deleted_shaders[1] == 103U);
PP_EXPECT(h, recorded_linked_programs.size() == 2U);
PP_EXPECT(h, recorded_linked_programs[0] == 401U);
PP_EXPECT(h, recorded_linked_programs[1] == 401U);
PP_EXPECT(h, recorded_attrib_bind_calls.size() == 1U);
PP_EXPECT(h, recorded_attrib_bind_calls[0].program == 401U);
PP_EXPECT(h, recorded_attrib_bind_calls[0].location == 1U);
PP_EXPECT(h, recorded_attrib_bind_calls[0].name == std::string_view("uvs"));
PP_EXPECT(h, recorded_program_integer_queries.size() == 1U);
PP_EXPECT(h, recorded_program_integer_queries[0] == 0x8B82U);
}
void rejects_invalid_shader_program_link_dispatch(pp::tests::Harness& h)
{
std::array<char, 16> info_log {};
const auto missing_dispatch = pp::renderer::gl::link_opengl_shader_program(
1U,
2U,
pp::renderer::gl::panopainter_shader_attribute_bindings(),
info_log.data(),
static_cast<std::int32_t>(info_log.size()),
pp::renderer::gl::OpenGlProgramLinkDispatch {
.create_program = record_create_program,
.attach_shader = record_attach_shader,
});
const auto zero_shader = pp::renderer::gl::link_opengl_shader_program(
0U,
2U,
pp::renderer::gl::panopainter_shader_attribute_bindings(),
info_log.data(),
static_cast<std::int32_t>(info_log.size()),
pp::renderer::gl::OpenGlProgramLinkDispatch {
.create_program = record_create_program,
.attach_shader = record_attach_shader,
.delete_shader = record_delete_shader,
.link_program = record_link_program,
.get_attrib_location = record_get_attrib_location,
.bind_attrib_location = record_bind_attrib_location,
.get_program_integer = record_get_program_integer,
.get_program_info_log = record_get_program_info_log,
});
const auto missing_log = pp::renderer::gl::link_opengl_shader_program(
1U,
2U,
pp::renderer::gl::panopainter_shader_attribute_bindings(),
nullptr,
static_cast<std::int32_t>(info_log.size()),
pp::renderer::gl::OpenGlProgramLinkDispatch {
.create_program = record_create_program,
.attach_shader = record_attach_shader,
.delete_shader = record_delete_shader,
.link_program = record_link_program,
.get_attrib_location = record_get_attrib_location,
.bind_attrib_location = record_bind_attrib_location,
.get_program_integer = record_get_program_integer,
.get_program_info_log = record_get_program_info_log,
});
PP_EXPECT(h, !missing_dispatch.ok());
PP_EXPECT(h, missing_dispatch.status().code == pp::foundation::StatusCode::invalid_argument);
PP_EXPECT(h, !zero_shader.ok());
PP_EXPECT(h, zero_shader.status().code == pp::foundation::StatusCode::invalid_argument);
PP_EXPECT(h, !missing_log.ok());
PP_EXPECT(h, missing_log.status().code == pp::foundation::StatusCode::invalid_argument);
}
void discovers_program_uniforms_through_dispatch(pp::tests::Harness& h)
{
recorded_program_integer_queries.clear();
recorded_active_uniform_calls.clear();
recorded_uniform_location_programs.clear();
recorded_uniform_location_names.clear();
configured_active_uniform_count = 2;
std::array<char, 64> name {};
const auto count = pp::renderer::gl::query_opengl_program_integer(
401U,
pp::renderer::gl::active_uniform_count_query(),
pp::renderer::gl::OpenGlProgramIntegerDispatch {
.get_program_integer = record_get_program_integer,
});
const auto first = pp::renderer::gl::get_opengl_active_uniform(
401U,
0U,
name.data(),
static_cast<std::int32_t>(name.size()),
pp::renderer::gl::OpenGlActiveUniformDispatch {
.get_active_uniform = record_get_active_uniform,
});
const auto location = pp::renderer::gl::get_opengl_uniform_location(
401U,
name.data(),
pp::renderer::gl::OpenGlUniformLocationDispatch {
.get_uniform_location = record_get_uniform_location,
});
PP_EXPECT(h, count.ok());
PP_EXPECT(h, count.value() == 2);
PP_EXPECT(h, first.ok());
PP_EXPECT(h, first.value().length == 3);
PP_EXPECT(h, first.value().size == 1);
PP_EXPECT(h, first.value().type == 0x8B5CU);
PP_EXPECT(h, std::string_view { name.data() } == std::string_view("mvp"));
PP_EXPECT(h, location.ok());
PP_EXPECT(h, location.value() == 4);
PP_EXPECT(h, recorded_program_integer_queries.size() == 1U);
PP_EXPECT(h, recorded_program_integer_queries[0] == 0x8B86U);
PP_EXPECT(h, recorded_active_uniform_calls.size() == 1U);
PP_EXPECT(h, recorded_active_uniform_calls[0].program == 401U);
PP_EXPECT(h, recorded_active_uniform_calls[0].index == 0U);
PP_EXPECT(h, recorded_uniform_location_names.size() == 1U);
PP_EXPECT(h, recorded_uniform_location_names[0] == std::string_view("mvp"));
}
void rejects_invalid_uniform_discovery_dispatch(pp::tests::Harness& h)
{
std::array<char, 16> name {};
const auto missing_program_integer = pp::renderer::gl::query_opengl_program_integer(
1U,
pp::renderer::gl::active_uniform_count_query(),
pp::renderer::gl::OpenGlProgramIntegerDispatch {});
const auto invalid_program_integer = pp::renderer::gl::query_opengl_program_integer(
0U,
pp::renderer::gl::active_uniform_count_query(),
pp::renderer::gl::OpenGlProgramIntegerDispatch {
.get_program_integer = record_get_program_integer,
});
const auto missing_active = pp::renderer::gl::get_opengl_active_uniform(
1U,
0U,
name.data(),
static_cast<std::int32_t>(name.size()),
pp::renderer::gl::OpenGlActiveUniformDispatch {});
const auto invalid_active_buffer = pp::renderer::gl::get_opengl_active_uniform(
1U,
0U,
nullptr,
static_cast<std::int32_t>(name.size()),
pp::renderer::gl::OpenGlActiveUniformDispatch {
.get_active_uniform = record_get_active_uniform,
});
const auto missing_uniform_location = pp::renderer::gl::get_opengl_uniform_location(
1U,
"mvp",
pp::renderer::gl::OpenGlUniformLocationDispatch {});
const auto invalid_uniform_name = pp::renderer::gl::get_opengl_uniform_location(
1U,
"",
pp::renderer::gl::OpenGlUniformLocationDispatch {
.get_uniform_location = record_get_uniform_location,
});
PP_EXPECT(h, !missing_program_integer.ok());
PP_EXPECT(h, missing_program_integer.status().code == pp::foundation::StatusCode::invalid_argument);
PP_EXPECT(h, !invalid_program_integer.ok());
PP_EXPECT(h, invalid_program_integer.status().code == pp::foundation::StatusCode::invalid_argument);
PP_EXPECT(h, !missing_active.ok());
PP_EXPECT(h, missing_active.status().code == pp::foundation::StatusCode::invalid_argument);
PP_EXPECT(h, !invalid_active_buffer.ok());
PP_EXPECT(h, invalid_active_buffer.status().code == pp::foundation::StatusCode::invalid_argument);
PP_EXPECT(h, !missing_uniform_location.ok());
PP_EXPECT(h, missing_uniform_location.status().code == pp::foundation::StatusCode::invalid_argument);
PP_EXPECT(h, !invalid_uniform_name.ok());
PP_EXPECT(h, invalid_uniform_name.status().code == pp::foundation::StatusCode::invalid_argument);
}
void updates_texture_2d_through_dispatch(pp::tests::Harness& h)
{
recorded_binding_calls.clear();
@@ -3043,6 +3558,13 @@ int main()
harness.run("sets_scalar_uniforms_through_dispatch", sets_scalar_uniforms_through_dispatch);
harness.run("queries_attribute_location_through_dispatch", queries_attribute_location_through_dispatch);
harness.run("rejects_invalid_program_uniform_dispatch", rejects_invalid_program_uniform_dispatch);
harness.run("compiles_shader_source_through_dispatch", compiles_shader_source_through_dispatch);
harness.run("rejects_invalid_shader_compile_dispatch", rejects_invalid_shader_compile_dispatch);
harness.run("deletes_shader_through_dispatch", deletes_shader_through_dispatch);
harness.run("links_shader_program_through_dispatch", links_shader_program_through_dispatch);
harness.run("rejects_invalid_shader_program_link_dispatch", rejects_invalid_shader_program_link_dispatch);
harness.run("discovers_program_uniforms_through_dispatch", discovers_program_uniforms_through_dispatch);
harness.run("rejects_invalid_uniform_discovery_dispatch", rejects_invalid_uniform_discovery_dispatch);
harness.run("updates_texture_2d_through_dispatch", updates_texture_2d_through_dispatch);
harness.run("generates_texture_2d_mipmaps_through_dispatch", generates_texture_2d_mipmaps_through_dispatch);
harness.run("reads_back_texture_2d_through_framebuffer_dispatch", reads_back_texture_2d_through_framebuffer_dispatch);