#include "renderer_gl/opengl_capabilities.h"
#include "renderer_gl/shader_bindings.h"
#include "test_harness.h"

#include <algorithm>
#include <array>
#include <cstdint>
#include <cstring>
#include <limits>
#include <string_view>
#include <vector>

namespace {

struct RecordedOpenGlStateCall {
    enum class Kind {
        enable,
        disable,
        blend_func,
        blend_equation,
        blend_equation_separate,
    };

    Kind kind = Kind::enable;
    std::uint32_t first = 0;
    std::uint32_t second = 0;
};

struct RecordedOpenGlBindingCall {
    enum class Kind {
        bind_framebuffer,
        use_program,
        active_texture,
        bind_texture,
        bind_sampler,
    };

    Kind kind = Kind::bind_framebuffer;
    std::uint32_t first = 0;
    std::uint32_t second = 0;
};

struct RecordedOpenGlTextureImageCall {
    std::uint32_t target = 0;
    std::int32_t level = 0;
    std::int32_t internal_format = 0;
    std::int32_t x = 0;
    std::int32_t y = 0;
    std::int32_t width = 0;
    std::int32_t height = 0;
    std::int32_t border = 0;
    std::uint32_t pixel_format = 0;
    std::uint32_t component_type = 0;
    const void* data = nullptr;
    bool sub_image = false;
};

struct RecordedOpenGlTextureParameterCall {
    std::uint32_t target = 0;
    std::uint32_t parameter = 0;
    float value = 0.0F;
};

struct RecordedOpenGlColorMaskCall {
    std::uint8_t r = 0;
    std::uint8_t g = 0;
    std::uint8_t b = 0;
    std::uint8_t a = 0;
};

struct RecordedOpenGlFramebufferTextureCopyCall {
    std::uint32_t target = 0;
    std::int32_t level = 0;
    std::int32_t destination_x = 0;
    std::int32_t destination_y = 0;
    std::int32_t source_x = 0;
    std::int32_t source_y = 0;
    std::int32_t width = 0;
    std::int32_t height = 0;
};

struct RecordedOpenGlFramebufferAttachmentCall {
    std::uint32_t target = 0;
    std::uint32_t attachment = 0;
    std::uint32_t texture_target = 0;
    std::uint32_t texture = 0;
    std::int32_t level = 0;
};

struct RecordedOpenGlRenderbufferStorageCall {
    std::uint32_t target = 0;
    std::uint32_t internal_format = 0;
    std::int32_t width = 0;
    std::int32_t height = 0;
};

struct RecordedOpenGlReadPixelsCall {
    std::int32_t x = 0;
    std::int32_t y = 0;
    std::int32_t width = 0;
    std::int32_t height = 0;
    std::uint32_t pixel_format = 0;
    std::uint32_t component_type = 0;
    void* pixels = nullptr;
};

struct RecordedOpenGlBlitFramebufferCall {
    std::int32_t source_x0 = 0;
    std::int32_t source_y0 = 0;
    std::int32_t source_x1 = 0;
    std::int32_t source_y1 = 0;
    std::int32_t destination_x0 = 0;
    std::int32_t destination_y0 = 0;
    std::int32_t destination_x1 = 0;
    std::int32_t destination_y1 = 0;
    std::uint32_t mask = 0;
    std::uint32_t filter = 0;
};

struct RecordedOpenGlSamplerParameterCall {
    std::uint32_t sampler = 0;
    std::uint32_t parameter = 0;
    std::int32_t value = 0;
};

struct RecordedOpenGlSamplerBorderCall {
    std::uint32_t sampler = 0;
    std::uint32_t parameter = 0;
    const float* values = nullptr;
};

struct RecordedOpenGlUniformFloatVectorCall {
    std::int32_t location = 0;
    std::int32_t count = 0;
    const float* values = nullptr;
    std::uint8_t transpose = 0;
    std::uint32_t component_count = 0;
};

struct RecordedOpenGlUniformScalarCall {
    std::int32_t location = 0;
    std::int32_t int_value = 0;
    float float_value = 0.0F;
    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;
};

struct RecordedOpenGlBufferBindCall {
    std::uint32_t target = 0;
    std::uint32_t buffer = 0;
};

struct RecordedOpenGlBufferDataCall {
    std::uint32_t target = 0;
    std::intptr_t byte_count = 0;
    const void* data = nullptr;
    std::uint32_t usage = 0;
};

struct RecordedOpenGlBufferMapCall {
    std::uint32_t target = 0;
    std::intptr_t offset = 0;
    std::intptr_t byte_count = 0;
    std::uint32_t access = 0;
};

struct RecordedOpenGlVertexAttribPointerCall {
    std::uint32_t index = 0;
    std::int32_t component_count = 0;
    std::uint32_t component_type = 0;
    std::uint8_t normalized = 0;
    std::int32_t stride = 0;
    const void* offset = nullptr;
};

struct RecordedOpenGlMeshDrawCall {
    bool indexed = false;
    std::uint32_t mode = 0;
    std::int32_t first = 0;
    std::int32_t count = 0;
    std::uint32_t index_type = 0;
    const void* index_offset = nullptr;
};

std::vector<RecordedOpenGlStateCall> recorded_state_calls;
std::vector<std::uint32_t> recorded_string_queries;
std::vector<std::uint32_t> recorded_indexed_string_queries;
std::vector<pp::renderer::gl::OpenGlDefaultClear> recorded_clear_calls;
std::vector<pp::renderer::gl::OpenGlViewportRect> recorded_viewport_calls;
std::vector<pp::renderer::gl::OpenGlScissorRect> recorded_scissor_calls;
std::vector<std::uint32_t> recorded_integer_queries;
std::vector<std::uint32_t> recorded_float_queries;
std::vector<std::uint32_t> recorded_boolean_queries;
std::vector<RecordedOpenGlColorMaskCall> recorded_color_mask_calls;
std::vector<std::uint32_t> recorded_active_texture_calls;
std::vector<RecordedOpenGlBindingCall> recorded_binding_calls;
std::vector<std::uint32_t> recorded_generated_texture_counts;
std::vector<std::uint32_t> recorded_deleted_textures;
std::vector<RecordedOpenGlTextureImageCall> recorded_texture_image_calls;
std::vector<RecordedOpenGlTextureParameterCall> recorded_texture_parameter_calls;
std::vector<RecordedOpenGlFramebufferTextureCopyCall> recorded_framebuffer_texture_copy_calls;
std::vector<std::uint32_t> recorded_mipmap_targets;
std::vector<std::uint32_t> recorded_generated_framebuffer_counts;
std::vector<std::uint32_t> recorded_deleted_framebuffers;
std::vector<RecordedOpenGlFramebufferAttachmentCall> recorded_framebuffer_attachment_calls;
std::vector<std::uint32_t> recorded_framebuffer_status_queries;
std::vector<std::uint32_t> recorded_generated_renderbuffer_counts;
std::vector<std::uint32_t> recorded_deleted_renderbuffers;
std::vector<std::uint32_t> recorded_bound_renderbuffers;
std::vector<RecordedOpenGlRenderbufferStorageCall> recorded_renderbuffer_storage_calls;
std::vector<RecordedOpenGlReadPixelsCall> recorded_read_pixels_calls;
std::vector<RecordedOpenGlBlitFramebufferCall> recorded_blit_framebuffer_calls;
std::vector<std::uint32_t> recorded_generated_sampler_counts;
std::vector<RecordedOpenGlSamplerParameterCall> recorded_sampler_parameter_calls;
std::vector<RecordedOpenGlSamplerBorderCall> recorded_sampler_border_calls;
std::vector<std::uint32_t> recorded_deleted_programs;
std::int32_t recorded_extension_count = 3;
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::vector<std::uint32_t> recorded_generated_buffer_counts;
std::vector<std::uint32_t> recorded_deleted_buffers;
std::vector<std::uint32_t> recorded_generated_vertex_array_counts;
std::vector<std::uint32_t> recorded_deleted_vertex_arrays;
std::vector<RecordedOpenGlBufferBindCall> recorded_buffer_bind_calls;
std::vector<RecordedOpenGlBufferDataCall> recorded_buffer_data_calls;
std::vector<RecordedOpenGlBufferMapCall> recorded_buffer_map_calls;
std::vector<std::uint32_t> recorded_buffer_unmap_calls;
std::vector<std::uint32_t> recorded_vertex_array_bind_calls;
std::vector<std::uint32_t> recorded_enabled_vertex_attributes;
std::vector<RecordedOpenGlVertexAttribPointerCall> recorded_vertex_attrib_pointer_calls;
std::vector<RecordedOpenGlMeshDrawCall> recorded_mesh_draw_calls;
std::uint32_t next_texture_id = 91U;
std::uint32_t next_framebuffer_id = 44U;
std::uint32_t next_renderbuffer_id = 81U;
std::uint32_t next_sampler_id = 71U;
std::uint32_t next_shader_id = 301U;
std::uint32_t next_program_id = 401U;
std::uint32_t next_buffer_id = 501U;
std::uint32_t next_vertex_array_id = 601U;
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
{
    recorded_state_calls.push_back(RecordedOpenGlStateCall {
        .kind = RecordedOpenGlStateCall::Kind::enable,
        .first = state,
    });
}

void record_disable(std::uint32_t state) noexcept
{
    recorded_state_calls.push_back(RecordedOpenGlStateCall {
        .kind = RecordedOpenGlStateCall::Kind::disable,
        .first = state,
    });
}

void record_blend_func(std::uint32_t source_factor, std::uint32_t destination_factor) noexcept
{
    recorded_state_calls.push_back(RecordedOpenGlStateCall {
        .kind = RecordedOpenGlStateCall::Kind::blend_func,
        .first = source_factor,
        .second = destination_factor,
    });
}

void record_blend_equation(std::uint32_t equation) noexcept
{
    recorded_state_calls.push_back(RecordedOpenGlStateCall {
        .kind = RecordedOpenGlStateCall::Kind::blend_equation,
        .first = equation,
    });
}

void record_blend_equation_separate(std::uint32_t color_equation, std::uint32_t alpha_equation) noexcept
{
    recorded_state_calls.push_back(RecordedOpenGlStateCall {
        .kind = RecordedOpenGlStateCall::Kind::blend_equation_separate,
        .first = color_equation,
        .second = alpha_equation,
    });
}

const char* record_string_query(std::uint32_t name) noexcept
{
    recorded_string_queries.push_back(name);
    switch (name) {
    case 0x1F02U:
        return "test-version";
    case 0x1F00U:
        return "test-vendor";
    case 0x1F01U:
        return "test-renderer";
    case 0x8B8CU:
        return "test-glsl";
    default:
        return "unexpected";
    }
}

const char* record_indexed_string_query(std::uint32_t name, std::uint32_t index) noexcept
{
    recorded_indexed_string_queries.push_back(name);
    if (name != 0x1F03U) {
        return "unexpected";
    }

    switch (index) {
    case 0U:
        return "GL_EXT_shader_framebuffer_fetch";
    case 1U:
        return "GL_EXT_map_buffer_alignment";
    case 2U:
        return "GL_OES_texture_float";
    default:
        return nullptr;
    }
}

void record_clear_color(float r, float g, float b, float a) noexcept
{
    recorded_clear_calls.push_back(pp::renderer::gl::OpenGlDefaultClear {
        .color = { r, g, b, a },
    });
}

void record_clear(std::uint32_t mask) noexcept
{
    recorded_clear_calls.push_back(pp::renderer::gl::OpenGlDefaultClear {
        .mask = mask,
    });
}

void record_viewport(std::int32_t x, std::int32_t y, std::int32_t width, std::int32_t height) noexcept
{
    recorded_viewport_calls.push_back(pp::renderer::gl::OpenGlViewportRect {
        .x = x,
        .y = y,
        .width = width,
        .height = height,
    });
}

void record_scissor(std::int32_t x, std::int32_t y, std::int32_t width, std::int32_t height) noexcept
{
    recorded_scissor_calls.push_back(pp::renderer::gl::OpenGlScissorRect {
        .enabled = 1U,
        .x = x,
        .y = y,
        .width = width,
        .height = height,
    });
}

std::uint8_t record_is_enabled(std::uint32_t state) noexcept
{
    if (state == 0x0BE2U) {
        return 1U;
    }
    if (state == 0x0B71U) {
        return 0U;
    }
    if (state == 0x0C11U) {
        return 1U;
    }
    return 0U;
}

void record_get_integer(std::uint32_t name, std::int32_t* value) noexcept
{
    recorded_integer_queries.push_back(name);
    switch (name) {
    case 0x821DU:
        *value = recorded_extension_count;
        break;
    case 0x0BA2U:
        value[0] = 2;
        value[1] = 4;
        value[2] = 640;
        value[3] = 320;
        break;
    case 0x8B8DU:
        *value = 42;
        break;
    case 0x8CA6U:
        *value = 7;
        break;
    case 0x8CAAU:
        *value = 9;
        break;
    case 0x84E0U:
        *value = 0x84C4;
        break;
    case 0x8514U:
        *value = 88;
        break;
    case 0x8069U:
        *value = 100 + static_cast<std::int32_t>(recorded_active_texture_calls.size());
        break;
    case 0x8919U:
        *value = 200 + static_cast<std::int32_t>(recorded_active_texture_calls.size());
        break;
    default:
        *value = -1;
        break;
    }
}

void record_get_float(std::uint32_t name, float* value) noexcept
{
    recorded_float_queries.push_back(name);
    if (name == 0x0C22U) {
        value[0] = 0.25F;
        value[1] = 0.5F;
        value[2] = 0.75F;
        value[3] = 1.0F;
    }
}

void record_get_boolean(std::uint32_t name, std::uint8_t* value) noexcept
{
    recorded_boolean_queries.push_back(name);
    if (name == 0x0C23U) {
        value[0] = 1U;
        value[1] = 0U;
        value[2] = 1U;
        value[3] = 1U;
    }
}

void record_color_mask(std::uint8_t r, std::uint8_t g, std::uint8_t b, std::uint8_t a) noexcept
{
    recorded_color_mask_calls.push_back(RecordedOpenGlColorMaskCall {
        .r = r,
        .g = g,
        .b = b,
        .a = a,
    });
}

void record_active_texture(std::uint32_t texture_unit) noexcept
{
    recorded_active_texture_calls.push_back(texture_unit);
}

void record_bind_framebuffer(std::uint32_t target, std::uint32_t framebuffer) noexcept
{
    recorded_binding_calls.push_back(RecordedOpenGlBindingCall {
        .kind = RecordedOpenGlBindingCall::Kind::bind_framebuffer,
        .first = target,
        .second = framebuffer,
    });
}

void record_use_program(std::uint32_t program) noexcept
{
    recorded_binding_calls.push_back(RecordedOpenGlBindingCall {
        .kind = RecordedOpenGlBindingCall::Kind::use_program,
        .first = program,
    });
}

void record_bind_texture(std::uint32_t target, std::uint32_t texture) noexcept
{
    recorded_binding_calls.push_back(RecordedOpenGlBindingCall {
        .kind = RecordedOpenGlBindingCall::Kind::bind_texture,
        .first = target,
        .second = texture,
    });
}

void record_bind_sampler(std::uint32_t unit, std::uint32_t sampler) noexcept
{
    recorded_binding_calls.push_back(RecordedOpenGlBindingCall {
        .kind = RecordedOpenGlBindingCall::Kind::bind_sampler,
        .first = unit,
        .second = sampler,
    });
}

void record_gen_textures(std::uint32_t count, std::uint32_t* ids) noexcept
{
    recorded_generated_texture_counts.push_back(count);
    for (std::uint32_t i = 0U; i < count; ++i) {
        ids[i] = next_texture_id + i;
    }
}

void record_delete_textures(std::uint32_t count, const std::uint32_t* ids) noexcept
{
    for (std::uint32_t i = 0U; i < count; ++i) {
        recorded_deleted_textures.push_back(ids[i]);
    }
}

void record_tex_image_2d(
    std::uint32_t target,
    std::int32_t level,
    std::int32_t internal_format,
    std::int32_t width,
    std::int32_t height,
    std::int32_t border,
    std::uint32_t pixel_format,
    std::uint32_t component_type,
    const void* data) noexcept
{
    recorded_texture_image_calls.push_back(RecordedOpenGlTextureImageCall {
        .target = target,
        .level = level,
        .internal_format = internal_format,
        .width = width,
        .height = height,
        .border = border,
        .pixel_format = pixel_format,
        .component_type = component_type,
        .data = data,
    });
}

void record_tex_sub_image_2d(
    std::uint32_t target,
    std::int32_t level,
    std::int32_t x,
    std::int32_t y,
    std::int32_t width,
    std::int32_t height,
    std::uint32_t pixel_format,
    std::uint32_t component_type,
    const void* data) noexcept
{
    recorded_texture_image_calls.push_back(RecordedOpenGlTextureImageCall {
        .target = target,
        .level = level,
        .x = x,
        .y = y,
        .width = width,
        .height = height,
        .pixel_format = pixel_format,
        .component_type = component_type,
        .data = data,
        .sub_image = true,
    });
}

void record_tex_parameter_f(std::uint32_t target, std::uint32_t parameter, float value) noexcept
{
    recorded_texture_parameter_calls.push_back(RecordedOpenGlTextureParameterCall {
        .target = target,
        .parameter = parameter,
        .value = value,
    });
}

void record_copy_tex_sub_image_2d(
    std::uint32_t target,
    std::int32_t level,
    std::int32_t destination_x,
    std::int32_t destination_y,
    std::int32_t source_x,
    std::int32_t source_y,
    std::int32_t width,
    std::int32_t height) noexcept
{
    recorded_framebuffer_texture_copy_calls.push_back(RecordedOpenGlFramebufferTextureCopyCall {
        .target = target,
        .level = level,
        .destination_x = destination_x,
        .destination_y = destination_y,
        .source_x = source_x,
        .source_y = source_y,
        .width = width,
        .height = height,
    });
}

void record_generate_mipmap(std::uint32_t target) noexcept
{
    recorded_mipmap_targets.push_back(target);
}

void record_gen_framebuffers(std::uint32_t count, std::uint32_t* ids) noexcept
{
    recorded_generated_framebuffer_counts.push_back(count);
    for (std::uint32_t i = 0U; i < count; ++i) {
        ids[i] = next_framebuffer_id + i;
    }
}

void record_delete_framebuffers(std::uint32_t count, const std::uint32_t* ids) noexcept
{
    for (std::uint32_t i = 0U; i < count; ++i) {
        recorded_deleted_framebuffers.push_back(ids[i]);
    }
}

void record_gen_renderbuffers(std::uint32_t count, std::uint32_t* ids) noexcept
{
    recorded_generated_renderbuffer_counts.push_back(count);
    for (std::uint32_t i = 0U; i < count; ++i) {
        ids[i] = next_renderbuffer_id + i;
    }
}

void record_delete_renderbuffers(std::uint32_t count, const std::uint32_t* ids) noexcept
{
    for (std::uint32_t i = 0U; i < count; ++i) {
        recorded_deleted_renderbuffers.push_back(ids[i]);
    }
}

void record_bind_renderbuffer(std::uint32_t target, std::uint32_t renderbuffer) noexcept
{
    recorded_bound_renderbuffers.push_back(target);
    recorded_bound_renderbuffers.push_back(renderbuffer);
}

void record_renderbuffer_storage(
    std::uint32_t target,
    std::uint32_t internal_format,
    std::int32_t width,
    std::int32_t height) noexcept
{
    recorded_renderbuffer_storage_calls.push_back(RecordedOpenGlRenderbufferStorageCall {
        .target = target,
        .internal_format = internal_format,
        .width = width,
        .height = height,
    });
}

void record_framebuffer_texture_2d(
    std::uint32_t target,
    std::uint32_t attachment,
    std::uint32_t texture_target,
    std::uint32_t texture,
    std::int32_t level) noexcept
{
    recorded_framebuffer_attachment_calls.push_back(RecordedOpenGlFramebufferAttachmentCall {
        .target = target,
        .attachment = attachment,
        .texture_target = texture_target,
        .texture = texture,
        .level = level,
    });
}

void record_framebuffer_renderbuffer(
    std::uint32_t target,
    std::uint32_t attachment,
    std::uint32_t renderbuffer_target,
    std::uint32_t renderbuffer) noexcept
{
    recorded_framebuffer_attachment_calls.push_back(RecordedOpenGlFramebufferAttachmentCall {
        .target = target,
        .attachment = attachment,
        .texture_target = renderbuffer_target,
        .texture = renderbuffer,
    });
}

std::uint32_t record_check_framebuffer_status(std::uint32_t target) noexcept
{
    recorded_framebuffer_status_queries.push_back(target);
    return configured_framebuffer_status;
}

void record_read_pixels(
    std::int32_t x,
    std::int32_t y,
    std::int32_t width,
    std::int32_t height,
    std::uint32_t pixel_format,
    std::uint32_t component_type,
    void* pixels) noexcept
{
    recorded_read_pixels_calls.push_back(RecordedOpenGlReadPixelsCall {
        .x = x,
        .y = y,
        .width = width,
        .height = height,
        .pixel_format = pixel_format,
        .component_type = component_type,
        .pixels = pixels,
    });
}

void record_blit_framebuffer(
    std::int32_t source_x0,
    std::int32_t source_y0,
    std::int32_t source_x1,
    std::int32_t source_y1,
    std::int32_t destination_x0,
    std::int32_t destination_y0,
    std::int32_t destination_x1,
    std::int32_t destination_y1,
    std::uint32_t mask,
    std::uint32_t filter) noexcept
{
    recorded_blit_framebuffer_calls.push_back(RecordedOpenGlBlitFramebufferCall {
        .source_x0 = source_x0,
        .source_y0 = source_y0,
        .source_x1 = source_x1,
        .source_y1 = source_y1,
        .destination_x0 = destination_x0,
        .destination_y0 = destination_y0,
        .destination_x1 = destination_x1,
        .destination_y1 = destination_y1,
        .mask = mask,
        .filter = filter,
    });
}

void record_gen_samplers(std::uint32_t count, std::uint32_t* ids) noexcept
{
    recorded_generated_sampler_counts.push_back(count);
    for (std::uint32_t i = 0U; i < count; ++i) {
        ids[i] = next_sampler_id + i;
    }
}

void record_sampler_parameter_i(
    std::uint32_t sampler,
    std::uint32_t parameter,
    std::int32_t value) noexcept
{
    recorded_sampler_parameter_calls.push_back(RecordedOpenGlSamplerParameterCall {
        .sampler = sampler,
        .parameter = parameter,
        .value = value,
    });
}

void record_sampler_parameter_fv(
    std::uint32_t sampler,
    std::uint32_t parameter,
    const float* values) noexcept
{
    recorded_sampler_border_calls.push_back(RecordedOpenGlSamplerBorderCall {
        .sampler = sampler,
        .parameter = parameter,
        .values = values,
    });
}

void record_delete_program(std::uint32_t program) noexcept
{
    recorded_deleted_programs.push_back(program);
}

void record_uniform_4fv(std::int32_t location, std::int32_t count, const float* values) noexcept
{
    recorded_uniform_vector_calls.push_back(RecordedOpenGlUniformFloatVectorCall {
        .location = location,
        .count = count,
        .values = values,
        .component_count = 4U,
    });
}

void record_uniform_3fv(std::int32_t location, std::int32_t count, const float* values) noexcept
{
    recorded_uniform_vector_calls.push_back(RecordedOpenGlUniformFloatVectorCall {
        .location = location,
        .count = count,
        .values = values,
        .component_count = 3U,
    });
}

void record_uniform_2fv(std::int32_t location, std::int32_t count, const float* values) noexcept
{
    recorded_uniform_vector_calls.push_back(RecordedOpenGlUniformFloatVectorCall {
        .location = location,
        .count = count,
        .values = values,
        .component_count = 2U,
    });
}

void record_uniform_matrix_4fv(
    std::int32_t location,
    std::int32_t count,
    std::uint8_t transpose,
    const float* values) noexcept
{
    recorded_uniform_vector_calls.push_back(RecordedOpenGlUniformFloatVectorCall {
        .location = location,
        .count = count,
        .values = values,
        .transpose = transpose,
        .component_count = 16U,
    });
}

void record_uniform_1i(std::int32_t location, std::int32_t value) noexcept
{
    recorded_uniform_scalar_calls.push_back(RecordedOpenGlUniformScalarCall {
        .location = location,
        .int_value = value,
    });
}

void record_uniform_1f(std::int32_t location, float value) noexcept
{
    recorded_uniform_scalar_calls.push_back(RecordedOpenGlUniformScalarCall {
        .location = location,
        .float_value = value,
        .is_float = true,
    });
}

std::int32_t record_get_attrib_location(std::uint32_t program, const char* name) noexcept
{
    recorded_attrib_location_programs.push_back(program);
    recorded_attrib_location_names.push_back(name == nullptr ? std::string_view {} : std::string_view { name });
    if (name != nullptr && std::string_view { name } == std::string_view { "uvs" }) {
        return 1;
    }
    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 record_gen_buffers(std::uint32_t count, std::uint32_t* ids) noexcept
{
    recorded_generated_buffer_counts.push_back(count);
    if (ids == nullptr) {
        return;
    }
    for (std::uint32_t i = 0; i < count; ++i) {
        ids[i] = next_buffer_id++;
    }
}

void record_delete_buffers(std::uint32_t count, const std::uint32_t* ids) noexcept
{
    for (std::uint32_t i = 0; i < count; ++i) {
        recorded_deleted_buffers.push_back(ids == nullptr ? 0U : ids[i]);
    }
}

void record_bind_buffer(std::uint32_t target, std::uint32_t buffer) noexcept
{
    recorded_buffer_bind_calls.push_back(RecordedOpenGlBufferBindCall {
        .target = target,
        .buffer = buffer,
    });
}

void record_buffer_data(
    std::uint32_t target,
    std::intptr_t byte_count,
    const void* data,
    std::uint32_t usage) noexcept
{
    recorded_buffer_data_calls.push_back(RecordedOpenGlBufferDataCall {
        .target = target,
        .byte_count = byte_count,
        .data = data,
        .usage = usage,
    });
}

void* record_map_buffer_range(
    std::uint32_t target,
    std::intptr_t offset,
    std::intptr_t byte_count,
    std::uint32_t access) noexcept
{
    recorded_buffer_map_calls.push_back(RecordedOpenGlBufferMapCall {
        .target = target,
        .offset = offset,
        .byte_count = byte_count,
        .access = access,
    });
    return reinterpret_cast<void*>(0x1234U);
}

void* record_failed_map_buffer_range(
    std::uint32_t target,
    std::intptr_t offset,
    std::intptr_t byte_count,
    std::uint32_t access) noexcept
{
    recorded_buffer_map_calls.push_back(RecordedOpenGlBufferMapCall {
        .target = target,
        .offset = offset,
        .byte_count = byte_count,
        .access = access,
    });
    return nullptr;
}

void record_unmap_buffer(std::uint32_t target) noexcept
{
    recorded_buffer_unmap_calls.push_back(target);
}

void record_gen_vertex_arrays(std::uint32_t count, std::uint32_t* ids) noexcept
{
    recorded_generated_vertex_array_counts.push_back(count);
    if (ids == nullptr) {
        return;
    }
    for (std::uint32_t i = 0; i < count; ++i) {
        ids[i] = next_vertex_array_id++;
    }
}

void record_delete_vertex_arrays(std::uint32_t count, const std::uint32_t* ids) noexcept
{
    for (std::uint32_t i = 0; i < count; ++i) {
        recorded_deleted_vertex_arrays.push_back(ids == nullptr ? 0U : ids[i]);
    }
}

void record_bind_vertex_array(std::uint32_t vertex_array) noexcept
{
    recorded_vertex_array_bind_calls.push_back(vertex_array);
}

void record_enable_vertex_attrib_array(std::uint32_t index) noexcept
{
    recorded_enabled_vertex_attributes.push_back(index);
}

void record_vertex_attrib_pointer(
    std::uint32_t index,
    std::int32_t component_count,
    std::uint32_t component_type,
    std::uint8_t normalized,
    std::int32_t stride,
    const void* offset) noexcept
{
    recorded_vertex_attrib_pointer_calls.push_back(RecordedOpenGlVertexAttribPointerCall {
        .index = index,
        .component_count = component_count,
        .component_type = component_type,
        .normalized = normalized,
        .stride = stride,
        .offset = offset,
    });
}

void record_draw_elements(
    std::uint32_t mode,
    std::int32_t count,
    std::uint32_t index_type,
    const void* index_offset) noexcept
{
    recorded_mesh_draw_calls.push_back(RecordedOpenGlMeshDrawCall {
        .indexed = true,
        .mode = mode,
        .count = count,
        .index_type = index_type,
        .index_offset = index_offset,
    });
}

void record_draw_arrays(
    std::uint32_t mode,
    std::int32_t first,
    std::int32_t count) noexcept
{
    recorded_mesh_draw_calls.push_back(RecordedOpenGlMeshDrawCall {
        .indexed = false,
        .mode = mode,
        .first = first,
        .count = count,
    });
}

void detects_common_extension_capabilities(pp::tests::Harness& h)
{
    constexpr std::array<std::string_view, 2> extensions {
        "GL_EXT_shader_framebuffer_fetch",
        "GL_ARB_map_buffer_alignment",
    };

    PP_EXPECT(h, pp::renderer::gl::extension_count_query() == 0x821DU);
    PP_EXPECT(h, pp::renderer::gl::extension_string_name() == 0x1F03U);
    PP_EXPECT(h, pp::renderer::gl::no_error_code() == 0U);
    PP_EXPECT(h, pp::renderer::gl::opengl_error_name(0U) == std::string_view("GL_NO_ERROR"));
    PP_EXPECT(h, pp::renderer::gl::opengl_error_name(0x0500U) == std::string_view("GL_INVALID_ENUM"));
    PP_EXPECT(h, pp::renderer::gl::opengl_error_name(0x0501U) == std::string_view("GL_INVALID_VALUE"));
    PP_EXPECT(h, pp::renderer::gl::opengl_error_name(0x0502U) == std::string_view("GL_INVALID_OPERATION"));
    PP_EXPECT(h, pp::renderer::gl::opengl_error_name(0x0505U) == std::string_view("GL_OUT_OF_MEMORY"));
    PP_EXPECT(h, pp::renderer::gl::opengl_error_name(0x0506U)
                     == std::string_view("GL_INVALID_FRAMEBUFFER_OPERATION"));
    PP_EXPECT(h, pp::renderer::gl::opengl_error_name(0xffffffffU) == std::string_view("Unknown"));

    const auto capabilities = pp::renderer::gl::detect_opengl_capabilities(
        extensions,
        pp::renderer::gl::OpenGlRuntime {});

    PP_EXPECT(h, capabilities.framebuffer_fetch);
    PP_EXPECT(h, capabilities.map_buffer_alignment);
    PP_EXPECT(h, !capabilities.float32_textures);
    PP_EXPECT(h, !capabilities.float16_textures);

    const auto features = pp::renderer::gl::render_device_features(capabilities);
    PP_EXPECT(h, features.framebuffer_fetch);
    PP_EXPECT(h, !features.explicit_texture_transitions);
    PP_EXPECT(h, features.texture_copy);
    PP_EXPECT(h, features.render_target_blit);
    PP_EXPECT(h, features.frame_capture);
    PP_EXPECT(h, !features.float16_render_targets);
    PP_EXPECT(h, !features.float32_render_targets);
    PP_EXPECT(h, !features.float32_linear_filtering);
}

void treats_desktop_gl_float_rendering_as_core(pp::tests::Harness& h)
{
    const auto capabilities = pp::renderer::gl::detect_opengl_capabilities(
        {},
        pp::renderer::gl::OpenGlRuntime { .desktop_gl = true });

    PP_EXPECT(h, capabilities.float32_textures);
    PP_EXPECT(h, capabilities.float32_linear);
    PP_EXPECT(h, capabilities.float16_textures);

    const auto features = pp::renderer::gl::render_device_features(capabilities);
    PP_EXPECT(h, features.float16_render_targets);
    PP_EXPECT(h, features.float32_render_targets);
    PP_EXPECT(h, features.float32_linear_filtering);
}

void detects_feature_state_from_extension_capabilities(pp::tests::Harness& h)
{
    constexpr std::array<std::string_view, 2> extensions {
        "GL_EXT_shader_framebuffer_fetch",
        "GL_ARB_map_buffer_alignment",
    };

    const auto feature_state = pp::renderer::gl::detect_opengl_feature_state(
        extensions,
        pp::renderer::gl::OpenGlRuntime {});

    PP_EXPECT(h, feature_state.capabilities.framebuffer_fetch);
    PP_EXPECT(h, feature_state.capabilities.map_buffer_alignment);
    PP_EXPECT(h, !feature_state.capabilities.float32_textures);
    PP_EXPECT(h, feature_state.features.framebuffer_fetch);
    PP_EXPECT(h, feature_state.features.texture_copy);
    PP_EXPECT(h, feature_state.features.render_target_blit);
    PP_EXPECT(h, !feature_state.features.float32_render_targets);
    PP_EXPECT(h, !feature_state.features.float32_linear_filtering);
}

void detects_desktop_feature_state_without_extensions(pp::tests::Harness& h)
{
    const auto feature_state = pp::renderer::gl::detect_opengl_feature_state(
        {},
        pp::renderer::gl::OpenGlRuntime { .desktop_gl = true });

    PP_EXPECT(h, !feature_state.capabilities.framebuffer_fetch);
    PP_EXPECT(h, feature_state.capabilities.float32_textures);
    PP_EXPECT(h, feature_state.capabilities.float32_linear);
    PP_EXPECT(h, feature_state.capabilities.float16_textures);
    PP_EXPECT(h, feature_state.features.float32_render_targets);
    PP_EXPECT(h, feature_state.features.float16_render_targets);
    PP_EXPECT(h, feature_state.features.float32_linear_filtering);
}

void detects_gles_texture_float_extensions(pp::tests::Harness& h)
{
    constexpr std::array<std::string_view, 3> extensions {
        "GL_OES_texture_float",
        "GL_OES_texture_float_linear",
        "GL_EXT_color_buffer_half_float",
    };

    const auto capabilities = pp::renderer::gl::detect_opengl_capabilities(
        extensions,
        pp::renderer::gl::OpenGlRuntime { .gles = true });

    PP_EXPECT(h, capabilities.float32_textures);
    PP_EXPECT(h, capabilities.float32_linear);
    PP_EXPECT(h, capabilities.float16_textures);
}

void ignores_gles_texture_extensions_for_webgl_runtime(pp::tests::Harness& h)
{
    constexpr std::array<std::string_view, 3> extensions {
        "GL_OES_texture_float",
        "GL_OES_texture_float_linear",
        "GL_EXT_color_buffer_half_float",
    };

    const auto capabilities = pp::renderer::gl::detect_opengl_capabilities(
        extensions,
        pp::renderer::gl::OpenGlRuntime { .gles = true, .web = true });

    PP_EXPECT(h, !capabilities.float32_textures);
    PP_EXPECT(h, !capabilities.float32_linear);
    PP_EXPECT(h, !capabilities.float16_textures);
}

void classifies_current_build_opengl_runtime(pp::tests::Harness& h)
{
    const auto runtime = pp::renderer::gl::opengl_runtime_for_current_build();

    PP_EXPECT(h, runtime.desktop_gl || runtime.gles);
    PP_EXPECT(h, !(runtime.desktop_gl && runtime.gles));
    if (runtime.web)
        PP_EXPECT(h, runtime.gles);
}

void selects_texture_upload_type_from_internal_format(pp::tests::Harness& h)
{
    constexpr std::uint32_t gl_unsigned_byte = 0x1401U;
    constexpr std::uint32_t gl_float = 0x1406U;
    constexpr std::uint32_t gl_half_float = 0x140BU;
    constexpr std::uint32_t gl_rgba8 = 0x8058U;
    constexpr std::uint32_t gl_rgba32f = 0x8814U;
    constexpr std::uint32_t gl_rgba16f = 0x881AU;

    PP_EXPECT(h, pp::renderer::gl::texture_upload_type_for_internal_format(gl_rgba8) == gl_unsigned_byte);
    PP_EXPECT(h, pp::renderer::gl::texture_upload_type_for_internal_format(gl_rgba32f) == gl_float);
    PP_EXPECT(h, pp::renderer::gl::texture_upload_type_for_internal_format(gl_rgba16f) == gl_half_float);
    PP_EXPECT(h, pp::renderer::gl::texture_upload_type_for_internal_format(0U) == gl_unsigned_byte);
    PP_EXPECT(h, pp::renderer::gl::unsigned_byte_component_type() == gl_unsigned_byte);
    PP_EXPECT(h, pp::renderer::gl::rgba_pixel_format() == 0x1908U);
}

void maps_image_channel_count_to_texture_format(pp::tests::Harness& h)
{
    constexpr std::uint32_t gl_red = 0x1903U;
    constexpr std::uint32_t gl_rgb = 0x1907U;
    constexpr std::uint32_t gl_rgba = 0x1908U;
    constexpr std::uint32_t gl_rg = 0x8227U;
    constexpr std::uint32_t gl_r8 = 0x8229U;
    constexpr std::uint32_t gl_rg8 = 0x822BU;
    constexpr std::uint32_t gl_rgb8 = 0x8051U;
    constexpr std::uint32_t gl_rgba8 = 0x8058U;

    const auto r = pp::renderer::gl::texture_format_for_channel_count(1U);
    const auto rg = pp::renderer::gl::texture_format_for_channel_count(2U);
    const auto rgb = pp::renderer::gl::texture_format_for_channel_count(3U);
    const auto rgba = pp::renderer::gl::texture_format_for_channel_count(4U);
    const auto invalid = pp::renderer::gl::texture_format_for_channel_count(5U);

    PP_EXPECT(h, r.internal_format == gl_r8);
    PP_EXPECT(h, r.pixel_format == gl_red);
    PP_EXPECT(h, rg.internal_format == gl_rg8);
    PP_EXPECT(h, rg.pixel_format == gl_rg);
    PP_EXPECT(h, rgb.internal_format == gl_rgb8);
    PP_EXPECT(h, rgb.pixel_format == gl_rgb);
    PP_EXPECT(h, rgba.internal_format == gl_rgba8);
    PP_EXPECT(h, rgba.pixel_format == gl_rgba);
    PP_EXPECT(h, invalid.channel_count == 0U);
    PP_EXPECT(h, invalid.internal_format == 0U);
    PP_EXPECT(h, invalid.pixel_format == 0U);
}

void maps_renderer_texture_formats_to_opengl_tokens(pp::tests::Harness& h)
{
    const auto rgba8 = pp::renderer::gl::texture_format_for_renderer_format(pp::renderer::TextureFormat::rgba8);
    const auto r8 = pp::renderer::gl::texture_format_for_renderer_format(pp::renderer::TextureFormat::r8);
    const auto depth_stencil = pp::renderer::gl::texture_format_for_renderer_format(
        pp::renderer::TextureFormat::depth24_stencil8);
    const auto invalid = pp::renderer::gl::texture_format_for_renderer_format(
        static_cast<pp::renderer::TextureFormat>(255U));

    PP_EXPECT(h, rgba8.internal_format == 0x8058U);
    PP_EXPECT(h, rgba8.pixel_format == 0x1908U);
    PP_EXPECT(h, rgba8.component_type == 0x1401U);
    PP_EXPECT(h, rgba8.bytes_per_pixel == 4U);
    PP_EXPECT(h, r8.internal_format == 0x8229U);
    PP_EXPECT(h, r8.pixel_format == 0x1903U);
    PP_EXPECT(h, r8.component_type == 0x1401U);
    PP_EXPECT(h, r8.bytes_per_pixel == 1U);
    PP_EXPECT(h, depth_stencil.internal_format == 0x88F0U);
    PP_EXPECT(h, depth_stencil.pixel_format == 0x84F9U);
    PP_EXPECT(h, depth_stencil.component_type == 0x84FAU);
    PP_EXPECT(h, depth_stencil.bytes_per_pixel == 4U);
    PP_EXPECT(h, invalid.internal_format == 0U);
    PP_EXPECT(h, invalid.pixel_format == 0U);
    PP_EXPECT(h, invalid.component_type == 0U);
    PP_EXPECT(h, invalid.bytes_per_pixel == 0U);
}

void maps_readback_formats(pp::tests::Harness& h)
{
    const auto rgba8 = pp::renderer::gl::rgba8_readback_format();
    const auto rgba32f = pp::renderer::gl::rgba32f_readback_format();

    PP_EXPECT(h, rgba8.pixel_format == 0x1908U);
    PP_EXPECT(h, rgba8.component_type == 0x1401U);
    PP_EXPECT(h, rgba8.bytes_per_pixel == 4U);
    PP_EXPECT(h, pp::renderer::gl::readback_byte_count(rgba8, 3U, 5U) == 60U);
    PP_EXPECT(h, pp::renderer::gl::readback_byte_count(rgba8, 0U, 5U) == 0U);

    PP_EXPECT(h, rgba32f.pixel_format == 0x1908U);
    PP_EXPECT(h, rgba32f.component_type == 0x1406U);
    PP_EXPECT(h, rgba32f.bytes_per_pixel == 16U);
    PP_EXPECT(h, pp::renderer::gl::readback_byte_count(rgba32f, 3U, 5U) == 240U);

    const auto invalid_format = pp::renderer::gl::OpenGlReadbackFormat {
        .pixel_format = rgba8.pixel_format,
        .component_type = rgba8.component_type,
        .bytes_per_pixel = 0U,
    };
    const auto overflowing_format = pp::renderer::gl::OpenGlReadbackFormat {
        .pixel_format = rgba8.pixel_format,
        .component_type = rgba8.component_type,
        .bytes_per_pixel = std::numeric_limits<std::uint32_t>::max(),
    };

    PP_EXPECT(h, pp::renderer::gl::readback_byte_count(invalid_format, 1U, 1U) == 0U);
    PP_EXPECT(h, pp::renderer::gl::readback_byte_count(
                     overflowing_format,
                     std::numeric_limits<std::uint32_t>::max(),
                     std::numeric_limits<std::uint32_t>::max())
                     == 0U);
}

void maps_pixel_buffer_parameters(pp::tests::Harness& h)
{
    PP_EXPECT(h, pp::renderer::gl::pixel_pack_buffer_target() == 0x88EBU);
    PP_EXPECT(h, pp::renderer::gl::pixel_unpack_buffer_target() == 0x88ECU);
    PP_EXPECT(h, pp::renderer::gl::pixel_buffer_stream_read_usage() == 0x88E1U);
    PP_EXPECT(h, pp::renderer::gl::pixel_buffer_map_read_access() == 0x0001U);
}

void names_framebuffer_status_codes(pp::tests::Harness& h)
{
    PP_EXPECT(h, pp::renderer::gl::framebuffer_complete_status() == 0x8CD5U);
    PP_EXPECT(h, pp::renderer::gl::framebuffer_status_name(0x8CD5U) == std::string_view("GL_FRAMEBUFFER_COMPLETE"));
    PP_EXPECT(h, pp::renderer::gl::framebuffer_status_name(0x8219U) == std::string_view("GL_FRAMEBUFFER_UNDEFINED"));
    PP_EXPECT(h, pp::renderer::gl::framebuffer_status_name(0x8CD6U)
                     == std::string_view("GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT"));
    PP_EXPECT(h, pp::renderer::gl::framebuffer_status_name(0x8CD7U)
                     == std::string_view("GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT"));
    PP_EXPECT(h, pp::renderer::gl::framebuffer_status_name(0x8CDBU)
                     == std::string_view("GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER"));
    PP_EXPECT(h, pp::renderer::gl::framebuffer_status_name(0x8CDCU)
                     == std::string_view("GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER"));
    PP_EXPECT(h, pp::renderer::gl::framebuffer_status_name(0x8CDDU)
                     == std::string_view("GL_FRAMEBUFFER_UNSUPPORTED"));
    PP_EXPECT(h, pp::renderer::gl::framebuffer_status_name(0x8D56U)
                     == std::string_view("GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE"));
    PP_EXPECT(h, pp::renderer::gl::framebuffer_status_name(0U) == std::string_view("UNKNOWN"));
}

void maps_framebuffer_render_target_parameters(pp::tests::Harness& h)
{
    PP_EXPECT(h, pp::renderer::gl::texture_2d_target() == 0x0DE1U);
    PP_EXPECT(h, pp::renderer::gl::renderbuffer_target() == 0x8D41U);
    PP_EXPECT(h, pp::renderer::gl::depth_component24_format() == 0x81A6U);
    PP_EXPECT(h, pp::renderer::gl::framebuffer_target() == 0x8D40U);
    PP_EXPECT(h, pp::renderer::gl::draw_framebuffer_target() == 0x8CA9U);
    PP_EXPECT(h, pp::renderer::gl::read_framebuffer_target() == 0x8CA8U);
    PP_EXPECT(h, pp::renderer::gl::draw_framebuffer_binding_query() == 0x8CA6U);
    PP_EXPECT(h, pp::renderer::gl::read_framebuffer_binding_query() == 0x8CAAU);
    PP_EXPECT(h, pp::renderer::gl::framebuffer_color_attachment() == 0x8CE0U);
    PP_EXPECT(h, pp::renderer::gl::framebuffer_depth_attachment() == 0x8D00U);
    PP_EXPECT(h, pp::renderer::gl::default_framebuffer_id() == 0U);
}

void maps_framebuffer_blit_parameters(pp::tests::Harness& h)
{
    const auto nearest = pp::renderer::gl::blit_filter_for_renderer_filter(pp::renderer::BlitFilter::nearest);
    const auto linear = pp::renderer::gl::blit_filter_for_renderer_filter(pp::renderer::BlitFilter::linear);
    const auto invalid = pp::renderer::gl::blit_filter_for_renderer_filter(
        static_cast<pp::renderer::BlitFilter>(255U));

    PP_EXPECT(h, pp::renderer::gl::framebuffer_color_buffer_mask() == 0x00004000U);
    PP_EXPECT(h, pp::renderer::gl::framebuffer_depth_buffer_mask() == 0x00000100U);
    PP_EXPECT(h, pp::renderer::gl::framebuffer_stencil_buffer_mask() == 0x00000400U);
    PP_EXPECT(h, pp::renderer::gl::color_write_mask_query() == 0x0C23U);
    PP_EXPECT(h, pp::renderer::gl::framebuffer_blit_filter(true) == 0x2601U);
    PP_EXPECT(h, pp::renderer::gl::framebuffer_blit_filter(false) == 0x2600U);
    PP_EXPECT(h, nearest.supported);
    PP_EXPECT(h, nearest.value == 0x2600U);
    PP_EXPECT(h, linear.supported);
    PP_EXPECT(h, linear.value == 0x2601U);
    PP_EXPECT(h, !invalid.supported);
    PP_EXPECT(h, invalid.value == 0U);
}

void maps_render_pass_clear_masks(pp::tests::Harness& h)
{
    const auto no_clear = pp::renderer::gl::clear_mask_for_render_pass(pp::renderer::RenderPassDesc {
        .clear_color_enabled = false,
        .clear_color = {},
        .clear_depth_enabled = false,
        .clear_stencil_enabled = false,
    });
    const auto color_only = pp::renderer::gl::clear_mask_for_render_pass(pp::renderer::RenderPassDesc {
        .clear_color_enabled = true,
        .clear_color = {},
        .clear_depth_enabled = false,
        .clear_stencil_enabled = false,
    });
    const auto depth_only = pp::renderer::gl::clear_mask_for_render_pass(pp::renderer::RenderPassDesc {
        .clear_color_enabled = false,
        .clear_color = {},
        .clear_depth_enabled = true,
        .clear_stencil_enabled = false,
    });
    const auto stencil_only = pp::renderer::gl::clear_mask_for_render_pass(pp::renderer::RenderPassDesc {
        .clear_color_enabled = false,
        .clear_color = {},
        .clear_depth_enabled = false,
        .clear_stencil_enabled = true,
    });
    const auto all_buffers = pp::renderer::gl::clear_mask_for_render_pass(pp::renderer::RenderPassDesc {
        .clear_color_enabled = true,
        .clear_color = {},
        .clear_depth_enabled = true,
        .clear_stencil_enabled = true,
    });

    PP_EXPECT(h, no_clear == 0U);
    PP_EXPECT(h, color_only == 0x00004000U);
    PP_EXPECT(h, depth_only == 0x00000100U);
    PP_EXPECT(h, stencil_only == 0x00000400U);
    PP_EXPECT(h, all_buffers == 0x00004500U);
}

void maps_render_pass_clear_values(pp::tests::Harness& h)
{
    const auto values = pp::renderer::gl::clear_values_for_render_pass(pp::renderer::RenderPassDesc {
        .clear_color_enabled = true,
        .clear_color = {
            .r = 0.125F,
            .g = 0.25F,
            .b = 0.5F,
            .a = 1.0F,
        },
        .clear_depth_enabled = true,
        .clear_depth = 0.625F,
        .clear_stencil_enabled = true,
        .clear_stencil = 7U,
    });
    const auto disabled_values = pp::renderer::gl::clear_values_for_render_pass(pp::renderer::RenderPassDesc {
        .clear_color_enabled = false,
        .clear_color = {
            .r = 1.0F,
            .g = 0.75F,
            .b = 0.5F,
            .a = 0.25F,
        },
        .clear_depth_enabled = false,
        .clear_depth = 0.125F,
        .clear_stencil_enabled = false,
        .clear_stencil = 13U,
    });

    PP_EXPECT(h, values.color[0] == 0.125F);
    PP_EXPECT(h, values.color[1] == 0.25F);
    PP_EXPECT(h, values.color[2] == 0.5F);
    PP_EXPECT(h, values.color[3] == 1.0F);
    PP_EXPECT(h, values.depth == 0.625F);
    PP_EXPECT(h, values.stencil == 7U);
    PP_EXPECT(h, disabled_values.color[0] == 1.0F);
    PP_EXPECT(h, disabled_values.color[1] == 0.75F);
    PP_EXPECT(h, disabled_values.color[2] == 0.5F);
    PP_EXPECT(h, disabled_values.color[3] == 0.25F);
    PP_EXPECT(h, disabled_values.depth == 0.125F);
    PP_EXPECT(h, disabled_values.stencil == 13U);
}

void maps_renderer_primitive_topologies_to_draw_modes(pp::tests::Harness& h)
{
    PP_EXPECT(h, pp::renderer::gl::primitive_mode_for_renderer_topology(
                     pp::renderer::PrimitiveTopology::triangles)
                     == 0x0004U);
    PP_EXPECT(h, pp::renderer::gl::primitive_mode_for_renderer_topology(
                     pp::renderer::PrimitiveTopology::triangle_strip)
                     == 0x0005U);
    PP_EXPECT(h, pp::renderer::gl::primitive_mode_for_renderer_topology(
                     pp::renderer::PrimitiveTopology::lines)
                     == 0x0001U);
    PP_EXPECT(h, pp::renderer::gl::primitive_mode_for_renderer_topology(
                     static_cast<pp::renderer::PrimitiveTopology>(255U))
                     == 0U);
}

void maps_shape_index_and_primitive_modes(pp::tests::Harness& h)
{
    constexpr std::uint32_t gl_points = 0x0000U;
    constexpr std::uint32_t gl_lines = 0x0001U;
    constexpr std::uint32_t gl_triangles = 0x0004U;
    constexpr std::uint32_t gl_unsigned_short = 0x1403U;
    constexpr std::uint32_t gl_unsigned_int = 0x1405U;
    constexpr std::uint32_t gl_float = 0x1406U;

    PP_EXPECT(h, pp::renderer::gl::index_type_for_index_size(2U) == gl_unsigned_short);
    PP_EXPECT(h, pp::renderer::gl::index_type_for_index_size(4U) == gl_unsigned_int);
    PP_EXPECT(h, pp::renderer::gl::index_type_for_index_size(1U) == 0U);
    PP_EXPECT(h, pp::renderer::gl::index_type_for_index_size(8U) == 0U);

    PP_EXPECT(h, pp::renderer::gl::primitive_mode_for_fill_count(1U) == gl_points);
    PP_EXPECT(h, pp::renderer::gl::primitive_mode_for_fill_count(2U) == gl_lines);
    PP_EXPECT(h, pp::renderer::gl::primitive_mode_for_fill_count(3U) == gl_triangles);
    PP_EXPECT(h, pp::renderer::gl::primitive_mode_for_fill_count(99U) == gl_triangles);

    PP_EXPECT(h, pp::renderer::gl::primitive_mode_for_stroke_count(1U) == gl_points);
    PP_EXPECT(h, pp::renderer::gl::primitive_mode_for_stroke_count(2U) == gl_lines);
    PP_EXPECT(h, pp::renderer::gl::primitive_mode_for_stroke_count(99U) == gl_lines);

    PP_EXPECT(h, pp::renderer::gl::array_buffer_target() == 0x8892U);
    PP_EXPECT(h, pp::renderer::gl::element_array_buffer_target() == 0x8893U);
    PP_EXPECT(h, pp::renderer::gl::static_draw_buffer_usage() == 0x88E4U);
    PP_EXPECT(h, pp::renderer::gl::vertex_attribute_float_component_type() == gl_float);
    PP_EXPECT(h, pp::renderer::gl::vertex_attribute_not_normalized() == 0U);
}

void maps_panopainter_cube_faces_to_texture_targets(pp::tests::Harness& h)
{
    const auto targets = pp::renderer::gl::panopainter_cube_face_texture_targets();

    PP_EXPECT(h, pp::renderer::gl::texture_cube_map_target() == 0x8513U);
    PP_EXPECT(h, pp::renderer::gl::cube_map_allocation_face_texture_target(0U) == 0x8515U);
    PP_EXPECT(h, pp::renderer::gl::cube_map_allocation_face_texture_target(1U) == 0x8516U);
    PP_EXPECT(h, pp::renderer::gl::cube_map_allocation_face_texture_target(2U) == 0x8517U);
    PP_EXPECT(h, pp::renderer::gl::cube_map_allocation_face_texture_target(3U) == 0x8518U);
    PP_EXPECT(h, pp::renderer::gl::cube_map_allocation_face_texture_target(4U) == 0x8519U);
    PP_EXPECT(h, pp::renderer::gl::cube_map_allocation_face_texture_target(5U) == 0x851AU);
    PP_EXPECT(h, pp::renderer::gl::cube_map_allocation_face_texture_target(6U) == 0U);

    PP_EXPECT(h, targets.size() == 6U);
    PP_EXPECT(h, targets[0] == 0x851AU);
    PP_EXPECT(h, targets[1] == 0x8516U);
    PP_EXPECT(h, targets[2] == 0x8519U);
    PP_EXPECT(h, targets[3] == 0x8515U);
    PP_EXPECT(h, targets[4] == 0x8518U);
    PP_EXPECT(h, targets[5] == 0x8517U);
    PP_EXPECT(h, pp::renderer::gl::cube_face_texture_target(0U) == 0x851AU);
    PP_EXPECT(h, pp::renderer::gl::cube_face_texture_target(5U) == 0x8517U);
    PP_EXPECT(h, pp::renderer::gl::cube_face_texture_target(6U) == 0U);
}

void exposes_default_render_target_texture_parameters(pp::tests::Harness& h)
{
    const auto parameters = pp::renderer::gl::default_render_target_texture_parameters();

    PP_EXPECT(h, parameters.size() == 4U);
    PP_EXPECT(h, parameters[0].name == 0x2800U);
    PP_EXPECT(h, parameters[0].value == 0x2601U);
    PP_EXPECT(h, parameters[1].name == 0x2801U);
    PP_EXPECT(h, parameters[1].value == 0x2601U);
    PP_EXPECT(h, parameters[2].name == 0x2802U);
    PP_EXPECT(h, parameters[2].value == 0x812FU);
    PP_EXPECT(h, parameters[3].name == 0x2803U);
    PP_EXPECT(h, parameters[3].value == 0x812FU);
}

void maps_sampler_parameters(pp::tests::Harness& h)
{
    const auto parameters = pp::renderer::gl::sampler_parameters_for_filter_wrap(0x2601U, 0x812FU);

    PP_EXPECT(h, parameters.size() == 5U);
    PP_EXPECT(h, parameters[0].name == 0x2802U);
    PP_EXPECT(h, parameters[0].value == 0x812FU);
    PP_EXPECT(h, parameters[1].name == 0x2803U);
    PP_EXPECT(h, parameters[1].value == 0x812FU);
    PP_EXPECT(h, parameters[2].name == 0x8072U);
    PP_EXPECT(h, parameters[2].value == 0x812FU);
    PP_EXPECT(h, parameters[3].name == 0x2801U);
    PP_EXPECT(h, parameters[3].value == 0x2601U);
    PP_EXPECT(h, parameters[4].name == 0x2800U);
    PP_EXPECT(h, parameters[4].value == 0x2601U);

    const auto filters = pp::renderer::gl::sampler_filter_parameters(0x2600U, 0x2601U);
    PP_EXPECT(h, filters.size() == 2U);
    PP_EXPECT(h, filters[0].name == 0x2801U);
    PP_EXPECT(h, filters[0].value == 0x2600U);
    PP_EXPECT(h, filters[1].name == 0x2800U);
    PP_EXPECT(h, filters[1].value == 0x2601U);

    PP_EXPECT(h, pp::renderer::gl::sampler_border_color_parameter_name() == 0x1004U);
}

void maps_renderer_sampler_tokens(pp::tests::Harness& h)
{
    const auto nearest = pp::renderer::gl::sampler_filter_for_renderer_filter(
        pp::renderer::SamplerFilter::nearest);
    const auto linear = pp::renderer::gl::sampler_filter_for_renderer_filter(
        pp::renderer::SamplerFilter::linear);
    const auto invalid_filter = pp::renderer::gl::sampler_filter_for_renderer_filter(
        static_cast<pp::renderer::SamplerFilter>(255U));
    const auto clamp_to_edge = pp::renderer::gl::sampler_address_mode_for_renderer_mode(
        pp::renderer::SamplerAddressMode::clamp_to_edge);
    const auto repeat = pp::renderer::gl::sampler_address_mode_for_renderer_mode(
        pp::renderer::SamplerAddressMode::repeat);
    const auto mirrored_repeat = pp::renderer::gl::sampler_address_mode_for_renderer_mode(
        pp::renderer::SamplerAddressMode::mirrored_repeat);
    const auto clamp_to_border = pp::renderer::gl::sampler_address_mode_for_renderer_mode(
        pp::renderer::SamplerAddressMode::clamp_to_border);
    const auto invalid_mode = pp::renderer::gl::sampler_address_mode_for_renderer_mode(
        static_cast<pp::renderer::SamplerAddressMode>(255U));
    const auto nearest_nearest = pp::renderer::gl::sampler_min_filter_for_renderer_filters(
        pp::renderer::SamplerFilter::nearest,
        pp::renderer::SamplerFilter::nearest);
    const auto linear_nearest = pp::renderer::gl::sampler_min_filter_for_renderer_filters(
        pp::renderer::SamplerFilter::linear,
        pp::renderer::SamplerFilter::nearest);
    const auto nearest_linear = pp::renderer::gl::sampler_min_filter_for_renderer_filters(
        pp::renderer::SamplerFilter::nearest,
        pp::renderer::SamplerFilter::linear);
    const auto linear_linear = pp::renderer::gl::sampler_min_filter_for_renderer_filters(
        pp::renderer::SamplerFilter::linear,
        pp::renderer::SamplerFilter::linear);
    const auto invalid_min_filter = pp::renderer::gl::sampler_min_filter_for_renderer_filters(
        static_cast<pp::renderer::SamplerFilter>(255U),
        pp::renderer::SamplerFilter::linear);
    const auto invalid_mip_filter = pp::renderer::gl::sampler_min_filter_for_renderer_filters(
        pp::renderer::SamplerFilter::linear,
        static_cast<pp::renderer::SamplerFilter>(255U));

    PP_EXPECT(h, nearest.supported);
    PP_EXPECT(h, nearest.value == 0x2600U);
    PP_EXPECT(h, linear.supported);
    PP_EXPECT(h, linear.value == 0x2601U);
    PP_EXPECT(h, !invalid_filter.supported);
    PP_EXPECT(h, invalid_filter.value == 0U);
    PP_EXPECT(h, clamp_to_edge.supported);
    PP_EXPECT(h, clamp_to_edge.value == 0x812FU);
    PP_EXPECT(h, repeat.supported);
    PP_EXPECT(h, repeat.value == 0x2901U);
    PP_EXPECT(h, mirrored_repeat.supported);
    PP_EXPECT(h, mirrored_repeat.value == 0x8370U);
    PP_EXPECT(h, clamp_to_border.supported);
    PP_EXPECT(h, clamp_to_border.value == 0x812DU);
    PP_EXPECT(h, !invalid_mode.supported);
    PP_EXPECT(h, invalid_mode.value == 0U);
    PP_EXPECT(h, nearest_nearest.supported);
    PP_EXPECT(h, nearest_nearest.value == 0x2700U);
    PP_EXPECT(h, linear_nearest.supported);
    PP_EXPECT(h, linear_nearest.value == 0x2701U);
    PP_EXPECT(h, nearest_linear.supported);
    PP_EXPECT(h, nearest_linear.value == 0x2702U);
    PP_EXPECT(h, linear_linear.supported);
    PP_EXPECT(h, linear_linear.value == 0x2703U);
    PP_EXPECT(h, !invalid_min_filter.supported);
    PP_EXPECT(h, invalid_min_filter.value == 0U);
    PP_EXPECT(h, !invalid_mip_filter.supported);
    PP_EXPECT(h, invalid_mip_filter.value == 0U);
}

void maps_renderer_sampler_states(pp::tests::Harness& h)
{
    const auto default_sampler = pp::renderer::gl::sampler_state_for_renderer_sampler_desc(
        pp::renderer::SamplerDesc {});
    const auto mixed_sampler = pp::renderer::gl::sampler_state_for_renderer_sampler_desc(
        pp::renderer::SamplerDesc {
            .min_filter = pp::renderer::SamplerFilter::nearest,
            .mag_filter = pp::renderer::SamplerFilter::linear,
            .mip_filter = pp::renderer::SamplerFilter::linear,
            .address_u = pp::renderer::SamplerAddressMode::repeat,
            .address_v = pp::renderer::SamplerAddressMode::mirrored_repeat,
            .address_w = pp::renderer::SamplerAddressMode::clamp_to_border,
        });
    const auto invalid_filter = pp::renderer::gl::sampler_state_for_renderer_sampler_desc(
        pp::renderer::SamplerDesc {
            .min_filter = static_cast<pp::renderer::SamplerFilter>(255U),
        });
    const auto invalid_address = pp::renderer::gl::sampler_state_for_renderer_sampler_desc(
        pp::renderer::SamplerDesc {
            .address_w = static_cast<pp::renderer::SamplerAddressMode>(255U),
        });

    PP_EXPECT(h, default_sampler.supported);
    PP_EXPECT(h, default_sampler.min_filter == 0x2703U);
    PP_EXPECT(h, default_sampler.mag_filter == 0x2601U);
    PP_EXPECT(h, default_sampler.wrap_s == 0x812FU);
    PP_EXPECT(h, default_sampler.wrap_t == 0x812FU);
    PP_EXPECT(h, default_sampler.wrap_r == 0x812FU);
    PP_EXPECT(h, mixed_sampler.supported);
    PP_EXPECT(h, mixed_sampler.min_filter == 0x2702U);
    PP_EXPECT(h, mixed_sampler.mag_filter == 0x2601U);
    PP_EXPECT(h, mixed_sampler.wrap_s == 0x2901U);
    PP_EXPECT(h, mixed_sampler.wrap_t == 0x8370U);
    PP_EXPECT(h, mixed_sampler.wrap_r == 0x812DU);
    PP_EXPECT(h, !invalid_filter.supported);
    PP_EXPECT(h, invalid_filter.min_filter == 0U);
    PP_EXPECT(h, !invalid_address.supported);
    PP_EXPECT(h, invalid_address.wrap_r == 0U);
}

void exposes_shader_attribute_binding_catalog(pp::tests::Harness& h)
{
    const auto bindings = pp::renderer::gl::panopainter_shader_attribute_bindings();

    PP_EXPECT(h, pp::renderer::gl::vertex_shader_stage() == 0x8B31U);
    PP_EXPECT(h, pp::renderer::gl::fragment_shader_stage() == 0x8B30U);
    PP_EXPECT(h, pp::renderer::gl::shader_compile_status_query() == 0x8B81U);
    PP_EXPECT(h, pp::renderer::gl::program_link_status_query() == 0x8B82U);
    PP_EXPECT(h, pp::renderer::gl::active_uniform_count_query() == 0x8B86U);
    PP_EXPECT(h, pp::renderer::gl::matrix_uniform_not_transposed() == 0U);

    PP_EXPECT(h, bindings.size() == 5U);
    PP_EXPECT(h, pp::renderer::gl::validate_shader_attribute_bindings(bindings).ok());
    PP_EXPECT(h, std::strcmp(bindings[0].name, "pos") == 0);
    PP_EXPECT(h, bindings[0].location == 0U);
    PP_EXPECT(h, std::strcmp(bindings[1].name, "uvs") == 0);
    PP_EXPECT(h, bindings[1].location == 1U);
    PP_EXPECT(h, std::strcmp(bindings[2].name, "uvs2") == 0);
    PP_EXPECT(h, bindings[2].location == 2U);
    PP_EXPECT(h, std::strcmp(bindings[3].name, "col") == 0);
    PP_EXPECT(h, bindings[3].location == 3U);
    PP_EXPECT(h, std::strcmp(bindings[4].name, "nor") == 0);
    PP_EXPECT(h, bindings[4].location == 3U);
}

void maps_app_initialization_parameters(pp::tests::Harness& h)
{
    PP_EXPECT(h, pp::renderer::gl::debug_severity_notification() == 0x826BU);
    PP_EXPECT(h, pp::renderer::gl::debug_severity_low() == 0x9148U);
    PP_EXPECT(h, pp::renderer::gl::debug_severity_medium() == 0x9147U);
    PP_EXPECT(h, pp::renderer::gl::debug_severity_high() == 0x9146U);
    PP_EXPECT(h, pp::renderer::gl::debug_output_state() == 0x92E0U);
    PP_EXPECT(h, pp::renderer::gl::debug_output_synchronous_state() == 0x8242U);

    PP_EXPECT(h, pp::renderer::gl::version_string_name() == 0x1F02U);
    PP_EXPECT(h, pp::renderer::gl::vendor_string_name() == 0x1F00U);
    PP_EXPECT(h, pp::renderer::gl::renderer_string_name() == 0x1F01U);
    PP_EXPECT(h, pp::renderer::gl::shading_language_version_string_name() == 0x8B8CU);

    PP_EXPECT(h, pp::renderer::gl::viewport_query() == 0x0BA2U);
    PP_EXPECT(h, pp::renderer::gl::color_clear_value_query() == 0x0C22U);
    PP_EXPECT(h, pp::renderer::gl::current_program_query() == 0x8B8DU);
    PP_EXPECT(h, pp::renderer::gl::active_texture_query() == 0x84E0U);
    PP_EXPECT(h, pp::renderer::gl::texture_binding_2d_query() == 0x8069U);
    PP_EXPECT(h, pp::renderer::gl::texture_binding_cube_map_query() == 0x8514U);
    PP_EXPECT(h, pp::renderer::gl::sampler_binding_query() == 0x8919U);
    PP_EXPECT(h, pp::renderer::gl::blend_state() == 0x0BE2U);
    PP_EXPECT(h, pp::renderer::gl::depth_test_state() == 0x0B71U);
    PP_EXPECT(h, pp::renderer::gl::scissor_test_state() == 0x0C11U);
    PP_EXPECT(h, pp::renderer::gl::program_point_size_state() == 0x8642U);
    PP_EXPECT(h, pp::renderer::gl::line_smooth_state() == 0x0B20U);
    PP_EXPECT(h, pp::renderer::gl::source_alpha_blend_factor() == 0x0302U);
    PP_EXPECT(h, pp::renderer::gl::one_minus_source_alpha_blend_factor() == 0x0303U);
    PP_EXPECT(h, pp::renderer::gl::add_blend_equation() == 0x8006U);
    PP_EXPECT(h, pp::renderer::gl::max_blend_equation() == 0x8008U);

    const auto initial_state = pp::renderer::gl::panopainter_initial_state();
    PP_EXPECT(h, !initial_state.depth_test_enabled);
    PP_EXPECT(h, initial_state.depth_test_state == 0x0B71U);
    PP_EXPECT(h, initial_state.source_color_factor == 0x0302U);
    PP_EXPECT(h, initial_state.destination_color_factor == 0x0303U);
    PP_EXPECT(h, initial_state.color_equation == 0x8006U);
    PP_EXPECT(h, initial_state.alpha_equation == 0x8008U);

    const auto convert_state = pp::renderer::gl::panopainter_convert_command_state();
    PP_EXPECT(h, !convert_state.depth_test_enabled);
    PP_EXPECT(h, convert_state.program_point_size_enabled);
    PP_EXPECT(h, convert_state.depth_test_state == 0x0B71U);
    PP_EXPECT(h, convert_state.program_point_size_state == 0x8642U);
    PP_EXPECT(h, convert_state.source_color_factor == 0x0302U);
    PP_EXPECT(h, convert_state.destination_color_factor == 0x0303U);
    PP_EXPECT(h, convert_state.blend_equation == 0x8006U);

    PP_EXPECT(h, pp::renderer::gl::rgba8_internal_format() == 0x8058U);
    PP_EXPECT(h, pp::renderer::gl::rgba16f_internal_format() == 0x881AU);
    PP_EXPECT(h, pp::renderer::gl::rgba32f_internal_format() == 0x8814U);
    PP_EXPECT(h, pp::renderer::gl::color_write_disabled() == 0U);
    PP_EXPECT(h, pp::renderer::gl::color_write_enabled() == 1U);
    PP_EXPECT(h, pp::renderer::gl::linear_texture_filter() == 0x2601U);
    PP_EXPECT(h, pp::renderer::gl::linear_mipmap_linear_texture_filter() == 0x2703U);
    PP_EXPECT(h, pp::renderer::gl::nearest_texture_filter() == 0x2600U);
    PP_EXPECT(h, pp::renderer::gl::repeat_texture_wrap() == 0x2901U);
    PP_EXPECT(h, pp::renderer::gl::clamp_to_edge_texture_wrap() == 0x812FU);
    PP_EXPECT(h, pp::renderer::gl::clamp_to_border_texture_wrap() == 0x812DU);
    PP_EXPECT(h, pp::renderer::gl::active_texture_unit(0U) == 0x84C0U);
    PP_EXPECT(h, pp::renderer::gl::active_texture_unit(4U) == 0x84C4U);
}

void applies_app_initialization_state(pp::tests::Harness& h)
{
    recorded_state_calls.clear();

    const auto status = pp::renderer::gl::apply_panopainter_initial_state(
        pp::renderer::gl::OpenGlStateDispatch {
            .enable = record_enable,
            .disable = record_disable,
            .blend_func = record_blend_func,
            .blend_equation_separate = record_blend_equation_separate,
        });

    PP_EXPECT(h, status.ok());
    PP_EXPECT(h, recorded_state_calls.size() == 3U);
    PP_EXPECT(h, recorded_state_calls[0].kind == RecordedOpenGlStateCall::Kind::disable);
    PP_EXPECT(h, recorded_state_calls[0].first == 0x0B71U);
    PP_EXPECT(h, recorded_state_calls[1].kind == RecordedOpenGlStateCall::Kind::blend_func);
    PP_EXPECT(h, recorded_state_calls[1].first == 0x0302U);
    PP_EXPECT(h, recorded_state_calls[1].second == 0x0303U);
    PP_EXPECT(h, recorded_state_calls[2].kind == RecordedOpenGlStateCall::Kind::blend_equation_separate);
    PP_EXPECT(h, recorded_state_calls[2].first == 0x8006U);
    PP_EXPECT(h, recorded_state_calls[2].second == 0x8008U);
}

void rejects_incomplete_app_initialization_state_dispatch(pp::tests::Harness& h)
{
    const auto status = pp::renderer::gl::apply_panopainter_initial_state(
        pp::renderer::gl::OpenGlStateDispatch {
            .enable = record_enable,
            .disable = record_disable,
            .blend_func = record_blend_func,
        });

    PP_EXPECT(h, !status.ok());
    PP_EXPECT(h, status.code == pp::foundation::StatusCode::invalid_argument);
}

void applies_convert_command_state(pp::tests::Harness& h)
{
    recorded_state_calls.clear();

    const auto status = pp::renderer::gl::apply_panopainter_convert_command_state(
        pp::renderer::gl::OpenGlConvertCommandStateDispatch {
            .enable = record_enable,
            .disable = record_disable,
            .blend_func = record_blend_func,
            .blend_equation = record_blend_equation,
        });

    PP_EXPECT(h, status.ok());
    PP_EXPECT(h, recorded_state_calls.size() == 4U);
    PP_EXPECT(h, recorded_state_calls[0].kind == RecordedOpenGlStateCall::Kind::disable);
    PP_EXPECT(h, recorded_state_calls[0].first == 0x0B71U);
    PP_EXPECT(h, recorded_state_calls[1].kind == RecordedOpenGlStateCall::Kind::enable);
    PP_EXPECT(h, recorded_state_calls[1].first == 0x8642U);
    PP_EXPECT(h, recorded_state_calls[2].kind == RecordedOpenGlStateCall::Kind::blend_func);
    PP_EXPECT(h, recorded_state_calls[2].first == 0x0302U);
    PP_EXPECT(h, recorded_state_calls[2].second == 0x0303U);
    PP_EXPECT(h, recorded_state_calls[3].kind == RecordedOpenGlStateCall::Kind::blend_equation);
    PP_EXPECT(h, recorded_state_calls[3].first == 0x8006U);
}

void rejects_incomplete_convert_command_state_dispatch(pp::tests::Harness& h)
{
    const auto status = pp::renderer::gl::apply_panopainter_convert_command_state(
        pp::renderer::gl::OpenGlConvertCommandStateDispatch {
            .enable = record_enable,
            .disable = record_disable,
            .blend_func = record_blend_func,
        });

    PP_EXPECT(h, !status.ok());
    PP_EXPECT(h, status.code == pp::foundation::StatusCode::invalid_argument);
}

void snapshots_legacy_gl_state(pp::tests::Harness& h)
{
    recorded_integer_queries.clear();
    recorded_float_queries.clear();
    recorded_active_texture_calls.clear();

    const auto result = pp::renderer::gl::snapshot_opengl_state(
        pp::renderer::gl::OpenGlStateSnapshotDispatch {
            .is_enabled = record_is_enabled,
            .get_integer = record_get_integer,
            .get_float = record_get_float,
            .active_texture = record_active_texture,
        });

    PP_EXPECT(h, result.ok());
    const auto& state = result.value();
    PP_EXPECT(h, state.blend_enabled == 1U);
    PP_EXPECT(h, state.depth_test_enabled == 0U);
    PP_EXPECT(h, state.scissor_test_enabled == 1U);
    PP_EXPECT(h, state.viewport[0] == 2);
    PP_EXPECT(h, state.viewport[1] == 4);
    PP_EXPECT(h, state.viewport[2] == 640);
    PP_EXPECT(h, state.viewport[3] == 320);
    PP_EXPECT(h, state.clear_color[0] == 0.25F);
    PP_EXPECT(h, state.clear_color[1] == 0.5F);
    PP_EXPECT(h, state.clear_color[2] == 0.75F);
    PP_EXPECT(h, state.clear_color[3] == 1.0F);
    PP_EXPECT(h, state.program == 42);
    PP_EXPECT(h, state.draw_framebuffer == 7);
    PP_EXPECT(h, state.read_framebuffer == 9);
    PP_EXPECT(h, state.active_texture == 0x84C4);
    PP_EXPECT(h, state.cube_map_binding == 88);
    PP_EXPECT(h, state.texture_2d_bindings[0] == 101);
    PP_EXPECT(h, state.texture_2d_bindings[9] == 110);
    PP_EXPECT(h, state.sampler_bindings[0] == 201);
    PP_EXPECT(h, state.sampler_bindings[9] == 210);
    PP_EXPECT(h, recorded_float_queries.size() == 1U);
    PP_EXPECT(h, recorded_active_texture_calls.size() == 10U);
    PP_EXPECT(h, recorded_active_texture_calls[0] == 0x84C0U);
    PP_EXPECT(h, recorded_active_texture_calls[9] == 0x84C9U);
}

void rejects_incomplete_gl_state_snapshot_dispatch(pp::tests::Harness& h)
{
    const auto result = pp::renderer::gl::snapshot_opengl_state(
        pp::renderer::gl::OpenGlStateSnapshotDispatch {
            .is_enabled = record_is_enabled,
            .get_integer = record_get_integer,
            .get_float = record_get_float,
        });

    PP_EXPECT(h, !result.ok());
    PP_EXPECT(h, result.status().code == pp::foundation::StatusCode::invalid_argument);
}

void restores_legacy_gl_state(pp::tests::Harness& h)
{
    recorded_state_calls.clear();
    recorded_viewport_calls.clear();
    recorded_clear_calls.clear();
    recorded_active_texture_calls.clear();
    recorded_binding_calls.clear();

    pp::renderer::gl::OpenGlSavedState state {};
    state.blend_enabled = 1U;
    state.depth_test_enabled = 0U;
    state.scissor_test_enabled = 1U;
    state.viewport = { 3, 6, 800, 600 };
    state.clear_color = { 0.1F, 0.2F, 0.3F, 0.4F };
    state.program = 12;
    state.draw_framebuffer = 21;
    state.read_framebuffer = 22;
    state.active_texture = 0x84C7;
    state.cube_map_binding = 77;
    for (std::size_t i = 0; i < state.texture_2d_bindings.size(); ++i) {
        state.texture_2d_bindings[i] = 300 + static_cast<std::int32_t>(i);
        state.sampler_bindings[i] = 400 + static_cast<std::int32_t>(i);
    }

    const auto status = pp::renderer::gl::restore_opengl_state(
        state,
        pp::renderer::gl::OpenGlStateRestoreDispatch {
            .enable = record_enable,
            .disable = record_disable,
            .viewport = record_viewport,
            .clear_color = record_clear_color,
            .bind_framebuffer = record_bind_framebuffer,
            .use_program = record_use_program,
            .active_texture = record_active_texture,
            .bind_texture = record_bind_texture,
            .bind_sampler = record_bind_sampler,
        });

    PP_EXPECT(h, status.ok());
    PP_EXPECT(h, recorded_state_calls.size() == 3U);
    PP_EXPECT(h, recorded_state_calls[0].kind == RecordedOpenGlStateCall::Kind::enable);
    PP_EXPECT(h, recorded_state_calls[0].first == 0x0BE2U);
    PP_EXPECT(h, recorded_state_calls[1].kind == RecordedOpenGlStateCall::Kind::disable);
    PP_EXPECT(h, recorded_state_calls[1].first == 0x0B71U);
    PP_EXPECT(h, recorded_state_calls[2].kind == RecordedOpenGlStateCall::Kind::enable);
    PP_EXPECT(h, recorded_state_calls[2].first == 0x0C11U);
    PP_EXPECT(h, recorded_viewport_calls.size() == 1U);
    PP_EXPECT(h, recorded_viewport_calls[0].width == 800);
    PP_EXPECT(h, recorded_clear_calls.size() == 1U);
    PP_EXPECT(h, recorded_clear_calls[0].color[2] == 0.3F);
    PP_EXPECT(h, recorded_binding_calls.size() == 24U);
    PP_EXPECT(h, recorded_binding_calls[0].kind == RecordedOpenGlBindingCall::Kind::bind_framebuffer);
    PP_EXPECT(h, recorded_binding_calls[0].first == 0x8CA9U);
    PP_EXPECT(h, recorded_binding_calls[0].second == 21U);
    PP_EXPECT(h, recorded_binding_calls[1].kind == RecordedOpenGlBindingCall::Kind::bind_framebuffer);
    PP_EXPECT(h, recorded_binding_calls[1].first == 0x8CA8U);
    PP_EXPECT(h, recorded_binding_calls[1].second == 22U);
    PP_EXPECT(h, recorded_binding_calls[2].kind == RecordedOpenGlBindingCall::Kind::use_program);
    PP_EXPECT(h, recorded_binding_calls[2].first == 12U);
    PP_EXPECT(h, recorded_active_texture_calls.size() == 11U);
    PP_EXPECT(h, recorded_active_texture_calls[0] == 0x84C0U);
    PP_EXPECT(h, recorded_active_texture_calls[9] == 0x84C9U);
    PP_EXPECT(h, recorded_active_texture_calls[10] == 0x84C7U);
    PP_EXPECT(h, recorded_binding_calls[3].kind == RecordedOpenGlBindingCall::Kind::bind_texture);
    PP_EXPECT(h, recorded_binding_calls[3].first == 0x0DE1U);
    PP_EXPECT(h, recorded_binding_calls[3].second == 300U);
    PP_EXPECT(h, recorded_binding_calls[23].kind == RecordedOpenGlBindingCall::Kind::bind_texture);
    PP_EXPECT(h, recorded_binding_calls[23].first == 0x8513U);
    PP_EXPECT(h, recorded_binding_calls[23].second == 77U);
}

void rejects_incomplete_gl_state_restore_dispatch(pp::tests::Harness& h)
{
    const auto status = pp::renderer::gl::restore_opengl_state(
        pp::renderer::gl::OpenGlSavedState {},
        pp::renderer::gl::OpenGlStateRestoreDispatch {
            .enable = record_enable,
            .disable = record_disable,
            .viewport = record_viewport,
        });

    PP_EXPECT(h, !status.ok());
    PP_EXPECT(h, status.code == pp::foundation::StatusCode::invalid_argument);
}

void queries_app_runtime_info(pp::tests::Harness& h)
{
    recorded_string_queries.clear();

    const auto result = pp::renderer::gl::query_opengl_runtime_info(
        pp::renderer::gl::OpenGlRuntimeInfoDispatch {
            .get_string = record_string_query,
        });

    PP_EXPECT(h, result.ok());
    PP_EXPECT(h, recorded_string_queries.size() == 4U);
    PP_EXPECT(h, recorded_string_queries[0] == 0x1F02U);
    PP_EXPECT(h, recorded_string_queries[1] == 0x1F00U);
    PP_EXPECT(h, recorded_string_queries[2] == 0x1F01U);
    PP_EXPECT(h, recorded_string_queries[3] == 0x8B8CU);
    PP_EXPECT(h, std::strcmp(result.value().version, "test-version") == 0);
    PP_EXPECT(h, std::strcmp(result.value().vendor, "test-vendor") == 0);
    PP_EXPECT(h, std::strcmp(result.value().renderer, "test-renderer") == 0);
    PP_EXPECT(h, std::strcmp(result.value().shading_language_version, "test-glsl") == 0);
}

void rejects_incomplete_app_runtime_info_dispatch(pp::tests::Harness& h)
{
    const auto result = pp::renderer::gl::query_opengl_runtime_info(pp::renderer::gl::OpenGlRuntimeInfoDispatch {});

    PP_EXPECT(h, !result.ok());
    PP_EXPECT(h, result.status().code == pp::foundation::StatusCode::invalid_argument);
}

void queries_app_extensions(pp::tests::Harness& h)
{
    recorded_integer_queries.clear();
    recorded_indexed_string_queries.clear();
    recorded_extension_count = 3;

    const auto result = pp::renderer::gl::query_opengl_extensions(
        pp::renderer::gl::OpenGlExtensionQueryDispatch {
            .get_integer = record_get_integer,
            .get_string_indexed = record_indexed_string_query,
        });

    PP_EXPECT(h, result.ok());
    PP_EXPECT(h, recorded_integer_queries.size() == 1U);
    PP_EXPECT(h, recorded_integer_queries[0] == 0x821DU);
    PP_EXPECT(h, recorded_indexed_string_queries.size() == 3U);
    PP_EXPECT(h, result.value().size() == 3U);
    PP_EXPECT(h, result.value()[0] == "GL_EXT_shader_framebuffer_fetch");
    PP_EXPECT(h, result.value()[1] == "GL_EXT_map_buffer_alignment");
    PP_EXPECT(h, result.value()[2] == "GL_OES_texture_float");
}

void converts_null_extension_names_to_empty_strings(pp::tests::Harness& h)
{
    recorded_extension_count = 4;

    const auto result = pp::renderer::gl::query_opengl_extensions(
        pp::renderer::gl::OpenGlExtensionQueryDispatch {
            .get_integer = record_get_integer,
            .get_string_indexed = record_indexed_string_query,
        });

    PP_EXPECT(h, result.ok());
    PP_EXPECT(h, result.value().size() == 4U);
    PP_EXPECT(h, result.value()[3].empty());
}

void treats_negative_extension_count_as_empty(pp::tests::Harness& h)
{
    recorded_indexed_string_queries.clear();
    recorded_extension_count = -7;

    const auto result = pp::renderer::gl::query_opengl_extensions(
        pp::renderer::gl::OpenGlExtensionQueryDispatch {
            .get_integer = record_get_integer,
            .get_string_indexed = record_indexed_string_query,
        });

    PP_EXPECT(h, result.ok());
    PP_EXPECT(h, result.value().empty());
    PP_EXPECT(h, recorded_indexed_string_queries.empty());
}

void rejects_incomplete_extension_query_dispatch(pp::tests::Harness& h)
{
    const auto result = pp::renderer::gl::query_opengl_extensions(pp::renderer::gl::OpenGlExtensionQueryDispatch {});

    PP_EXPECT(h, !result.ok());
    PP_EXPECT(h, result.status().code == pp::foundation::StatusCode::invalid_argument);
}

void queries_app_capability_detection(pp::tests::Harness& h)
{
    recorded_integer_queries.clear();
    recorded_indexed_string_queries.clear();
    recorded_extension_count = 3;

    const auto result = pp::renderer::gl::query_opengl_capability_detection(
        pp::renderer::gl::OpenGlExtensionQueryDispatch {
            .get_integer = record_get_integer,
            .get_string_indexed = record_indexed_string_query,
        },
        pp::renderer::gl::OpenGlRuntime {});

    PP_EXPECT(h, result.ok());
    PP_EXPECT(h, recorded_integer_queries.size() == 1U);
    PP_EXPECT(h, recorded_integer_queries[0] == 0x821DU);
    PP_EXPECT(h, recorded_indexed_string_queries.size() == 3U);
    PP_EXPECT(h, result.value().extensions.size() == 3U);
    PP_EXPECT(h, result.value().extensions[0] == "GL_EXT_shader_framebuffer_fetch");
    PP_EXPECT(h, result.value().feature_state.capabilities.framebuffer_fetch);
    PP_EXPECT(h, result.value().feature_state.capabilities.map_buffer_alignment);
    PP_EXPECT(h, !result.value().feature_state.capabilities.float32_textures);
    PP_EXPECT(h, result.value().feature_state.features.framebuffer_fetch);
}

void query_capability_detection_preserves_webgl_float_policy(pp::tests::Harness& h)
{
    recorded_extension_count = 3;

    const auto result = pp::renderer::gl::query_opengl_capability_detection(
        pp::renderer::gl::OpenGlExtensionQueryDispatch {
            .get_integer = record_get_integer,
            .get_string_indexed = record_indexed_string_query,
        },
        pp::renderer::gl::OpenGlRuntime { .gles = true, .web = true });

    PP_EXPECT(h, result.ok());
    PP_EXPECT(h, result.value().feature_state.capabilities.framebuffer_fetch);
    PP_EXPECT(h, !result.value().feature_state.capabilities.float32_textures);
    PP_EXPECT(h, !result.value().feature_state.features.float32_render_targets);
    PP_EXPECT(h, !result.value().feature_state.features.float32_linear_filtering);
}

void rejects_incomplete_capability_detection_dispatch(pp::tests::Harness& h)
{
    const auto result = pp::renderer::gl::query_opengl_capability_detection(
        pp::renderer::gl::OpenGlExtensionQueryDispatch {},
        pp::renderer::gl::OpenGlRuntime {});

    PP_EXPECT(h, !result.ok());
    PP_EXPECT(h, result.status().code == pp::foundation::StatusCode::invalid_argument);
}

void clears_app_default_target(pp::tests::Harness& h)
{
    recorded_clear_calls.clear();

    const auto clear = pp::renderer::gl::panopainter_default_clear();
    PP_EXPECT(h, clear.color[0] == 0.1F);
    PP_EXPECT(h, clear.color[1] == 0.1F);
    PP_EXPECT(h, clear.color[2] == 0.1F);
    PP_EXPECT(h, clear.color[3] == 1.0F);
    PP_EXPECT(h, clear.mask == 0x00004000U);

    const auto status = pp::renderer::gl::clear_panopainter_default_target(
        pp::renderer::gl::OpenGlClearDispatch {
            .clear_color = record_clear_color,
            .clear = record_clear,
        });

    PP_EXPECT(h, status.ok());
    PP_EXPECT(h, recorded_clear_calls.size() == 2U);
    PP_EXPECT(h, recorded_clear_calls[0].color[0] == 0.1F);
    PP_EXPECT(h, recorded_clear_calls[0].color[1] == 0.1F);
    PP_EXPECT(h, recorded_clear_calls[0].color[2] == 0.1F);
    PP_EXPECT(h, recorded_clear_calls[0].color[3] == 1.0F);
    PP_EXPECT(h, recorded_clear_calls[0].mask == 0U);
    PP_EXPECT(h, recorded_clear_calls[1].mask == 0x00004000U);
}

void rejects_incomplete_app_clear_dispatch(pp::tests::Harness& h)
{
    const auto status = pp::renderer::gl::clear_panopainter_default_target(
        pp::renderer::gl::OpenGlClearDispatch {
            .clear_color = record_clear_color,
        });

    PP_EXPECT(h, !status.ok());
    PP_EXPECT(h, status.code == pp::foundation::StatusCode::invalid_argument);
}

void clears_render_target_through_dispatch(pp::tests::Harness& h)
{
    recorded_clear_calls.clear();

    const auto status = pp::renderer::gl::clear_opengl_render_target(
        pp::renderer::gl::OpenGlDefaultClear {
            .color = { 0.2F, 0.4F, 0.6F, 0.8F },
            .mask = 0x00004100U,
        },
        pp::renderer::gl::OpenGlClearDispatch {
            .clear_color = record_clear_color,
            .clear = record_clear,
        });

    PP_EXPECT(h, status.ok());
    PP_EXPECT(h, recorded_clear_calls.size() == 2U);
    PP_EXPECT(h, recorded_clear_calls[0].color[0] == 0.2F);
    PP_EXPECT(h, recorded_clear_calls[0].color[1] == 0.4F);
    PP_EXPECT(h, recorded_clear_calls[0].color[2] == 0.6F);
    PP_EXPECT(h, recorded_clear_calls[0].color[3] == 0.8F);
    PP_EXPECT(h, recorded_clear_calls[1].mask == 0x00004100U);
}

void clears_color_buffer_with_write_mask_and_restores_previous_mask(pp::tests::Harness& h)
{
    recorded_boolean_queries.clear();
    recorded_color_mask_calls.clear();
    recorded_clear_calls.clear();

    const auto status = pp::renderer::gl::clear_opengl_color_buffer_with_write_mask(
        pp::renderer::gl::OpenGlColorMaskedClear {
            .mask = { .r = 1U, .g = 0U, .b = 0U, .a = 1U },
            .color = { 0.75F, 0.5F, 0.25F, 1.0F },
        },
        pp::renderer::gl::OpenGlColorMaskedClearDispatch {
            .get_boolean = record_get_boolean,
            .color_mask = record_color_mask,
            .clear_color = record_clear_color,
            .clear = record_clear,
        });

    PP_EXPECT(h, status.ok());
    PP_EXPECT(h, recorded_boolean_queries.size() == 1U);
    PP_EXPECT(h, recorded_boolean_queries[0] == 0x0C23U);
    PP_EXPECT(h, recorded_color_mask_calls.size() == 2U);
    PP_EXPECT(h, recorded_color_mask_calls[0].r == 1U);
    PP_EXPECT(h, recorded_color_mask_calls[0].g == 0U);
    PP_EXPECT(h, recorded_color_mask_calls[0].b == 0U);
    PP_EXPECT(h, recorded_color_mask_calls[0].a == 1U);
    PP_EXPECT(h, recorded_color_mask_calls[1].r == 1U);
    PP_EXPECT(h, recorded_color_mask_calls[1].g == 0U);
    PP_EXPECT(h, recorded_color_mask_calls[1].b == 1U);
    PP_EXPECT(h, recorded_color_mask_calls[1].a == 1U);
    PP_EXPECT(h, recorded_clear_calls.size() == 2U);
    PP_EXPECT(h, recorded_clear_calls[0].color[0] == 0.75F);
    PP_EXPECT(h, recorded_clear_calls[0].color[1] == 0.5F);
    PP_EXPECT(h, recorded_clear_calls[0].color[2] == 0.25F);
    PP_EXPECT(h, recorded_clear_calls[0].color[3] == 1.0F);
    PP_EXPECT(h, recorded_clear_calls[1].mask == 0x00004000U);
}

void applies_color_write_mask_through_dispatch(pp::tests::Harness& h)
{
    recorded_color_mask_calls.clear();

    const auto status = pp::renderer::gl::apply_opengl_color_write_mask(
        pp::renderer::gl::OpenGlColorWriteMask { .r = 1U, .g = 0U, .b = 1U, .a = 0U },
        pp::renderer::gl::OpenGlColorWriteMaskDispatch {
            .color_mask = record_color_mask,
        });

    PP_EXPECT(h, status.ok());
    PP_EXPECT(h, recorded_color_mask_calls.size() == 1U);
    PP_EXPECT(h, recorded_color_mask_calls[0].r == 1U);
    PP_EXPECT(h, recorded_color_mask_calls[0].g == 0U);
    PP_EXPECT(h, recorded_color_mask_calls[0].b == 1U);
    PP_EXPECT(h, recorded_color_mask_calls[0].a == 0U);
}

void rejects_invalid_render_target_clear_dispatch(pp::tests::Harness& h)
{
    const auto missing_clear = pp::renderer::gl::clear_opengl_render_target(
        pp::renderer::gl::OpenGlDefaultClear {
            .color = { 1.0F, 0.0F, 0.0F, 1.0F },
            .mask = 0x00004000U,
        },
        pp::renderer::gl::OpenGlClearDispatch {
            .clear_color = record_clear_color,
        });
    const auto invalid_mask = pp::renderer::gl::clear_opengl_render_target(
        pp::renderer::gl::OpenGlDefaultClear {
            .color = { 1.0F, 0.0F, 0.0F, 1.0F },
            .mask = 0U,
        },
        pp::renderer::gl::OpenGlClearDispatch {
            .clear_color = record_clear_color,
            .clear = record_clear,
        });
    const auto missing_masked_dispatch = pp::renderer::gl::clear_opengl_color_buffer_with_write_mask(
        pp::renderer::gl::OpenGlColorMaskedClear {
            .mask = { .r = 1U, .g = 1U, .b = 1U, .a = 1U },
            .color = { 1.0F, 0.0F, 0.0F, 1.0F },
        },
        pp::renderer::gl::OpenGlColorMaskedClearDispatch {
            .get_boolean = record_get_boolean,
            .clear_color = record_clear_color,
            .clear = record_clear,
        });

    PP_EXPECT(h, !missing_clear.ok());
    PP_EXPECT(h, missing_clear.code == pp::foundation::StatusCode::invalid_argument);
    PP_EXPECT(h, !invalid_mask.ok());
    PP_EXPECT(h, invalid_mask.code == pp::foundation::StatusCode::invalid_argument);
    PP_EXPECT(h, !missing_masked_dispatch.ok());
    PP_EXPECT(h, missing_masked_dispatch.code == pp::foundation::StatusCode::invalid_argument);
}

void applies_clear_color_dispatch(pp::tests::Harness& h)
{
    recorded_clear_calls.clear();

    const auto status = pp::renderer::gl::apply_opengl_clear_color(
        { 0.125F, 0.25F, 0.5F, 1.0F },
        pp::renderer::gl::OpenGlClearColorDispatch {
            .clear_color = record_clear_color,
        });

    PP_EXPECT(h, status.ok());
    PP_EXPECT(h, recorded_clear_calls.size() == 1U);
    PP_EXPECT(h, recorded_clear_calls[0].color[0] == 0.125F);
    PP_EXPECT(h, recorded_clear_calls[0].color[1] == 0.25F);
    PP_EXPECT(h, recorded_clear_calls[0].color[2] == 0.5F);
    PP_EXPECT(h, recorded_clear_calls[0].color[3] == 1.0F);
}

void rejects_incomplete_clear_color_dispatch(pp::tests::Harness& h)
{
    const auto status = pp::renderer::gl::apply_opengl_clear_color(
        { 0.0F, 0.0F, 0.0F, 0.0F },
        pp::renderer::gl::OpenGlClearColorDispatch {});

    PP_EXPECT(h, !status.ok());
    PP_EXPECT(h, status.code == pp::foundation::StatusCode::invalid_argument);
}

void activates_texture_unit_through_dispatch(pp::tests::Harness& h)
{
    recorded_active_texture_calls.clear();

    const auto zero = pp::renderer::gl::activate_opengl_texture_unit(
        0U,
        pp::renderer::gl::OpenGlActiveTextureDispatch {
            .active_texture = record_active_texture,
        });
    const auto four = pp::renderer::gl::activate_opengl_texture_unit(
        4U,
        pp::renderer::gl::OpenGlActiveTextureDispatch {
            .active_texture = record_active_texture,
        });

    PP_EXPECT(h, zero.ok());
    PP_EXPECT(h, four.ok());
    PP_EXPECT(h, recorded_active_texture_calls.size() == 2U);
    PP_EXPECT(h, recorded_active_texture_calls[0] == 0x84C0U);
    PP_EXPECT(h, recorded_active_texture_calls[1] == 0x84C4U);
}

void rejects_incomplete_active_texture_dispatch(pp::tests::Harness& h)
{
    const auto status = pp::renderer::gl::activate_opengl_texture_unit(
        0U,
        pp::renderer::gl::OpenGlActiveTextureDispatch {});

    PP_EXPECT(h, !status.ok());
    PP_EXPECT(h, status.code == pp::foundation::StatusCode::invalid_argument);
}

void applies_viewport_dispatch(pp::tests::Harness& h)
{
    recorded_viewport_calls.clear();

    const auto status = pp::renderer::gl::apply_opengl_viewport(
        pp::renderer::gl::OpenGlViewportRect {
            .x = 8,
            .y = 16,
            .width = 1024,
            .height = 512,
        },
        pp::renderer::gl::OpenGlViewportDispatch {
            .viewport = record_viewport,
        });

    PP_EXPECT(h, status.ok());
    PP_EXPECT(h, recorded_viewport_calls.size() == 1U);
    PP_EXPECT(h, recorded_viewport_calls[0].x == 8);
    PP_EXPECT(h, recorded_viewport_calls[0].y == 16);
    PP_EXPECT(h, recorded_viewport_calls[0].width == 1024);
    PP_EXPECT(h, recorded_viewport_calls[0].height == 512);
}

void rejects_incomplete_viewport_dispatch(pp::tests::Harness& h)
{
    const auto status = pp::renderer::gl::apply_opengl_viewport(
        pp::renderer::gl::OpenGlViewportRect {
            .width = 1,
            .height = 1,
        },
        pp::renderer::gl::OpenGlViewportDispatch {});

    PP_EXPECT(h, !status.ok());
    PP_EXPECT(h, status.code == pp::foundation::StatusCode::invalid_argument);
}

void queries_viewport_through_dispatch(pp::tests::Harness& h)
{
    recorded_integer_queries.clear();

    const auto result = pp::renderer::gl::query_opengl_viewport(
        pp::renderer::gl::OpenGlViewportQueryDispatch {
            .get_integer = record_get_integer,
        });

    PP_EXPECT(h, result.ok());
    PP_EXPECT(h, result.value().x == 2);
    PP_EXPECT(h, result.value().y == 4);
    PP_EXPECT(h, result.value().width == 640);
    PP_EXPECT(h, result.value().height == 320);
    PP_EXPECT(h, recorded_integer_queries.size() == 1U);
    PP_EXPECT(h, recorded_integer_queries[0] == pp::renderer::gl::viewport_query());
}

void rejects_incomplete_viewport_query_dispatch(pp::tests::Harness& h)
{
    const auto result = pp::renderer::gl::query_opengl_viewport(
        pp::renderer::gl::OpenGlViewportQueryDispatch {});

    PP_EXPECT(h, !result.ok());
    PP_EXPECT(h, result.status().code == pp::foundation::StatusCode::invalid_argument);
}

void queries_clear_color_through_dispatch(pp::tests::Harness& h)
{
    recorded_float_queries.clear();

    const auto result = pp::renderer::gl::query_opengl_clear_color(
        pp::renderer::gl::OpenGlClearColorQueryDispatch {
            .get_float = record_get_float,
        });

    PP_EXPECT(h, result.ok());
    PP_EXPECT(h, result.value()[0] == 0.25F);
    PP_EXPECT(h, result.value()[1] == 0.5F);
    PP_EXPECT(h, result.value()[2] == 0.75F);
    PP_EXPECT(h, result.value()[3] == 1.0F);
    PP_EXPECT(h, recorded_float_queries.size() == 1U);
    PP_EXPECT(h, recorded_float_queries[0] == pp::renderer::gl::color_clear_value_query());
}

void rejects_incomplete_clear_color_query_dispatch(pp::tests::Harness& h)
{
    const auto result = pp::renderer::gl::query_opengl_clear_color(
        pp::renderer::gl::OpenGlClearColorQueryDispatch {});

    PP_EXPECT(h, !result.ok());
    PP_EXPECT(h, result.status().code == pp::foundation::StatusCode::invalid_argument);
}

void applies_scissor_dispatch(pp::tests::Harness& h)
{
    recorded_state_calls.clear();
    recorded_scissor_calls.clear();

    const auto enabled_status = pp::renderer::gl::apply_opengl_scissor_rect(
        pp::renderer::gl::OpenGlScissorRect {
            .enabled = 1U,
            .x = 4,
            .y = 12,
            .width = 320,
            .height = 200,
        },
        pp::renderer::gl::OpenGlScissorDispatch {
            .enable = record_enable,
            .disable = record_disable,
            .scissor = record_scissor,
        });

    const auto disabled_status = pp::renderer::gl::apply_opengl_scissor_rect(
        pp::renderer::gl::OpenGlScissorRect {},
        pp::renderer::gl::OpenGlScissorDispatch {
            .enable = record_enable,
            .disable = record_disable,
            .scissor = record_scissor,
        });

    PP_EXPECT(h, enabled_status.ok());
    PP_EXPECT(h, disabled_status.ok());
    PP_EXPECT(h, recorded_state_calls.size() == 2U);
    PP_EXPECT(h, recorded_state_calls[0].kind == RecordedOpenGlStateCall::Kind::enable);
    PP_EXPECT(h, recorded_state_calls[0].first == 0x0C11U);
    PP_EXPECT(h, recorded_state_calls[1].kind == RecordedOpenGlStateCall::Kind::disable);
    PP_EXPECT(h, recorded_state_calls[1].first == 0x0C11U);
    PP_EXPECT(h, recorded_scissor_calls.size() == 1U);
    PP_EXPECT(h, recorded_scissor_calls[0].x == 4);
    PP_EXPECT(h, recorded_scissor_calls[0].y == 12);
    PP_EXPECT(h, recorded_scissor_calls[0].width == 320);
    PP_EXPECT(h, recorded_scissor_calls[0].height == 200);
}

void rejects_incomplete_scissor_dispatch(pp::tests::Harness& h)
{
    const auto status = pp::renderer::gl::apply_opengl_scissor_rect(
        pp::renderer::gl::OpenGlScissorRect {
            .enabled = 1U,
        },
        pp::renderer::gl::OpenGlScissorDispatch {
            .enable = record_enable,
            .disable = record_disable,
        });

    PP_EXPECT(h, !status.ok());
    PP_EXPECT(h, status.code == pp::foundation::StatusCode::invalid_argument);
}

void applies_scissor_test_dispatch(pp::tests::Harness& h)
{
    recorded_state_calls.clear();

    const auto enabled_status = pp::renderer::gl::apply_opengl_scissor_test(
        true,
        pp::renderer::gl::OpenGlScissorTestDispatch {
            .enable = record_enable,
            .disable = record_disable,
        });
    const auto disabled_status = pp::renderer::gl::apply_opengl_scissor_test(
        false,
        pp::renderer::gl::OpenGlScissorTestDispatch {
            .enable = record_enable,
            .disable = record_disable,
        });

    PP_EXPECT(h, enabled_status.ok());
    PP_EXPECT(h, disabled_status.ok());
    PP_EXPECT(h, recorded_state_calls.size() == 2U);
    PP_EXPECT(h, recorded_state_calls[0].kind == RecordedOpenGlStateCall::Kind::enable);
    PP_EXPECT(h, recorded_state_calls[0].first == 0x0C11U);
    PP_EXPECT(h, recorded_state_calls[1].kind == RecordedOpenGlStateCall::Kind::disable);
    PP_EXPECT(h, recorded_state_calls[1].first == 0x0C11U);
}

void rejects_incomplete_scissor_test_dispatch(pp::tests::Harness& h)
{
    const auto status = pp::renderer::gl::apply_opengl_scissor_test(
        true,
        pp::renderer::gl::OpenGlScissorTestDispatch {
            .enable = record_enable,
        });

    PP_EXPECT(h, !status.ok());
    PP_EXPECT(h, status.code == pp::foundation::StatusCode::invalid_argument);
}

void applies_generic_capability_dispatch(pp::tests::Harness& h)
{
    recorded_state_calls.clear();

    const auto enabled_status = pp::renderer::gl::apply_opengl_capability(
        0x0BE2U,
        true,
        pp::renderer::gl::OpenGlCapabilityDispatch {
            .enable = record_enable,
            .disable = record_disable,
        });
    const auto disabled_status = pp::renderer::gl::apply_opengl_capability(
        0x0B71U,
        false,
        pp::renderer::gl::OpenGlCapabilityDispatch {
            .enable = record_enable,
            .disable = record_disable,
        });

    PP_EXPECT(h, enabled_status.ok());
    PP_EXPECT(h, disabled_status.ok());
    PP_EXPECT(h, recorded_state_calls.size() == 2U);
    PP_EXPECT(h, recorded_state_calls[0].kind == RecordedOpenGlStateCall::Kind::enable);
    PP_EXPECT(h, recorded_state_calls[0].first == 0x0BE2U);
    PP_EXPECT(h, recorded_state_calls[1].kind == RecordedOpenGlStateCall::Kind::disable);
    PP_EXPECT(h, recorded_state_calls[1].first == 0x0B71U);
}

void rejects_incomplete_generic_capability_dispatch(pp::tests::Harness& h)
{
    const auto status = pp::renderer::gl::apply_opengl_capability(
        0x0BE2U,
        true,
        pp::renderer::gl::OpenGlCapabilityDispatch {
            .enable = record_enable,
        });

    PP_EXPECT(h, !status.ok());
    PP_EXPECT(h, status.code == pp::foundation::StatusCode::invalid_argument);
}

void queries_generic_capability_state(pp::tests::Harness& h)
{
    const auto blend = pp::renderer::gl::query_opengl_capability_state(
        0x0BE2U,
        pp::renderer::gl::OpenGlCapabilityStateQueryDispatch {
            .is_enabled = record_is_enabled,
        });
    const auto depth = pp::renderer::gl::query_opengl_capability_state(
        0x0B71U,
        pp::renderer::gl::OpenGlCapabilityStateQueryDispatch {
            .is_enabled = record_is_enabled,
        });

    PP_EXPECT(h, blend.ok());
    PP_EXPECT(h, blend.value());
    PP_EXPECT(h, depth.ok());
    PP_EXPECT(h, !depth.value());
}

void rejects_incomplete_generic_capability_state_query(pp::tests::Harness& h)
{
    const auto result = pp::renderer::gl::query_opengl_capability_state(
        0x0BE2U,
        pp::renderer::gl::OpenGlCapabilityStateQueryDispatch {});

    PP_EXPECT(h, !result.ok());
    PP_EXPECT(h, result.status().code == pp::foundation::StatusCode::invalid_argument);
}

void applies_render_platform_hints(pp::tests::Harness& h)
{
    recorded_state_calls.clear();

    const auto status = pp::renderer::gl::apply_opengl_render_platform_hints(
        pp::renderer::gl::OpenGlRenderPlatformHintDispatch {
            .enable = record_enable,
        });

    PP_EXPECT(h, status.ok());
    PP_EXPECT(h, recorded_state_calls.size() == 2U);
    PP_EXPECT(h, recorded_state_calls[0].kind == RecordedOpenGlStateCall::Kind::enable);
    PP_EXPECT(h, recorded_state_calls[0].first == 0x8642U);
    PP_EXPECT(h, recorded_state_calls[1].kind == RecordedOpenGlStateCall::Kind::enable);
    PP_EXPECT(h, recorded_state_calls[1].first == 0x0B20U);
}

void rejects_incomplete_render_platform_hint_dispatch(pp::tests::Harness& h)
{
    const auto status = pp::renderer::gl::apply_opengl_render_platform_hints(
        pp::renderer::gl::OpenGlRenderPlatformHintDispatch {});

    PP_EXPECT(h, !status.ok());
    PP_EXPECT(h, status.code == pp::foundation::StatusCode::invalid_argument);
}

void applies_debug_output_states(pp::tests::Harness& h)
{
    recorded_state_calls.clear();

    const auto status = pp::renderer::gl::apply_opengl_debug_output_states(
        pp::renderer::gl::OpenGlDebugOutputStateDispatch {
            .enable = record_enable,
        });

    PP_EXPECT(h, status.ok());
    PP_EXPECT(h, recorded_state_calls.size() == 2U);
    PP_EXPECT(h, recorded_state_calls[0].kind == RecordedOpenGlStateCall::Kind::enable);
    PP_EXPECT(h, recorded_state_calls[0].first == 0x92E0U);
    PP_EXPECT(h, recorded_state_calls[1].kind == RecordedOpenGlStateCall::Kind::enable);
    PP_EXPECT(h, recorded_state_calls[1].first == 0x8242U);
}

void rejects_incomplete_debug_output_state_dispatch(pp::tests::Harness& h)
{
    const auto status = pp::renderer::gl::apply_opengl_debug_output_states(
        pp::renderer::gl::OpenGlDebugOutputStateDispatch {});

    PP_EXPECT(h, !status.ok());
    PP_EXPECT(h, status.code == pp::foundation::StatusCode::invalid_argument);
}

void applies_buffer_clear_dispatch(pp::tests::Harness& h)
{
    recorded_clear_calls.clear();

    const auto status = pp::renderer::gl::clear_opengl_buffers(
        0x00000100U,
        pp::renderer::gl::OpenGlBufferClearDispatch {
            .clear = record_clear,
        });

    PP_EXPECT(h, status.ok());
    PP_EXPECT(h, recorded_clear_calls.size() == 1U);
    PP_EXPECT(h, recorded_clear_calls[0].mask == 0x00000100U);
}

void rejects_incomplete_buffer_clear_dispatch(pp::tests::Harness& h)
{
    const auto status = pp::renderer::gl::clear_opengl_buffers(
        0x00000100U,
        pp::renderer::gl::OpenGlBufferClearDispatch {});

    PP_EXPECT(h, !status.ok());
    PP_EXPECT(h, status.code == pp::foundation::StatusCode::invalid_argument);
}

void allocates_texture_2d_through_dispatch(pp::tests::Harness& h)
{
    recorded_generated_texture_counts.clear();
    recorded_binding_calls.clear();
    recorded_texture_image_calls.clear();
    next_texture_id = 91U;
    const std::array<std::uint8_t, 4> pixels { 1U, 2U, 3U, 4U };

    const auto texture = pp::renderer::gl::allocate_opengl_texture_2d(
        pp::renderer::gl::OpenGlTexture2DAllocation {
            .width = 8,
            .height = 4,
            .internal_format = 0x8058,
            .pixel_format = 0x1908U,
            .component_type = 0x1401U,
            .data = pixels.data(),
        },
        pp::renderer::gl::OpenGlTexture2DAllocationDispatch {
            .gen_textures = record_gen_textures,
            .bind_texture = record_bind_texture,
            .tex_image_2d = record_tex_image_2d,
        });

    PP_EXPECT(h, texture.ok());
    PP_EXPECT(h, texture.value() == 91U);
    PP_EXPECT(h, recorded_generated_texture_counts.size() == 1U);
    PP_EXPECT(h, recorded_generated_texture_counts[0] == 1U);
    PP_EXPECT(h, recorded_binding_calls.size() == 2U);
    PP_EXPECT(h, recorded_binding_calls[0].kind == RecordedOpenGlBindingCall::Kind::bind_texture);
    PP_EXPECT(h, recorded_binding_calls[0].first == 0x0DE1U);
    PP_EXPECT(h, recorded_binding_calls[0].second == 91U);
    PP_EXPECT(h, recorded_binding_calls[1].second == 0U);
    PP_EXPECT(h, recorded_texture_image_calls.size() == 1U);
    PP_EXPECT(h, recorded_texture_image_calls[0].target == 0x0DE1U);
    PP_EXPECT(h, recorded_texture_image_calls[0].width == 8);
    PP_EXPECT(h, recorded_texture_image_calls[0].height == 4);
    PP_EXPECT(h, recorded_texture_image_calls[0].data == pixels.data());
}

void rejects_invalid_texture_2d_allocation(pp::tests::Harness& h)
{
    const auto missing_dispatch = pp::renderer::gl::allocate_opengl_texture_2d(
        pp::renderer::gl::OpenGlTexture2DAllocation {
            .width = 1,
            .height = 1,
            .internal_format = 0x8058,
            .pixel_format = 0x1908U,
            .component_type = 0x1401U,
        },
        pp::renderer::gl::OpenGlTexture2DAllocationDispatch {
            .gen_textures = record_gen_textures,
            .bind_texture = record_bind_texture,
        });
    const auto invalid_dimensions = pp::renderer::gl::allocate_opengl_texture_2d(
        pp::renderer::gl::OpenGlTexture2DAllocation {
            .width = 0,
            .height = 1,
            .internal_format = 0x8058,
            .pixel_format = 0x1908U,
            .component_type = 0x1401U,
        },
        pp::renderer::gl::OpenGlTexture2DAllocationDispatch {
            .gen_textures = record_gen_textures,
            .bind_texture = record_bind_texture,
            .tex_image_2d = record_tex_image_2d,
        });

    PP_EXPECT(h, !missing_dispatch.ok());
    PP_EXPECT(h, missing_dispatch.status().code == pp::foundation::StatusCode::invalid_argument);
    PP_EXPECT(h, !invalid_dimensions.ok());
    PP_EXPECT(h, invalid_dimensions.status().code == pp::foundation::StatusCode::invalid_argument);
}

void deletes_and_binds_texture_2d_through_dispatch(pp::tests::Harness& h)
{
    recorded_deleted_textures.clear();
    recorded_binding_calls.clear();

    const auto delete_status = pp::renderer::gl::delete_opengl_texture_2d(
        27U,
        pp::renderer::gl::OpenGlTexture2DDeleteDispatch {
            .delete_textures = record_delete_textures,
        });
    const auto bind_status = pp::renderer::gl::bind_opengl_texture_2d(
        27U,
        pp::renderer::gl::OpenGlTexture2DBindDispatch {
            .bind_texture = record_bind_texture,
        });

    PP_EXPECT(h, delete_status.ok());
    PP_EXPECT(h, recorded_deleted_textures.size() == 1U);
    PP_EXPECT(h, recorded_deleted_textures[0] == 27U);
    PP_EXPECT(h, bind_status.ok());
    PP_EXPECT(h, recorded_binding_calls.size() == 1U);
    PP_EXPECT(h, recorded_binding_calls[0].first == 0x0DE1U);
    PP_EXPECT(h, recorded_binding_calls[0].second == 27U);
}

void deletes_texture_objects_through_dispatch(pp::tests::Harness& h)
{
    recorded_deleted_textures.clear();

    constexpr std::array<std::uint32_t, 3> textures { 0U, 7U, 11U };
    const auto status = pp::renderer::gl::delete_opengl_texture_objects(
        textures,
        pp::renderer::gl::OpenGlTexture2DDeleteDispatch {
            .delete_textures = record_delete_textures,
        });

    PP_EXPECT(h, status.ok());
    PP_EXPECT(h, recorded_deleted_textures.size() == 3U);
    PP_EXPECT(h, recorded_deleted_textures[0] == 0U);
    PP_EXPECT(h, recorded_deleted_textures[1] == 7U);
    PP_EXPECT(h, recorded_deleted_textures[2] == 11U);
}

void sets_texture_2d_parameters_through_dispatch(pp::tests::Harness& h)
{
    recorded_binding_calls.clear();
    recorded_texture_parameter_calls.clear();

    constexpr std::array parameters {
        pp::renderer::gl::OpenGlTextureParameter { .name = 0x2800U, .value = 0x2601U },
        pp::renderer::gl::OpenGlTextureParameter { .name = 0x2801U, .value = 0x2600U },
    };
    const auto status = pp::renderer::gl::set_opengl_texture_2d_parameters(
        37U,
        parameters,
        pp::renderer::gl::OpenGlTexture2DParameterDispatch {
            .bind_texture = record_bind_texture,
            .tex_parameter_f = record_tex_parameter_f,
        });

    PP_EXPECT(h, status.ok());
    PP_EXPECT(h, recorded_binding_calls.size() == 2U);
    PP_EXPECT(h, recorded_binding_calls[0].kind == RecordedOpenGlBindingCall::Kind::bind_texture);
    PP_EXPECT(h, recorded_binding_calls[0].first == 0x0DE1U);
    PP_EXPECT(h, recorded_binding_calls[0].second == 37U);
    PP_EXPECT(h, recorded_binding_calls[1].second == 0U);
    PP_EXPECT(h, recorded_texture_parameter_calls.size() == 2U);
    PP_EXPECT(h, recorded_texture_parameter_calls[0].target == 0x0DE1U);
    PP_EXPECT(h, recorded_texture_parameter_calls[0].parameter == 0x2800U);
    PP_EXPECT(h, recorded_texture_parameter_calls[0].value == 0x2601U);
    PP_EXPECT(h, recorded_texture_parameter_calls[1].parameter == 0x2801U);
    PP_EXPECT(h, recorded_texture_parameter_calls[1].value == 0x2600U);
}

void rejects_invalid_texture_2d_parameter_dispatch(pp::tests::Harness& h)
{
    constexpr std::array parameters {
        pp::renderer::gl::OpenGlTextureParameter { .name = 0x2800U, .value = 0x2601U },
    };
    constexpr std::array invalid_parameters {
        pp::renderer::gl::OpenGlTextureParameter { .name = 0U, .value = 0x2601U },
    };

    const auto missing_dispatch = pp::renderer::gl::set_opengl_texture_2d_parameters(
        37U,
        parameters,
        pp::renderer::gl::OpenGlTexture2DParameterDispatch {
            .bind_texture = record_bind_texture,
        });
    const auto invalid_texture = pp::renderer::gl::set_opengl_texture_2d_parameters(
        0U,
        parameters,
        pp::renderer::gl::OpenGlTexture2DParameterDispatch {
            .bind_texture = record_bind_texture,
            .tex_parameter_f = record_tex_parameter_f,
        });
    const auto invalid_parameter = pp::renderer::gl::set_opengl_texture_2d_parameters(
        37U,
        invalid_parameters,
        pp::renderer::gl::OpenGlTexture2DParameterDispatch {
            .bind_texture = record_bind_texture,
            .tex_parameter_f = record_tex_parameter_f,
        });

    PP_EXPECT(h, !missing_dispatch.ok());
    PP_EXPECT(h, missing_dispatch.code == pp::foundation::StatusCode::invalid_argument);
    PP_EXPECT(h, !invalid_texture.ok());
    PP_EXPECT(h, invalid_texture.code == pp::foundation::StatusCode::invalid_argument);
    PP_EXPECT(h, !invalid_parameter.ok());
    PP_EXPECT(h, invalid_parameter.code == pp::foundation::StatusCode::invalid_argument);
}

void allocates_texture_cube_through_dispatch(pp::tests::Harness& h)
{
    recorded_generated_texture_counts.clear();
    recorded_binding_calls.clear();
    recorded_texture_image_calls.clear();
    next_texture_id = 121U;

    const auto texture = pp::renderer::gl::allocate_opengl_texture_cube(
        pp::renderer::gl::OpenGlTextureCubeAllocation {
            .resolution = 64,
            .internal_format = 0x8058,
            .pixel_format = 0x1908U,
            .component_type = 0x1401U,
        },
        pp::renderer::gl::OpenGlTextureCubeAllocationDispatch {
            .gen_textures = record_gen_textures,
            .bind_texture = record_bind_texture,
            .tex_image_2d = record_tex_image_2d,
        });

    PP_EXPECT(h, texture.ok());
    PP_EXPECT(h, texture.value() == 121U);
    PP_EXPECT(h, recorded_generated_texture_counts.size() == 1U);
    PP_EXPECT(h, recorded_generated_texture_counts[0] == 1U);
    PP_EXPECT(h, recorded_binding_calls.size() == 1U);
    PP_EXPECT(h, recorded_binding_calls[0].kind == RecordedOpenGlBindingCall::Kind::bind_texture);
    PP_EXPECT(h, recorded_binding_calls[0].first == 0x8513U);
    PP_EXPECT(h, recorded_binding_calls[0].second == 121U);
    PP_EXPECT(h, recorded_texture_image_calls.size() == 6U);
    for (std::size_t i = 0U; i < recorded_texture_image_calls.size(); ++i) {
        PP_EXPECT(h, recorded_texture_image_calls[i].target == 0x8515U + static_cast<std::uint32_t>(i));
        PP_EXPECT(h, recorded_texture_image_calls[i].width == 64);
        PP_EXPECT(h, recorded_texture_image_calls[i].height == 64);
        PP_EXPECT(h, recorded_texture_image_calls[i].internal_format == 0x8058);
        PP_EXPECT(h, recorded_texture_image_calls[i].pixel_format == 0x1908U);
        PP_EXPECT(h, recorded_texture_image_calls[i].component_type == 0x1401U);
        PP_EXPECT(h, recorded_texture_image_calls[i].data == nullptr);
    }
}

void rejects_invalid_texture_cube_allocation(pp::tests::Harness& h)
{
    const auto missing_dispatch = pp::renderer::gl::allocate_opengl_texture_cube(
        pp::renderer::gl::OpenGlTextureCubeAllocation {
            .resolution = 1,
            .internal_format = 0x8058,
            .pixel_format = 0x1908U,
            .component_type = 0x1401U,
        },
        pp::renderer::gl::OpenGlTextureCubeAllocationDispatch {
            .gen_textures = record_gen_textures,
            .bind_texture = record_bind_texture,
        });
    const auto invalid_resolution = pp::renderer::gl::allocate_opengl_texture_cube(
        pp::renderer::gl::OpenGlTextureCubeAllocation {
            .resolution = 0,
            .internal_format = 0x8058,
            .pixel_format = 0x1908U,
            .component_type = 0x1401U,
        },
        pp::renderer::gl::OpenGlTextureCubeAllocationDispatch {
            .gen_textures = record_gen_textures,
            .bind_texture = record_bind_texture,
            .tex_image_2d = record_tex_image_2d,
        });

    PP_EXPECT(h, !missing_dispatch.ok());
    PP_EXPECT(h, missing_dispatch.status().code == pp::foundation::StatusCode::invalid_argument);
    PP_EXPECT(h, !invalid_resolution.ok());
    PP_EXPECT(h, invalid_resolution.status().code == pp::foundation::StatusCode::invalid_argument);
}

void binds_texture_cube_through_dispatch(pp::tests::Harness& h)
{
    recorded_binding_calls.clear();

    const auto status = pp::renderer::gl::bind_opengl_texture_cube(
        33U,
        pp::renderer::gl::OpenGlTexture2DBindDispatch {
            .bind_texture = record_bind_texture,
        });

    PP_EXPECT(h, status.ok());
    PP_EXPECT(h, recorded_binding_calls.size() == 1U);
    PP_EXPECT(h, recorded_binding_calls[0].kind == RecordedOpenGlBindingCall::Kind::bind_texture);
    PP_EXPECT(h, recorded_binding_calls[0].first == 0x8513U);
    PP_EXPECT(h, recorded_binding_calls[0].second == 33U);
}

void creates_sampler_through_dispatch(pp::tests::Harness& h)
{
    recorded_generated_sampler_counts.clear();
    recorded_sampler_parameter_calls.clear();
    next_sampler_id = 81U;
    const auto parameters = pp::renderer::gl::sampler_parameters_for_filter_wrap(0x2601U, 0x812FU);

    const auto sampler = pp::renderer::gl::create_opengl_sampler(
        parameters,
        pp::renderer::gl::OpenGlSamplerCreateDispatch {
            .gen_samplers = record_gen_samplers,
            .sampler_parameter_i = record_sampler_parameter_i,
        });

    PP_EXPECT(h, sampler.ok());
    PP_EXPECT(h, sampler.value() == 81U);
    PP_EXPECT(h, recorded_generated_sampler_counts.size() == 1U);
    PP_EXPECT(h, recorded_generated_sampler_counts[0] == 1U);
    PP_EXPECT(h, recorded_sampler_parameter_calls.size() == parameters.size());
    for (std::size_t i = 0U; i < parameters.size(); ++i) {
        PP_EXPECT(h, recorded_sampler_parameter_calls[i].sampler == 81U);
        PP_EXPECT(h, recorded_sampler_parameter_calls[i].parameter == parameters[i].name);
        PP_EXPECT(h, recorded_sampler_parameter_calls[i].value == static_cast<std::int32_t>(parameters[i].value));
    }
}

void sets_sampler_parameters_through_dispatch(pp::tests::Harness& h)
{
    recorded_sampler_parameter_calls.clear();
    const auto parameters = pp::renderer::gl::sampler_filter_parameters(0x2600U, 0x2601U);

    const auto status = pp::renderer::gl::set_opengl_sampler_parameters(
        17U,
        parameters,
        pp::renderer::gl::OpenGlSamplerParameterDispatch {
            .sampler_parameter_i = record_sampler_parameter_i,
        });

    PP_EXPECT(h, status.ok());
    PP_EXPECT(h, recorded_sampler_parameter_calls.size() == 2U);
    PP_EXPECT(h, recorded_sampler_parameter_calls[0].sampler == 17U);
    PP_EXPECT(h, recorded_sampler_parameter_calls[0].parameter == 0x2801U);
    PP_EXPECT(h, recorded_sampler_parameter_calls[0].value == 0x2600);
    PP_EXPECT(h, recorded_sampler_parameter_calls[1].parameter == 0x2800U);
    PP_EXPECT(h, recorded_sampler_parameter_calls[1].value == 0x2601);
}

void sets_sampler_border_color_through_dispatch(pp::tests::Harness& h)
{
    recorded_sampler_border_calls.clear();
    constexpr std::array<float, 4> rgba { 0.25F, 0.5F, 0.75F, 1.0F };

    const auto status = pp::renderer::gl::set_opengl_sampler_border_color(
        19U,
        0x1004U,
        rgba.data(),
        pp::renderer::gl::OpenGlSamplerBorderDispatch {
            .sampler_parameter_fv = record_sampler_parameter_fv,
        });

    PP_EXPECT(h, status.ok());
    PP_EXPECT(h, recorded_sampler_border_calls.size() == 1U);
    PP_EXPECT(h, recorded_sampler_border_calls[0].sampler == 19U);
    PP_EXPECT(h, recorded_sampler_border_calls[0].parameter == 0x1004U);
    PP_EXPECT(h, recorded_sampler_border_calls[0].values == rgba.data());
}

void binds_sampler_through_dispatch(pp::tests::Harness& h)
{
    recorded_binding_calls.clear();

    const auto bind_status = pp::renderer::gl::bind_opengl_sampler_object(
        3U,
        23U,
        pp::renderer::gl::OpenGlSamplerBindDispatch {
            .bind_sampler = record_bind_sampler,
        });
    const auto unbind_status = pp::renderer::gl::bind_opengl_sampler_object(
        3U,
        0U,
        pp::renderer::gl::OpenGlSamplerBindDispatch {
            .bind_sampler = record_bind_sampler,
        });

    PP_EXPECT(h, bind_status.ok());
    PP_EXPECT(h, unbind_status.ok());
    PP_EXPECT(h, recorded_binding_calls.size() == 2U);
    PP_EXPECT(h, recorded_binding_calls[0].kind == RecordedOpenGlBindingCall::Kind::bind_sampler);
    PP_EXPECT(h, recorded_binding_calls[0].first == 3U);
    PP_EXPECT(h, recorded_binding_calls[0].second == 23U);
    PP_EXPECT(h, recorded_binding_calls[1].kind == RecordedOpenGlBindingCall::Kind::bind_sampler);
    PP_EXPECT(h, recorded_binding_calls[1].first == 3U);
    PP_EXPECT(h, recorded_binding_calls[1].second == 0U);
}

void rejects_invalid_sampler_dispatch(pp::tests::Harness& h)
{
    constexpr std::array<pp::renderer::gl::OpenGlTextureParameter, 1> parameters {
        pp::renderer::gl::OpenGlTextureParameter { .name = 0x2801U, .value = 0x2601U },
    };
    constexpr std::array<pp::renderer::gl::OpenGlTextureParameter, 1> invalid_parameters {
        pp::renderer::gl::OpenGlTextureParameter { .name = 0U, .value = 0x2601U },
    };
    constexpr std::array<float, 4> rgba { 1.0F, 1.0F, 1.0F, 1.0F };

    const auto missing_create_dispatch = pp::renderer::gl::create_opengl_sampler(
        parameters,
        pp::renderer::gl::OpenGlSamplerCreateDispatch {
            .gen_samplers = record_gen_samplers,
        });
    const auto empty_create_parameters = pp::renderer::gl::create_opengl_sampler(
        {},
        pp::renderer::gl::OpenGlSamplerCreateDispatch {
            .gen_samplers = record_gen_samplers,
            .sampler_parameter_i = record_sampler_parameter_i,
        });
    const auto zero_sampler_parameters = pp::renderer::gl::set_opengl_sampler_parameters(
        0U,
        parameters,
        pp::renderer::gl::OpenGlSamplerParameterDispatch {
            .sampler_parameter_i = record_sampler_parameter_i,
        });
    const auto invalid_parameter_name = pp::renderer::gl::set_opengl_sampler_parameters(
        7U,
        invalid_parameters,
        pp::renderer::gl::OpenGlSamplerParameterDispatch {
            .sampler_parameter_i = record_sampler_parameter_i,
        });
    const auto null_border = pp::renderer::gl::set_opengl_sampler_border_color(
        7U,
        0x1004U,
        nullptr,
        pp::renderer::gl::OpenGlSamplerBorderDispatch {
            .sampler_parameter_fv = record_sampler_parameter_fv,
        });
    const auto zero_border_parameter = pp::renderer::gl::set_opengl_sampler_border_color(
        7U,
        0U,
        rgba.data(),
        pp::renderer::gl::OpenGlSamplerBorderDispatch {
            .sampler_parameter_fv = record_sampler_parameter_fv,
        });
    const auto missing_bind_dispatch = pp::renderer::gl::bind_opengl_sampler_object(
        0U,
        7U,
        pp::renderer::gl::OpenGlSamplerBindDispatch {});

    PP_EXPECT(h, !missing_create_dispatch.ok());
    PP_EXPECT(h, missing_create_dispatch.status().code == pp::foundation::StatusCode::invalid_argument);
    PP_EXPECT(h, !empty_create_parameters.ok());
    PP_EXPECT(h, empty_create_parameters.status().code == pp::foundation::StatusCode::invalid_argument);
    PP_EXPECT(h, !zero_sampler_parameters.ok());
    PP_EXPECT(h, zero_sampler_parameters.code == pp::foundation::StatusCode::invalid_argument);
    PP_EXPECT(h, !invalid_parameter_name.ok());
    PP_EXPECT(h, invalid_parameter_name.code == pp::foundation::StatusCode::invalid_argument);
    PP_EXPECT(h, !null_border.ok());
    PP_EXPECT(h, null_border.code == pp::foundation::StatusCode::invalid_argument);
    PP_EXPECT(h, !zero_border_parameter.ok());
    PP_EXPECT(h, zero_border_parameter.code == pp::foundation::StatusCode::invalid_argument);
    PP_EXPECT(h, !missing_bind_dispatch.ok());
    PP_EXPECT(h, missing_bind_dispatch.code == pp::foundation::StatusCode::invalid_argument);
}

void uses_and_deletes_programs_through_dispatch(pp::tests::Harness& h)
{
    recorded_binding_calls.clear();
    recorded_deleted_programs.clear();

    const auto use_status = pp::renderer::gl::use_opengl_program(
        91U,
        pp::renderer::gl::OpenGlProgramUseDispatch {
            .use_program = record_use_program,
        });
    const auto delete_status = pp::renderer::gl::delete_opengl_program(
        91U,
        pp::renderer::gl::OpenGlProgramDeleteDispatch {
            .use_program = record_use_program,
            .delete_program = record_delete_program,
        });
    const auto delete_zero_status = pp::renderer::gl::delete_opengl_program(
        0U,
        pp::renderer::gl::OpenGlProgramDeleteDispatch {
            .use_program = record_use_program,
            .delete_program = record_delete_program,
        });

    PP_EXPECT(h, use_status.ok());
    PP_EXPECT(h, delete_status.ok());
    PP_EXPECT(h, delete_zero_status.ok());
    PP_EXPECT(h, recorded_binding_calls.size() == 2U);
    PP_EXPECT(h, recorded_binding_calls[0].kind == RecordedOpenGlBindingCall::Kind::use_program);
    PP_EXPECT(h, recorded_binding_calls[0].first == 91U);
    PP_EXPECT(h, recorded_binding_calls[1].kind == RecordedOpenGlBindingCall::Kind::use_program);
    PP_EXPECT(h, recorded_binding_calls[1].first == 0U);
    PP_EXPECT(h, recorded_deleted_programs.size() == 1U);
    PP_EXPECT(h, recorded_deleted_programs[0] == 91U);
}

void sets_vector_uniforms_through_dispatch(pp::tests::Harness& h)
{
    recorded_uniform_vector_calls.clear();
    constexpr std::array<float, 16> values {
        1.0F, 2.0F, 3.0F, 4.0F,
        5.0F, 6.0F, 7.0F, 8.0F,
        9.0F, 10.0F, 11.0F, 12.0F,
        13.0F, 14.0F, 15.0F, 16.0F,
    };

    const auto vec4_status = pp::renderer::gl::set_opengl_uniform_vec4(
        4,
        values.data(),
        pp::renderer::gl::OpenGlUniformVec4Dispatch {
            .uniform_4fv = record_uniform_4fv,
        });
    const auto vec3_status = pp::renderer::gl::set_opengl_uniform_vec3(
        3,
        values.data(),
        pp::renderer::gl::OpenGlUniformVec3Dispatch {
            .uniform_3fv = record_uniform_3fv,
        });
    const auto vec2_status = pp::renderer::gl::set_opengl_uniform_vec2(
        2,
        values.data(),
        pp::renderer::gl::OpenGlUniformVec2Dispatch {
            .uniform_2fv = record_uniform_2fv,
        });
    const auto mat4_status = pp::renderer::gl::set_opengl_uniform_mat4(
        -1,
        values.data(),
        pp::renderer::gl::OpenGlUniformMat4Dispatch {
            .uniform_matrix_4fv = record_uniform_matrix_4fv,
        });

    PP_EXPECT(h, vec4_status.ok());
    PP_EXPECT(h, vec3_status.ok());
    PP_EXPECT(h, vec2_status.ok());
    PP_EXPECT(h, mat4_status.ok());
    PP_EXPECT(h, recorded_uniform_vector_calls.size() == 4U);
    PP_EXPECT(h, recorded_uniform_vector_calls[0].location == 4);
    PP_EXPECT(h, recorded_uniform_vector_calls[0].count == 1);
    PP_EXPECT(h, recorded_uniform_vector_calls[0].values == values.data());
    PP_EXPECT(h, recorded_uniform_vector_calls[0].component_count == 4U);
    PP_EXPECT(h, recorded_uniform_vector_calls[1].component_count == 3U);
    PP_EXPECT(h, recorded_uniform_vector_calls[2].component_count == 2U);
    PP_EXPECT(h, recorded_uniform_vector_calls[3].location == -1);
    PP_EXPECT(h, recorded_uniform_vector_calls[3].transpose == 0U);
    PP_EXPECT(h, recorded_uniform_vector_calls[3].component_count == 16U);
}

void sets_scalar_uniforms_through_dispatch(pp::tests::Harness& h)
{
    recorded_uniform_scalar_calls.clear();

    const auto int_status = pp::renderer::gl::set_opengl_uniform_int(
        7,
        42,
        pp::renderer::gl::OpenGlUniformIntDispatch {
            .uniform_1i = record_uniform_1i,
        });
    const auto float_status = pp::renderer::gl::set_opengl_uniform_float(
        -1,
        0.25F,
        pp::renderer::gl::OpenGlUniformFloatDispatch {
            .uniform_1f = record_uniform_1f,
        });

    PP_EXPECT(h, int_status.ok());
    PP_EXPECT(h, float_status.ok());
    PP_EXPECT(h, recorded_uniform_scalar_calls.size() == 2U);
    PP_EXPECT(h, recorded_uniform_scalar_calls[0].location == 7);
    PP_EXPECT(h, recorded_uniform_scalar_calls[0].int_value == 42);
    PP_EXPECT(h, !recorded_uniform_scalar_calls[0].is_float);
    PP_EXPECT(h, recorded_uniform_scalar_calls[1].location == -1);
    PP_EXPECT(h, recorded_uniform_scalar_calls[1].float_value == 0.25F);
    PP_EXPECT(h, recorded_uniform_scalar_calls[1].is_float);
}

void queries_attribute_location_through_dispatch(pp::tests::Harness& h)
{
    recorded_attrib_location_programs.clear();
    recorded_attrib_location_names.clear();

    const auto location = pp::renderer::gl::get_opengl_attribute_location(
        11U,
        "uvs",
        pp::renderer::gl::OpenGlAttributeLocationDispatch {
            .get_attrib_location = record_get_attrib_location,
        });
    const auto missing = pp::renderer::gl::get_opengl_attribute_location(
        11U,
        "missing",
        pp::renderer::gl::OpenGlAttributeLocationDispatch {
            .get_attrib_location = record_get_attrib_location,
        });

    PP_EXPECT(h, location.ok());
    PP_EXPECT(h, location.value() == 1);
    PP_EXPECT(h, missing.ok());
    PP_EXPECT(h, missing.value() == -1);
    PP_EXPECT(h, recorded_attrib_location_programs.size() == 2U);
    PP_EXPECT(h, recorded_attrib_location_programs[0] == 11U);
    PP_EXPECT(h, recorded_attrib_location_names[0] == std::string_view("uvs"));
    PP_EXPECT(h, recorded_attrib_location_names[1] == std::string_view("missing"));
}

void rejects_invalid_program_uniform_dispatch(pp::tests::Harness& h)
{
    constexpr std::array<float, 4> values { 1.0F, 2.0F, 3.0F, 4.0F };

    const auto missing_use = pp::renderer::gl::use_opengl_program(
        1U,
        pp::renderer::gl::OpenGlProgramUseDispatch {});
    const auto missing_delete = pp::renderer::gl::delete_opengl_program(
        1U,
        pp::renderer::gl::OpenGlProgramDeleteDispatch {
            .use_program = record_use_program,
        });
    const auto missing_vec4 = pp::renderer::gl::set_opengl_uniform_vec4(
        1,
        values.data(),
        pp::renderer::gl::OpenGlUniformVec4Dispatch {});
    const auto null_vec3 = pp::renderer::gl::set_opengl_uniform_vec3(
        1,
        nullptr,
        pp::renderer::gl::OpenGlUniformVec3Dispatch {
            .uniform_3fv = record_uniform_3fv,
        });
    const auto missing_vec2 = pp::renderer::gl::set_opengl_uniform_vec2(
        1,
        values.data(),
        pp::renderer::gl::OpenGlUniformVec2Dispatch {});
    const auto null_mat4 = pp::renderer::gl::set_opengl_uniform_mat4(
        1,
        nullptr,
        pp::renderer::gl::OpenGlUniformMat4Dispatch {
            .uniform_matrix_4fv = record_uniform_matrix_4fv,
        });
    const auto missing_int = pp::renderer::gl::set_opengl_uniform_int(
        1,
        2,
        pp::renderer::gl::OpenGlUniformIntDispatch {});
    const auto missing_float = pp::renderer::gl::set_opengl_uniform_float(
        1,
        2.0F,
        pp::renderer::gl::OpenGlUniformFloatDispatch {});
    const auto missing_attrib_dispatch = pp::renderer::gl::get_opengl_attribute_location(
        1U,
        "pos",
        pp::renderer::gl::OpenGlAttributeLocationDispatch {});
    const auto invalid_attrib_name = pp::renderer::gl::get_opengl_attribute_location(
        1U,
        "",
        pp::renderer::gl::OpenGlAttributeLocationDispatch {
            .get_attrib_location = record_get_attrib_location,
        });

    PP_EXPECT(h, !missing_use.ok());
    PP_EXPECT(h, missing_use.code == pp::foundation::StatusCode::invalid_argument);
    PP_EXPECT(h, !missing_delete.ok());
    PP_EXPECT(h, missing_delete.code == pp::foundation::StatusCode::invalid_argument);
    PP_EXPECT(h, !missing_vec4.ok());
    PP_EXPECT(h, missing_vec4.code == pp::foundation::StatusCode::invalid_argument);
    PP_EXPECT(h, !null_vec3.ok());
    PP_EXPECT(h, null_vec3.code == pp::foundation::StatusCode::invalid_argument);
    PP_EXPECT(h, !missing_vec2.ok());
    PP_EXPECT(h, missing_vec2.code == pp::foundation::StatusCode::invalid_argument);
    PP_EXPECT(h, !null_mat4.ok());
    PP_EXPECT(h, null_mat4.code == pp::foundation::StatusCode::invalid_argument);
    PP_EXPECT(h, !missing_int.ok());
    PP_EXPECT(h, missing_int.code == pp::foundation::StatusCode::invalid_argument);
    PP_EXPECT(h, !missing_float.ok());
    PP_EXPECT(h, missing_float.code == pp::foundation::StatusCode::invalid_argument);
    PP_EXPECT(h, !missing_attrib_dispatch.ok());
    PP_EXPECT(h, missing_attrib_dispatch.status().code == pp::foundation::StatusCode::invalid_argument);
    PP_EXPECT(h, !invalid_attrib_name.ok());
    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 creates_indexed_mesh_objects_through_dispatch(pp::tests::Harness& h)
{
    recorded_generated_buffer_counts.clear();
    recorded_generated_vertex_array_counts.clear();
    recorded_buffer_bind_calls.clear();
    recorded_buffer_data_calls.clear();
    recorded_vertex_array_bind_calls.clear();
    recorded_enabled_vertex_attributes.clear();
    recorded_vertex_attrib_pointer_calls.clear();
    next_buffer_id = 501U;
    next_vertex_array_id = 601U;

    constexpr std::array<float, 12> vertices {};
    constexpr std::array<std::uint16_t, 6> indices { 0, 1, 2, 0, 2, 3 };
    constexpr std::array attributes {
        pp::renderer::gl::OpenGlVertexAttribute {
            .index = 0U,
            .component_count = 4,
            .component_type = 0x1406U,
            .normalized = 0U,
            .stride = 32,
            .offset = 0U,
        },
        pp::renderer::gl::OpenGlVertexAttribute {
            .index = 1U,
            .component_count = 2,
            .component_type = 0x1406U,
            .normalized = 0U,
            .stride = 32,
            .offset = 16U,
        },
    };

    const auto mesh = pp::renderer::gl::create_opengl_mesh_objects(
        pp::renderer::gl::OpenGlMeshUpload {
            .vertex_data = vertices.data(),
            .vertex_byte_count = static_cast<std::intptr_t>(sizeof(float) * vertices.size()),
            .index_data = indices.data(),
            .index_byte_count = static_cast<std::intptr_t>(sizeof(std::uint16_t) * indices.size()),
            .indexed = true,
            .attributes = attributes,
        },
        pp::renderer::gl::OpenGlMeshCreateDispatch {
            .gen_buffers = record_gen_buffers,
            .bind_buffer = record_bind_buffer,
            .buffer_data = record_buffer_data,
            .gen_vertex_arrays = record_gen_vertex_arrays,
            .bind_vertex_array = record_bind_vertex_array,
            .enable_vertex_attrib_array = record_enable_vertex_attrib_array,
            .vertex_attrib_pointer = record_vertex_attrib_pointer,
        });

    PP_EXPECT(h, mesh.ok());
    PP_EXPECT(h, mesh.value().vertex_buffer == 501U);
    PP_EXPECT(h, mesh.value().index_buffer == 502U);
    PP_EXPECT(h, mesh.value().vertex_arrays[0] == 601U);
    PP_EXPECT(h, mesh.value().vertex_arrays[1] == 602U);
    PP_EXPECT(h, recorded_generated_buffer_counts.size() == 1U);
    PP_EXPECT(h, recorded_generated_buffer_counts[0] == 2U);
    PP_EXPECT(h, recorded_generated_vertex_array_counts.size() == 1U);
    PP_EXPECT(h, recorded_generated_vertex_array_counts[0] == 2U);
    PP_EXPECT(h, recorded_buffer_data_calls.size() == 2U);
    PP_EXPECT(h, recorded_buffer_data_calls[0].target == 0x8893U);
    PP_EXPECT(h, recorded_buffer_data_calls[0].byte_count == static_cast<std::intptr_t>(sizeof(std::uint16_t) * indices.size()));
    PP_EXPECT(h, recorded_buffer_data_calls[0].data == indices.data());
    PP_EXPECT(h, recorded_buffer_data_calls[0].usage == 0x88E4U);
    PP_EXPECT(h, recorded_buffer_data_calls[1].target == 0x8892U);
    PP_EXPECT(h, recorded_buffer_data_calls[1].byte_count == static_cast<std::intptr_t>(sizeof(float) * vertices.size()));
    PP_EXPECT(h, recorded_buffer_data_calls[1].data == vertices.data());
    PP_EXPECT(h, recorded_vertex_array_bind_calls.size() == 3U);
    PP_EXPECT(h, recorded_vertex_array_bind_calls[0] == 601U);
    PP_EXPECT(h, recorded_vertex_array_bind_calls[1] == 602U);
    PP_EXPECT(h, recorded_vertex_array_bind_calls[2] == 0U);
    PP_EXPECT(h, recorded_enabled_vertex_attributes.size() == 4U);
    PP_EXPECT(h, recorded_enabled_vertex_attributes[0] == 0U);
    PP_EXPECT(h, recorded_enabled_vertex_attributes[1] == 1U);
    PP_EXPECT(h, recorded_enabled_vertex_attributes[2] == 0U);
    PP_EXPECT(h, recorded_enabled_vertex_attributes[3] == 1U);
    PP_EXPECT(h, recorded_vertex_attrib_pointer_calls.size() == 4U);
    PP_EXPECT(h, recorded_vertex_attrib_pointer_calls[0].index == 0U);
    PP_EXPECT(h, recorded_vertex_attrib_pointer_calls[0].component_count == 4);
    PP_EXPECT(h, recorded_vertex_attrib_pointer_calls[0].component_type == 0x1406U);
    PP_EXPECT(h, recorded_vertex_attrib_pointer_calls[1].offset == reinterpret_cast<const void*>(16U));
}

void creates_dynamic_mesh_without_initial_vertex_upload(pp::tests::Harness& h)
{
    recorded_generated_buffer_counts.clear();
    recorded_buffer_data_calls.clear();
    recorded_vertex_array_bind_calls.clear();
    next_buffer_id = 701U;
    next_vertex_array_id = 801U;

    constexpr std::array attributes {
        pp::renderer::gl::OpenGlVertexAttribute {
            .index = 0U,
            .component_count = 4,
            .component_type = 0x1406U,
            .normalized = 0U,
            .stride = 32,
            .offset = 0U,
        },
    };

    const auto mesh = pp::renderer::gl::create_opengl_mesh_objects(
        pp::renderer::gl::OpenGlMeshUpload {
            .vertex_data = nullptr,
            .vertex_byte_count = 0,
            .index_data = nullptr,
            .index_byte_count = 0,
            .indexed = false,
            .attributes = attributes,
        },
        pp::renderer::gl::OpenGlMeshCreateDispatch {
            .gen_buffers = record_gen_buffers,
            .bind_buffer = record_bind_buffer,
            .buffer_data = record_buffer_data,
            .gen_vertex_arrays = record_gen_vertex_arrays,
            .bind_vertex_array = record_bind_vertex_array,
            .enable_vertex_attrib_array = record_enable_vertex_attrib_array,
            .vertex_attrib_pointer = record_vertex_attrib_pointer,
        });

    PP_EXPECT(h, mesh.ok());
    PP_EXPECT(h, mesh.value().vertex_buffer == 701U);
    PP_EXPECT(h, mesh.value().index_buffer == 0U);
    PP_EXPECT(h, recorded_generated_buffer_counts.size() == 1U);
    PP_EXPECT(h, recorded_generated_buffer_counts[0] == 1U);
    PP_EXPECT(h, recorded_buffer_data_calls.empty());
    PP_EXPECT(h, recorded_vertex_array_bind_calls.size() == 3U);
}

void creates_single_vertex_array_mesh_with_deferred_upload(pp::tests::Harness& h)
{
    recorded_generated_buffer_counts.clear();
    recorded_generated_vertex_array_counts.clear();
    recorded_buffer_bind_calls.clear();
    recorded_buffer_data_calls.clear();
    recorded_vertex_array_bind_calls.clear();
    recorded_enabled_vertex_attributes.clear();
    recorded_vertex_attrib_pointer_calls.clear();
    next_buffer_id = 901U;
    next_vertex_array_id = 1001U;

    constexpr std::array attributes {
        pp::renderer::gl::OpenGlVertexAttribute {
            .index = 0U,
            .component_count = 2,
            .component_type = 0x1406U,
            .normalized = 0U,
            .stride = 16,
            .offset = 0U,
        },
        pp::renderer::gl::OpenGlVertexAttribute {
            .index = 1U,
            .component_count = 2,
            .component_type = 0x1406U,
            .normalized = 0U,
            .stride = 16,
            .offset = 8U,
        },
    };

    const auto mesh = pp::renderer::gl::create_opengl_mesh_objects(
        pp::renderer::gl::OpenGlMeshUpload {
            .vertex_data = nullptr,
            .vertex_byte_count = 0,
            .index_data = nullptr,
            .index_byte_count = 0,
            .indexed = true,
            .vertex_array_count = 1U,
            .attributes = attributes,
        },
        pp::renderer::gl::OpenGlMeshCreateDispatch {
            .gen_buffers = record_gen_buffers,
            .bind_buffer = record_bind_buffer,
            .buffer_data = record_buffer_data,
            .gen_vertex_arrays = record_gen_vertex_arrays,
            .bind_vertex_array = record_bind_vertex_array,
            .enable_vertex_attrib_array = record_enable_vertex_attrib_array,
            .vertex_attrib_pointer = record_vertex_attrib_pointer,
        });

    PP_EXPECT(h, mesh.ok());
    PP_EXPECT(h, mesh.value().vertex_buffer == 901U);
    PP_EXPECT(h, mesh.value().index_buffer == 902U);
    PP_EXPECT(h, mesh.value().vertex_arrays[0] == 1001U);
    PP_EXPECT(h, mesh.value().vertex_arrays[1] == 0U);
    PP_EXPECT(h, recorded_generated_buffer_counts.size() == 1U);
    PP_EXPECT(h, recorded_generated_buffer_counts[0] == 2U);
    PP_EXPECT(h, recorded_generated_vertex_array_counts.size() == 1U);
    PP_EXPECT(h, recorded_generated_vertex_array_counts[0] == 1U);
    PP_EXPECT(h, recorded_buffer_data_calls.empty());
    PP_EXPECT(h, recorded_vertex_array_bind_calls.size() == 2U);
    PP_EXPECT(h, recorded_vertex_array_bind_calls[0] == 1001U);
    PP_EXPECT(h, recorded_vertex_array_bind_calls[1] == 0U);
    PP_EXPECT(h, recorded_enabled_vertex_attributes.size() == 2U);
    PP_EXPECT(h, recorded_vertex_attrib_pointer_calls.size() == 2U);
    PP_EXPECT(h, recorded_vertex_attrib_pointer_calls[0].index == 0U);
    PP_EXPECT(h, recorded_vertex_attrib_pointer_calls[1].offset == reinterpret_cast<const void*>(8U));
}

void updates_draws_and_deletes_mesh_through_dispatch(pp::tests::Harness& h)
{
    recorded_buffer_bind_calls.clear();
    recorded_buffer_data_calls.clear();
    recorded_vertex_array_bind_calls.clear();
    recorded_mesh_draw_calls.clear();
    recorded_deleted_buffers.clear();
    recorded_deleted_vertex_arrays.clear();

    constexpr std::array<float, 8> vertices {};
    const auto upload_status = pp::renderer::gl::upload_opengl_buffer_data(
        pp::renderer::gl::OpenGlBufferUpload {
            .target = 0x8892U,
            .buffer_id = 501U,
            .data = vertices.data(),
            .byte_count = static_cast<std::intptr_t>(sizeof(float) * vertices.size()),
            .usage = 0x88E4U,
        },
        pp::renderer::gl::OpenGlBufferUploadDispatch {
            .bind_buffer = record_bind_buffer,
            .buffer_data = record_buffer_data,
        });
    const auto indexed_draw = pp::renderer::gl::draw_opengl_mesh(
        pp::renderer::gl::OpenGlMeshDraw {
            .vertex_array = 601U,
            .mode = 0x0004U,
            .count = 6,
            .indexed = true,
            .index_type = 0x1403U,
            .index_offset = reinterpret_cast<const void*>(12U),
        },
        pp::renderer::gl::OpenGlMeshDrawDispatch {
            .bind_vertex_array = record_bind_vertex_array,
            .draw_elements = record_draw_elements,
            .draw_arrays = record_draw_arrays,
        });
    const auto array_draw = pp::renderer::gl::draw_opengl_mesh(
        pp::renderer::gl::OpenGlMeshDraw {
            .vertex_array = 602U,
            .mode = 0x0001U,
            .count = 2,
            .indexed = false,
        },
        pp::renderer::gl::OpenGlMeshDrawDispatch {
            .bind_vertex_array = record_bind_vertex_array,
            .draw_elements = record_draw_elements,
            .draw_arrays = record_draw_arrays,
        });
    const auto delete_status = pp::renderer::gl::delete_opengl_mesh_objects(
        pp::renderer::gl::OpenGlMeshDelete {
            .buffers = { 501U, 502U },
            .vertex_arrays = { 601U, 602U },
        },
        pp::renderer::gl::OpenGlMeshDeleteDispatch {
            .delete_buffers = record_delete_buffers,
            .delete_vertex_arrays = record_delete_vertex_arrays,
        });

    PP_EXPECT(h, upload_status.ok());
    PP_EXPECT(h, indexed_draw.ok());
    PP_EXPECT(h, array_draw.ok());
    PP_EXPECT(h, delete_status.ok());
    PP_EXPECT(h, recorded_buffer_bind_calls.size() >= 2U);
    PP_EXPECT(h, recorded_buffer_bind_calls[0].target == 0x8892U);
    PP_EXPECT(h, recorded_buffer_bind_calls[0].buffer == 501U);
    PP_EXPECT(h, recorded_buffer_bind_calls[1].target == 0x8892U);
    PP_EXPECT(h, recorded_buffer_bind_calls[1].buffer == 0U);
    PP_EXPECT(h, recorded_buffer_data_calls.size() == 1U);
    PP_EXPECT(h, recorded_buffer_data_calls[0].data == vertices.data());
    PP_EXPECT(h, recorded_vertex_array_bind_calls.size() == 4U);
    PP_EXPECT(h, recorded_vertex_array_bind_calls[0] == 601U);
    PP_EXPECT(h, recorded_vertex_array_bind_calls[1] == 0U);
    PP_EXPECT(h, recorded_vertex_array_bind_calls[2] == 602U);
    PP_EXPECT(h, recorded_vertex_array_bind_calls[3] == 0U);
    PP_EXPECT(h, recorded_mesh_draw_calls.size() == 2U);
    PP_EXPECT(h, recorded_mesh_draw_calls[0].indexed);
    PP_EXPECT(h, recorded_mesh_draw_calls[0].mode == 0x0004U);
    PP_EXPECT(h, recorded_mesh_draw_calls[0].count == 6);
    PP_EXPECT(h, recorded_mesh_draw_calls[0].index_type == 0x1403U);
    PP_EXPECT(h, recorded_mesh_draw_calls[0].index_offset == reinterpret_cast<const void*>(12U));
    PP_EXPECT(h, !recorded_mesh_draw_calls[1].indexed);
    PP_EXPECT(h, recorded_mesh_draw_calls[1].first == 0);
    PP_EXPECT(h, recorded_deleted_buffers.size() == 2U);
    PP_EXPECT(h, recorded_deleted_buffers[0] == 501U);
    PP_EXPECT(h, recorded_deleted_buffers[1] == 502U);
    PP_EXPECT(h, recorded_deleted_vertex_arrays.size() == 2U);
    PP_EXPECT(h, recorded_deleted_vertex_arrays[0] == 601U);
    PP_EXPECT(h, recorded_deleted_vertex_arrays[1] == 602U);
}

void rejects_invalid_mesh_dispatch(pp::tests::Harness& h)
{
    constexpr std::array<float, 4> vertices {};
    constexpr std::array attributes {
        pp::renderer::gl::OpenGlVertexAttribute {
            .index = 0U,
            .component_count = 4,
            .component_type = 0x1406U,
            .normalized = 0U,
            .stride = 16,
            .offset = 0U,
        },
    };
    constexpr std::array bad_attributes {
        pp::renderer::gl::OpenGlVertexAttribute {
            .index = 0U,
            .component_count = 0,
            .component_type = 0x1406U,
            .normalized = 0U,
            .stride = 16,
            .offset = 0U,
        },
    };

    const auto missing_create_dispatch = pp::renderer::gl::create_opengl_mesh_objects(
        pp::renderer::gl::OpenGlMeshUpload {
            .vertex_data = vertices.data(),
            .vertex_byte_count = static_cast<std::intptr_t>(sizeof(float) * vertices.size()),
            .attributes = attributes,
        },
        pp::renderer::gl::OpenGlMeshCreateDispatch {
            .gen_buffers = record_gen_buffers,
        });
    const auto invalid_create_data = pp::renderer::gl::create_opengl_mesh_objects(
        pp::renderer::gl::OpenGlMeshUpload {
            .vertex_data = nullptr,
            .vertex_byte_count = 4,
            .attributes = attributes,
        },
        pp::renderer::gl::OpenGlMeshCreateDispatch {
            .gen_buffers = record_gen_buffers,
            .bind_buffer = record_bind_buffer,
            .buffer_data = record_buffer_data,
            .gen_vertex_arrays = record_gen_vertex_arrays,
            .bind_vertex_array = record_bind_vertex_array,
            .enable_vertex_attrib_array = record_enable_vertex_attrib_array,
            .vertex_attrib_pointer = record_vertex_attrib_pointer,
        });
    const auto invalid_attribute = pp::renderer::gl::create_opengl_mesh_objects(
        pp::renderer::gl::OpenGlMeshUpload {
            .vertex_data = vertices.data(),
            .vertex_byte_count = static_cast<std::intptr_t>(sizeof(float) * vertices.size()),
            .attributes = bad_attributes,
        },
        pp::renderer::gl::OpenGlMeshCreateDispatch {
            .gen_buffers = record_gen_buffers,
            .bind_buffer = record_bind_buffer,
            .buffer_data = record_buffer_data,
            .gen_vertex_arrays = record_gen_vertex_arrays,
            .bind_vertex_array = record_bind_vertex_array,
            .enable_vertex_attrib_array = record_enable_vertex_attrib_array,
            .vertex_attrib_pointer = record_vertex_attrib_pointer,
        });
    const auto invalid_vertex_array_count = pp::renderer::gl::create_opengl_mesh_objects(
        pp::renderer::gl::OpenGlMeshUpload {
            .vertex_data = vertices.data(),
            .vertex_byte_count = static_cast<std::intptr_t>(sizeof(float) * vertices.size()),
            .vertex_array_count = 3U,
            .attributes = attributes,
        },
        pp::renderer::gl::OpenGlMeshCreateDispatch {
            .gen_buffers = record_gen_buffers,
            .bind_buffer = record_bind_buffer,
            .buffer_data = record_buffer_data,
            .gen_vertex_arrays = record_gen_vertex_arrays,
            .bind_vertex_array = record_bind_vertex_array,
            .enable_vertex_attrib_array = record_enable_vertex_attrib_array,
            .vertex_attrib_pointer = record_vertex_attrib_pointer,
        });
    const auto missing_upload_dispatch = pp::renderer::gl::upload_opengl_buffer_data(
        pp::renderer::gl::OpenGlBufferUpload {
            .target = 0x8892U,
            .buffer_id = 1U,
            .data = vertices.data(),
            .byte_count = static_cast<std::intptr_t>(sizeof(float) * vertices.size()),
            .usage = 0x88E4U,
        },
        pp::renderer::gl::OpenGlBufferUploadDispatch {});
    const auto invalid_upload = pp::renderer::gl::upload_opengl_buffer_data(
        pp::renderer::gl::OpenGlBufferUpload {
            .target = 0x8892U,
            .buffer_id = 0U,
            .data = vertices.data(),
            .byte_count = static_cast<std::intptr_t>(sizeof(float) * vertices.size()),
            .usage = 0x88E4U,
        },
        pp::renderer::gl::OpenGlBufferUploadDispatch {
            .bind_buffer = record_bind_buffer,
            .buffer_data = record_buffer_data,
        });
    const auto missing_draw_dispatch = pp::renderer::gl::draw_opengl_mesh(
        pp::renderer::gl::OpenGlMeshDraw {
            .vertex_array = 1U,
            .mode = 0x0004U,
            .count = 6,
        },
        pp::renderer::gl::OpenGlMeshDrawDispatch {});
    const auto invalid_draw = pp::renderer::gl::draw_opengl_mesh(
        pp::renderer::gl::OpenGlMeshDraw {
            .vertex_array = 0U,
            .mode = 0x0004U,
            .count = 6,
        },
        pp::renderer::gl::OpenGlMeshDrawDispatch {
            .bind_vertex_array = record_bind_vertex_array,
            .draw_elements = record_draw_elements,
            .draw_arrays = record_draw_arrays,
        });
    const auto missing_delete_dispatch = pp::renderer::gl::delete_opengl_mesh_objects(
        pp::renderer::gl::OpenGlMeshDelete {},
        pp::renderer::gl::OpenGlMeshDeleteDispatch {});

    PP_EXPECT(h, !missing_create_dispatch.ok());
    PP_EXPECT(h, missing_create_dispatch.status().code == pp::foundation::StatusCode::invalid_argument);
    PP_EXPECT(h, !invalid_create_data.ok());
    PP_EXPECT(h, invalid_create_data.status().code == pp::foundation::StatusCode::invalid_argument);
    PP_EXPECT(h, !invalid_attribute.ok());
    PP_EXPECT(h, invalid_attribute.status().code == pp::foundation::StatusCode::invalid_argument);
    PP_EXPECT(h, !invalid_vertex_array_count.ok());
    PP_EXPECT(h, invalid_vertex_array_count.status().code == pp::foundation::StatusCode::invalid_argument);
    PP_EXPECT(h, !missing_upload_dispatch.ok());
    PP_EXPECT(h, missing_upload_dispatch.code == pp::foundation::StatusCode::invalid_argument);
    PP_EXPECT(h, !invalid_upload.ok());
    PP_EXPECT(h, invalid_upload.code == pp::foundation::StatusCode::invalid_argument);
    PP_EXPECT(h, !missing_draw_dispatch.ok());
    PP_EXPECT(h, missing_draw_dispatch.code == pp::foundation::StatusCode::invalid_argument);
    PP_EXPECT(h, !invalid_draw.ok());
    PP_EXPECT(h, invalid_draw.code == pp::foundation::StatusCode::invalid_argument);
    PP_EXPECT(h, !missing_delete_dispatch.ok());
    PP_EXPECT(h, missing_delete_dispatch.code == pp::foundation::StatusCode::invalid_argument);
}

void creates_reads_maps_and_deletes_pixel_buffers_through_dispatch(pp::tests::Harness& h)
{
    recorded_generated_buffer_counts.clear();
    recorded_buffer_bind_calls.clear();
    recorded_buffer_data_calls.clear();
    recorded_read_pixels_calls.clear();
    recorded_buffer_map_calls.clear();
    recorded_buffer_unmap_calls.clear();
    recorded_deleted_buffers.clear();
    next_buffer_id = 801U;

    const auto allocated = pp::renderer::gl::allocate_opengl_pixel_buffer(
        pp::renderer::gl::OpenGlPixelBufferAllocationDispatch {
            .gen_buffers = record_gen_buffers,
        });
    const auto readback = pp::renderer::gl::readback_opengl_framebuffer_to_pixel_buffer(
        8,
        4,
        pp::renderer::gl::rgba8_readback_format(),
        pp::renderer::gl::OpenGlPixelBufferReadbackDispatch {
            .gen_buffers = record_gen_buffers,
            .bind_buffer = record_bind_buffer,
            .buffer_data = record_buffer_data,
            .read_pixels = record_read_pixels,
        });
    const auto mapped = pp::renderer::gl::map_opengl_pixel_buffer(
        readback.ok() ? readback.value() : 0U,
        8,
        4,
        pp::renderer::gl::rgba8_readback_format(),
        pp::renderer::gl::OpenGlPixelBufferMapDispatch {
            .bind_buffer = record_bind_buffer,
            .map_buffer_range = record_map_buffer_range,
        });
    const auto unmap_status = pp::renderer::gl::unmap_opengl_pixel_buffer(
        readback.ok() ? readback.value() : 0U,
        pp::renderer::gl::OpenGlPixelBufferUnmapDispatch {
            .bind_buffer = record_bind_buffer,
            .unmap_buffer = record_unmap_buffer,
        });
    const auto delete_status = pp::renderer::gl::delete_opengl_pixel_buffer(
        readback.ok() ? readback.value() : 0U,
        pp::renderer::gl::OpenGlPixelBufferDeleteDispatch {
            .delete_buffers = record_delete_buffers,
        });

    PP_EXPECT(h, allocated.ok());
    PP_EXPECT(h, allocated.value() == 801U);
    PP_EXPECT(h, readback.ok());
    PP_EXPECT(h, readback.value() == 802U);
    PP_EXPECT(h, mapped.ok());
    PP_EXPECT(h, mapped.value() == reinterpret_cast<void*>(0x1234U));
    PP_EXPECT(h, unmap_status.ok());
    PP_EXPECT(h, delete_status.ok());
    PP_EXPECT(h, recorded_generated_buffer_counts.size() == 2U);
    PP_EXPECT(h, recorded_generated_buffer_counts[0] == 1U);
    PP_EXPECT(h, recorded_generated_buffer_counts[1] == 1U);
    PP_EXPECT(h, recorded_buffer_bind_calls.size() == 6U);
    PP_EXPECT(h, recorded_buffer_bind_calls[0].target == 0x88EBU);
    PP_EXPECT(h, recorded_buffer_bind_calls[0].buffer == 802U);
    PP_EXPECT(h, recorded_buffer_bind_calls[1].target == 0x88EBU);
    PP_EXPECT(h, recorded_buffer_bind_calls[1].buffer == 0U);
    PP_EXPECT(h, recorded_buffer_bind_calls[2].buffer == 802U);
    PP_EXPECT(h, recorded_buffer_bind_calls[3].buffer == 0U);
    PP_EXPECT(h, recorded_buffer_bind_calls[4].buffer == 802U);
    PP_EXPECT(h, recorded_buffer_bind_calls[5].buffer == 0U);
    PP_EXPECT(h, recorded_buffer_data_calls.size() == 1U);
    PP_EXPECT(h, recorded_buffer_data_calls[0].target == 0x88EBU);
    PP_EXPECT(h, recorded_buffer_data_calls[0].byte_count == 128);
    PP_EXPECT(h, recorded_buffer_data_calls[0].data == nullptr);
    PP_EXPECT(h, recorded_buffer_data_calls[0].usage == 0x88E1U);
    PP_EXPECT(h, recorded_read_pixels_calls.size() == 1U);
    PP_EXPECT(h, recorded_read_pixels_calls[0].width == 8);
    PP_EXPECT(h, recorded_read_pixels_calls[0].height == 4);
    PP_EXPECT(h, recorded_read_pixels_calls[0].pixel_format == 0x1908U);
    PP_EXPECT(h, recorded_read_pixels_calls[0].component_type == 0x1401U);
    PP_EXPECT(h, recorded_read_pixels_calls[0].pixels == nullptr);
    PP_EXPECT(h, recorded_buffer_map_calls.size() == 1U);
    PP_EXPECT(h, recorded_buffer_map_calls[0].target == 0x88EBU);
    PP_EXPECT(h, recorded_buffer_map_calls[0].offset == 0);
    PP_EXPECT(h, recorded_buffer_map_calls[0].byte_count == 128);
    PP_EXPECT(h, recorded_buffer_map_calls[0].access == 0x0001U);
    PP_EXPECT(h, recorded_buffer_unmap_calls.size() == 1U);
    PP_EXPECT(h, recorded_buffer_unmap_calls[0] == 0x88EBU);
    PP_EXPECT(h, recorded_deleted_buffers.size() == 1U);
    PP_EXPECT(h, recorded_deleted_buffers[0] == 802U);
}

void rejects_invalid_pixel_buffer_dispatch(pp::tests::Harness& h)
{
    const auto missing_allocate_dispatch = pp::renderer::gl::allocate_opengl_pixel_buffer(
        pp::renderer::gl::OpenGlPixelBufferAllocationDispatch {});
    const auto missing_readback_dispatch = pp::renderer::gl::readback_opengl_framebuffer_to_pixel_buffer(
        8,
        4,
        pp::renderer::gl::rgba8_readback_format(),
        pp::renderer::gl::OpenGlPixelBufferReadbackDispatch {});
    const auto invalid_readback_size = pp::renderer::gl::readback_opengl_framebuffer_to_pixel_buffer(
        0,
        4,
        pp::renderer::gl::rgba8_readback_format(),
        pp::renderer::gl::OpenGlPixelBufferReadbackDispatch {
            .gen_buffers = record_gen_buffers,
            .bind_buffer = record_bind_buffer,
            .buffer_data = record_buffer_data,
            .read_pixels = record_read_pixels,
        });
    const auto missing_map_dispatch = pp::renderer::gl::map_opengl_pixel_buffer(
        1U,
        8,
        4,
        pp::renderer::gl::rgba8_readback_format(),
        pp::renderer::gl::OpenGlPixelBufferMapDispatch {});
    const auto invalid_map_buffer = pp::renderer::gl::map_opengl_pixel_buffer(
        0U,
        8,
        4,
        pp::renderer::gl::rgba8_readback_format(),
        pp::renderer::gl::OpenGlPixelBufferMapDispatch {
            .bind_buffer = record_bind_buffer,
            .map_buffer_range = record_map_buffer_range,
        });
    const auto failed_map = pp::renderer::gl::map_opengl_pixel_buffer(
        1U,
        8,
        4,
        pp::renderer::gl::rgba8_readback_format(),
        pp::renderer::gl::OpenGlPixelBufferMapDispatch {
            .bind_buffer = record_bind_buffer,
            .map_buffer_range = record_failed_map_buffer_range,
        });
    const auto missing_unmap_dispatch = pp::renderer::gl::unmap_opengl_pixel_buffer(
        1U,
        pp::renderer::gl::OpenGlPixelBufferUnmapDispatch {});
    const auto invalid_unmap_buffer = pp::renderer::gl::unmap_opengl_pixel_buffer(
        0U,
        pp::renderer::gl::OpenGlPixelBufferUnmapDispatch {
            .bind_buffer = record_bind_buffer,
            .unmap_buffer = record_unmap_buffer,
        });
    const auto missing_delete_dispatch = pp::renderer::gl::delete_opengl_pixel_buffer(
        1U,
        pp::renderer::gl::OpenGlPixelBufferDeleteDispatch {});

    PP_EXPECT(h, !missing_allocate_dispatch.ok());
    PP_EXPECT(h, missing_allocate_dispatch.status().code == pp::foundation::StatusCode::invalid_argument);
    PP_EXPECT(h, !missing_readback_dispatch.ok());
    PP_EXPECT(h, missing_readback_dispatch.status().code == pp::foundation::StatusCode::invalid_argument);
    PP_EXPECT(h, !invalid_readback_size.ok());
    PP_EXPECT(h, invalid_readback_size.status().code == pp::foundation::StatusCode::invalid_argument);
    PP_EXPECT(h, !missing_map_dispatch.ok());
    PP_EXPECT(h, missing_map_dispatch.status().code == pp::foundation::StatusCode::invalid_argument);
    PP_EXPECT(h, !invalid_map_buffer.ok());
    PP_EXPECT(h, invalid_map_buffer.status().code == pp::foundation::StatusCode::invalid_argument);
    PP_EXPECT(h, !failed_map.ok());
    PP_EXPECT(h, failed_map.status().code == pp::foundation::StatusCode::out_of_range);
    PP_EXPECT(h, !missing_unmap_dispatch.ok());
    PP_EXPECT(h, missing_unmap_dispatch.code == pp::foundation::StatusCode::invalid_argument);
    PP_EXPECT(h, !invalid_unmap_buffer.ok());
    PP_EXPECT(h, invalid_unmap_buffer.code == pp::foundation::StatusCode::invalid_argument);
    PP_EXPECT(h, !missing_delete_dispatch.ok());
    PP_EXPECT(h, missing_delete_dispatch.code == pp::foundation::StatusCode::invalid_argument);
}

void updates_texture_2d_through_dispatch(pp::tests::Harness& h)
{
    recorded_binding_calls.clear();
    recorded_texture_image_calls.clear();
    const std::array<std::uint8_t, 4> pixels { 9U, 8U, 7U, 6U };

    const auto status = pp::renderer::gl::update_opengl_texture_2d(
        pp::renderer::gl::OpenGlTexture2DUpdate {
            .texture_id = 31U,
            .x = 3,
            .y = 4,
            .width = 2,
            .height = 2,
            .pixel_format = 0x1908U,
            .component_type = 0x1401U,
            .data = pixels.data(),
        },
        pp::renderer::gl::OpenGlTexture2DUpdateDispatch {
            .bind_texture = record_bind_texture,
            .tex_sub_image_2d = record_tex_sub_image_2d,
        });

    PP_EXPECT(h, status.ok());
    PP_EXPECT(h, recorded_binding_calls.size() == 1U);
    PP_EXPECT(h, recorded_binding_calls[0].second == 31U);
    PP_EXPECT(h, recorded_texture_image_calls.size() == 1U);
    PP_EXPECT(h, recorded_texture_image_calls[0].sub_image);
    PP_EXPECT(h, recorded_texture_image_calls[0].x == 3);
    PP_EXPECT(h, recorded_texture_image_calls[0].y == 4);
    PP_EXPECT(h, recorded_texture_image_calls[0].width == 2);
    PP_EXPECT(h, recorded_texture_image_calls[0].height == 2);
    PP_EXPECT(h, recorded_texture_image_calls[0].data == pixels.data());
}

void rejects_invalid_texture_2d_updates(pp::tests::Harness& h)
{
    const std::array<std::uint8_t, 4> pixels { 9U, 8U, 7U, 6U };

    const auto negative_offset = pp::renderer::gl::update_opengl_texture_2d(
        pp::renderer::gl::OpenGlTexture2DUpdate {
            .texture_id = 31U,
            .x = -1,
            .width = 2,
            .height = 2,
            .pixel_format = 0x1908U,
            .component_type = 0x1401U,
            .data = pixels.data(),
        },
        pp::renderer::gl::OpenGlTexture2DUpdateDispatch {
            .bind_texture = record_bind_texture,
            .tex_sub_image_2d = record_tex_sub_image_2d,
        });
    const auto missing_dispatch = pp::renderer::gl::update_opengl_texture_2d(
        pp::renderer::gl::OpenGlTexture2DUpdate {
            .texture_id = 31U,
            .width = 2,
            .height = 2,
            .pixel_format = 0x1908U,
            .component_type = 0x1401U,
            .data = pixels.data(),
        },
        pp::renderer::gl::OpenGlTexture2DUpdateDispatch {});

    PP_EXPECT(h, !negative_offset.ok());
    PP_EXPECT(h, negative_offset.code == pp::foundation::StatusCode::invalid_argument);
    PP_EXPECT(h, !missing_dispatch.ok());
    PP_EXPECT(h, missing_dispatch.code == pp::foundation::StatusCode::invalid_argument);
}

void copies_framebuffer_to_texture_2d_through_dispatch(pp::tests::Harness& h)
{
    recorded_framebuffer_texture_copy_calls.clear();

    const auto status = pp::renderer::gl::copy_opengl_framebuffer_to_texture_2d(
        pp::renderer::gl::OpenGlTexture2DFramebufferCopy {
            .level = 1,
            .destination_x = 2,
            .destination_y = 3,
            .source_x = 4,
            .source_y = 5,
            .width = 6,
            .height = 7,
        },
        pp::renderer::gl::OpenGlTexture2DFramebufferCopyDispatch {
            .copy_tex_sub_image_2d = record_copy_tex_sub_image_2d,
        });

    PP_EXPECT(h, status.ok());
    PP_EXPECT(h, recorded_framebuffer_texture_copy_calls.size() == 1U);
    PP_EXPECT(h, recorded_framebuffer_texture_copy_calls[0].target == 0x0DE1U);
    PP_EXPECT(h, recorded_framebuffer_texture_copy_calls[0].level == 1);
    PP_EXPECT(h, recorded_framebuffer_texture_copy_calls[0].destination_x == 2);
    PP_EXPECT(h, recorded_framebuffer_texture_copy_calls[0].destination_y == 3);
    PP_EXPECT(h, recorded_framebuffer_texture_copy_calls[0].source_x == 4);
    PP_EXPECT(h, recorded_framebuffer_texture_copy_calls[0].source_y == 5);
    PP_EXPECT(h, recorded_framebuffer_texture_copy_calls[0].width == 6);
    PP_EXPECT(h, recorded_framebuffer_texture_copy_calls[0].height == 7);
}

void copies_framebuffer_to_requested_texture_target(pp::tests::Harness& h)
{
    recorded_framebuffer_texture_copy_calls.clear();

    const auto status = pp::renderer::gl::copy_opengl_framebuffer_to_texture_2d(
        pp::renderer::gl::OpenGlTexture2DFramebufferCopy {
            .texture_target = pp::renderer::gl::cube_face_texture_target(2U),
            .width = 4,
            .height = 4,
        },
        pp::renderer::gl::OpenGlTexture2DFramebufferCopyDispatch {
            .copy_tex_sub_image_2d = record_copy_tex_sub_image_2d,
        });

    PP_EXPECT(h, status.ok());
    PP_EXPECT(h, recorded_framebuffer_texture_copy_calls.size() == 1U);
    PP_EXPECT(h, recorded_framebuffer_texture_copy_calls[0].target == pp::renderer::gl::cube_face_texture_target(2U));
}

void skips_zero_sized_framebuffer_to_texture_copies(pp::tests::Harness& h)
{
    recorded_framebuffer_texture_copy_calls.clear();

    const auto status = pp::renderer::gl::copy_opengl_framebuffer_to_texture_2d(
        pp::renderer::gl::OpenGlTexture2DFramebufferCopy {
            .width = 0,
            .height = 5,
        },
        pp::renderer::gl::OpenGlTexture2DFramebufferCopyDispatch {
            .copy_tex_sub_image_2d = record_copy_tex_sub_image_2d,
        });

    PP_EXPECT(h, status.ok());
    PP_EXPECT(h, recorded_framebuffer_texture_copy_calls.empty());
}

void rejects_invalid_framebuffer_to_texture_copies(pp::tests::Harness& h)
{
    const auto missing_dispatch = pp::renderer::gl::copy_opengl_framebuffer_to_texture_2d(
        pp::renderer::gl::OpenGlTexture2DFramebufferCopy {
            .width = 1,
            .height = 1,
        },
        pp::renderer::gl::OpenGlTexture2DFramebufferCopyDispatch {});
    const auto invalid_dimensions = pp::renderer::gl::copy_opengl_framebuffer_to_texture_2d(
        pp::renderer::gl::OpenGlTexture2DFramebufferCopy {
            .width = -1,
            .height = 1,
        },
        pp::renderer::gl::OpenGlTexture2DFramebufferCopyDispatch {
            .copy_tex_sub_image_2d = record_copy_tex_sub_image_2d,
        });

    PP_EXPECT(h, !missing_dispatch.ok());
    PP_EXPECT(h, missing_dispatch.code == pp::foundation::StatusCode::invalid_argument);
    PP_EXPECT(h, !invalid_dimensions.ok());
    PP_EXPECT(h, invalid_dimensions.code == pp::foundation::StatusCode::invalid_argument);
}

void generates_texture_2d_mipmaps_through_dispatch(pp::tests::Harness& h)
{
    recorded_binding_calls.clear();
    recorded_mipmap_targets.clear();

    const auto status = pp::renderer::gl::generate_opengl_texture_2d_mipmaps(
        41U,
        pp::renderer::gl::OpenGlTexture2DMipmapDispatch {
            .bind_texture = record_bind_texture,
            .generate_mipmap = record_generate_mipmap,
        });

    PP_EXPECT(h, status.ok());
    PP_EXPECT(h, recorded_binding_calls.size() == 2U);
    PP_EXPECT(h, recorded_binding_calls[0].second == 41U);
    PP_EXPECT(h, recorded_binding_calls[1].second == 0U);
    PP_EXPECT(h, recorded_mipmap_targets.size() == 1U);
    PP_EXPECT(h, recorded_mipmap_targets[0] == 0x0DE1U);
}

void reads_back_texture_2d_through_framebuffer_dispatch(pp::tests::Harness& h)
{
    recorded_generated_framebuffer_counts.clear();
    recorded_deleted_framebuffers.clear();
    recorded_binding_calls.clear();
    recorded_framebuffer_attachment_calls.clear();
    recorded_framebuffer_status_queries.clear();
    recorded_read_pixels_calls.clear();
    next_framebuffer_id = 44U;
    configured_framebuffer_status = 0x8CD5U;
    std::array<std::uint8_t, 16> pixels {};

    const auto result = pp::renderer::gl::readback_opengl_texture_2d(
        pp::renderer::gl::OpenGlTexture2DReadback {
            .texture_id = 56U,
            .width = 2,
            .height = 2,
            .format = pp::renderer::gl::rgba8_readback_format(),
            .pixels = pixels.data(),
        },
        pp::renderer::gl::OpenGlTexture2DReadbackDispatch {
            .bind_texture = record_bind_texture,
            .gen_framebuffers = record_gen_framebuffers,
            .get_integer = record_get_integer,
            .bind_framebuffer = record_bind_framebuffer,
            .framebuffer_texture_2d = record_framebuffer_texture_2d,
            .check_framebuffer_status = record_check_framebuffer_status,
            .read_pixels = record_read_pixels,
            .delete_framebuffers = record_delete_framebuffers,
        });

    PP_EXPECT(h, result.ok());
    PP_EXPECT(h, result.value().pixels_read);
    PP_EXPECT(h, result.value().framebuffer_status == 0x8CD5U);
    PP_EXPECT(h, recorded_generated_framebuffer_counts.size() == 1U);
    PP_EXPECT(h, recorded_generated_framebuffer_counts[0] == 1U);
    PP_EXPECT(h, recorded_binding_calls.size() == 3U);
    PP_EXPECT(h, recorded_binding_calls[0].kind == RecordedOpenGlBindingCall::Kind::bind_texture);
    PP_EXPECT(h, recorded_binding_calls[0].second == 56U);
    PP_EXPECT(h, recorded_binding_calls[1].kind == RecordedOpenGlBindingCall::Kind::bind_framebuffer);
    PP_EXPECT(h, recorded_binding_calls[1].first == 0x8D40U);
    PP_EXPECT(h, recorded_binding_calls[1].second == 44U);
    PP_EXPECT(h, recorded_binding_calls[2].kind == RecordedOpenGlBindingCall::Kind::bind_framebuffer);
    PP_EXPECT(h, recorded_binding_calls[2].second == 7U);
    PP_EXPECT(h, recorded_framebuffer_attachment_calls.size() == 1U);
    PP_EXPECT(h, recorded_framebuffer_attachment_calls[0].attachment == 0x8CE0U);
    PP_EXPECT(h, recorded_framebuffer_attachment_calls[0].texture == 56U);
    PP_EXPECT(h, recorded_read_pixels_calls.size() == 1U);
    PP_EXPECT(h, recorded_read_pixels_calls[0].pixels == pixels.data());
    PP_EXPECT(h, recorded_deleted_framebuffers.size() == 1U);
    PP_EXPECT(h, recorded_deleted_framebuffers[0] == 44U);
}

void reports_incomplete_texture_2d_readback_framebuffer(pp::tests::Harness& h)
{
    recorded_deleted_framebuffers.clear();
    recorded_read_pixels_calls.clear();
    next_framebuffer_id = 45U;
    configured_framebuffer_status = 0x8CD6U;
    std::array<std::uint8_t, 16> pixels {};

    const auto result = pp::renderer::gl::readback_opengl_texture_2d(
        pp::renderer::gl::OpenGlTexture2DReadback {
            .texture_id = 57U,
            .width = 2,
            .height = 2,
            .format = pp::renderer::gl::rgba8_readback_format(),
            .pixels = pixels.data(),
        },
        pp::renderer::gl::OpenGlTexture2DReadbackDispatch {
            .bind_texture = record_bind_texture,
            .gen_framebuffers = record_gen_framebuffers,
            .get_integer = record_get_integer,
            .bind_framebuffer = record_bind_framebuffer,
            .framebuffer_texture_2d = record_framebuffer_texture_2d,
            .check_framebuffer_status = record_check_framebuffer_status,
            .read_pixels = record_read_pixels,
            .delete_framebuffers = record_delete_framebuffers,
        });

    PP_EXPECT(h, result.ok());
    PP_EXPECT(h, !result.value().pixels_read);
    PP_EXPECT(h, result.value().framebuffer_status == 0x8CD6U);
    PP_EXPECT(h, recorded_read_pixels_calls.empty());
    PP_EXPECT(h, recorded_deleted_framebuffers.back() == 45U);
}

void rejects_incomplete_texture_2d_readback_dispatch(pp::tests::Harness& h)
{
    std::array<std::uint8_t, 4> pixels {};
    const auto result = pp::renderer::gl::readback_opengl_texture_2d(
        pp::renderer::gl::OpenGlTexture2DReadback {
            .texture_id = 1U,
            .width = 1,
            .height = 1,
            .format = pp::renderer::gl::rgba8_readback_format(),
            .pixels = pixels.data(),
        },
        pp::renderer::gl::OpenGlTexture2DReadbackDispatch {
            .bind_texture = record_bind_texture,
            .gen_framebuffers = record_gen_framebuffers,
        });

    PP_EXPECT(h, !result.ok());
    PP_EXPECT(h, result.status().code == pp::foundation::StatusCode::invalid_argument);
}

void blits_framebuffers_through_dispatch(pp::tests::Harness& h)
{
    recorded_integer_queries.clear();
    recorded_binding_calls.clear();
    recorded_blit_framebuffer_calls.clear();

    const auto status = pp::renderer::gl::blit_opengl_framebuffer(
        pp::renderer::gl::OpenGlFramebufferBlit {
            .source_framebuffer = 13U,
            .destination_framebuffer = 17U,
            .source_rect = pp::renderer::gl::OpenGlFramebufferRect {
                .x0 = 1,
                .y0 = 2,
                .x1 = 65,
                .y1 = 34,
            },
            .destination_rect = pp::renderer::gl::OpenGlFramebufferRect {
                .x0 = 3,
                .y0 = 4,
                .x1 = 131,
                .y1 = 68,
            },
            .mask = 0x00004000U,
            .filter = 0x2601U,
        },
        pp::renderer::gl::OpenGlFramebufferBlitDispatch {
            .get_integer = record_get_integer,
            .bind_framebuffer = record_bind_framebuffer,
            .blit_framebuffer = record_blit_framebuffer,
        });

    PP_EXPECT(h, status.ok());
    PP_EXPECT(h, recorded_integer_queries.size() == 2U);
    PP_EXPECT(h, recorded_integer_queries[0] == 0x8CA6U);
    PP_EXPECT(h, recorded_integer_queries[1] == 0x8CAAU);
    PP_EXPECT(h, recorded_binding_calls.size() == 4U);
    PP_EXPECT(h, recorded_binding_calls[0].kind == RecordedOpenGlBindingCall::Kind::bind_framebuffer);
    PP_EXPECT(h, recorded_binding_calls[0].first == 0x8CA9U);
    PP_EXPECT(h, recorded_binding_calls[0].second == 17U);
    PP_EXPECT(h, recorded_binding_calls[1].first == 0x8CA8U);
    PP_EXPECT(h, recorded_binding_calls[1].second == 13U);
    PP_EXPECT(h, recorded_binding_calls[2].first == 0x8CA9U);
    PP_EXPECT(h, recorded_binding_calls[2].second == 7U);
    PP_EXPECT(h, recorded_binding_calls[3].first == 0x8CA8U);
    PP_EXPECT(h, recorded_binding_calls[3].second == 9U);
    PP_EXPECT(h, recorded_blit_framebuffer_calls.size() == 1U);
    PP_EXPECT(h, recorded_blit_framebuffer_calls[0].source_x0 == 1);
    PP_EXPECT(h, recorded_blit_framebuffer_calls[0].source_y1 == 34);
    PP_EXPECT(h, recorded_blit_framebuffer_calls[0].destination_x1 == 131);
    PP_EXPECT(h, recorded_blit_framebuffer_calls[0].mask == 0x00004000U);
    PP_EXPECT(h, recorded_blit_framebuffer_calls[0].filter == 0x2601U);
}

void rejects_invalid_framebuffer_blits(pp::tests::Harness& h)
{
    const auto missing_dispatch = pp::renderer::gl::blit_opengl_framebuffer(
        pp::renderer::gl::OpenGlFramebufferBlit {
            .source_rect = pp::renderer::gl::OpenGlFramebufferRect { .x1 = 1, .y1 = 1 },
            .destination_rect = pp::renderer::gl::OpenGlFramebufferRect { .x1 = 1, .y1 = 1 },
            .mask = 0x00004000U,
            .filter = 0x2601U,
        },
        pp::renderer::gl::OpenGlFramebufferBlitDispatch {
            .get_integer = record_get_integer,
            .bind_framebuffer = record_bind_framebuffer,
        });
    const auto empty_source = pp::renderer::gl::blit_opengl_framebuffer(
        pp::renderer::gl::OpenGlFramebufferBlit {
            .source_rect = pp::renderer::gl::OpenGlFramebufferRect { .x1 = 0, .y1 = 1 },
            .destination_rect = pp::renderer::gl::OpenGlFramebufferRect { .x1 = 1, .y1 = 1 },
            .mask = 0x00004000U,
            .filter = 0x2601U,
        },
        pp::renderer::gl::OpenGlFramebufferBlitDispatch {
            .get_integer = record_get_integer,
            .bind_framebuffer = record_bind_framebuffer,
            .blit_framebuffer = record_blit_framebuffer,
        });

    PP_EXPECT(h, !missing_dispatch.ok());
    PP_EXPECT(h, missing_dispatch.code == pp::foundation::StatusCode::invalid_argument);
    PP_EXPECT(h, !empty_source.ok());
    PP_EXPECT(h, empty_source.code == pp::foundation::StatusCode::invalid_argument);
}

void reads_back_framebuffer_through_dispatch(pp::tests::Harness& h)
{
    recorded_integer_queries.clear();
    recorded_binding_calls.clear();
    recorded_read_pixels_calls.clear();
    std::array<std::uint8_t, 16> pixels {};

    const auto status = pp::renderer::gl::readback_opengl_framebuffer(
        pp::renderer::gl::OpenGlFramebufferReadback {
            .framebuffer = 23U,
            .x = 2,
            .y = 3,
            .width = 4,
            .height = 5,
            .format = pp::renderer::gl::rgba8_readback_format(),
            .pixels = pixels.data(),
        },
        pp::renderer::gl::OpenGlFramebufferReadbackDispatch {
            .get_integer = record_get_integer,
            .bind_framebuffer = record_bind_framebuffer,
            .read_pixels = record_read_pixels,
        });

    PP_EXPECT(h, status.ok());
    PP_EXPECT(h, recorded_integer_queries.size() == 1U);
    PP_EXPECT(h, recorded_integer_queries[0] == 0x8CAAU);
    PP_EXPECT(h, recorded_binding_calls.size() == 2U);
    PP_EXPECT(h, recorded_binding_calls[0].first == 0x8CA8U);
    PP_EXPECT(h, recorded_binding_calls[0].second == 23U);
    PP_EXPECT(h, recorded_binding_calls[1].first == 0x8CA8U);
    PP_EXPECT(h, recorded_binding_calls[1].second == 9U);
    PP_EXPECT(h, recorded_read_pixels_calls.size() == 1U);
    PP_EXPECT(h, recorded_read_pixels_calls[0].x == 2);
    PP_EXPECT(h, recorded_read_pixels_calls[0].y == 3);
    PP_EXPECT(h, recorded_read_pixels_calls[0].width == 4);
    PP_EXPECT(h, recorded_read_pixels_calls[0].height == 5);
    PP_EXPECT(h, recorded_read_pixels_calls[0].pixels == pixels.data());
}

void rejects_invalid_framebuffer_readbacks(pp::tests::Harness& h)
{
    std::array<std::uint8_t, 4> pixels {};
    const auto missing_dispatch = pp::renderer::gl::readback_opengl_framebuffer(
        pp::renderer::gl::OpenGlFramebufferReadback {
            .width = 1,
            .height = 1,
            .format = pp::renderer::gl::rgba8_readback_format(),
            .pixels = pixels.data(),
        },
        pp::renderer::gl::OpenGlFramebufferReadbackDispatch {
            .get_integer = record_get_integer,
            .bind_framebuffer = record_bind_framebuffer,
        });
    const auto missing_pixels = pp::renderer::gl::readback_opengl_framebuffer(
        pp::renderer::gl::OpenGlFramebufferReadback {
            .width = 1,
            .height = 1,
            .format = pp::renderer::gl::rgba8_readback_format(),
        },
        pp::renderer::gl::OpenGlFramebufferReadbackDispatch {
            .get_integer = record_get_integer,
            .bind_framebuffer = record_bind_framebuffer,
            .read_pixels = record_read_pixels,
        });

    PP_EXPECT(h, !missing_dispatch.ok());
    PP_EXPECT(h, missing_dispatch.code == pp::foundation::StatusCode::invalid_argument);
    PP_EXPECT(h, !missing_pixels.ok());
    PP_EXPECT(h, missing_pixels.code == pp::foundation::StatusCode::invalid_argument);
}

void binds_framebuffer_draw_read_through_dispatch(pp::tests::Harness& h)
{
    recorded_integer_queries.clear();
    recorded_binding_calls.clear();

    const auto binding = pp::renderer::gl::bind_opengl_framebuffer_for_draw_read(
        29U,
        pp::renderer::gl::OpenGlFramebufferBindDispatch {
            .get_integer = record_get_integer,
            .bind_framebuffer = record_bind_framebuffer,
        });

    PP_EXPECT(h, binding.ok());
    PP_EXPECT(h, binding.value().draw_framebuffer == 7);
    PP_EXPECT(h, binding.value().read_framebuffer == 9);
    PP_EXPECT(h, recorded_integer_queries.size() == 2U);
    PP_EXPECT(h, recorded_integer_queries[0] == 0x8CA6U);
    PP_EXPECT(h, recorded_integer_queries[1] == 0x8CAAU);
    PP_EXPECT(h, recorded_binding_calls.size() == 2U);
    PP_EXPECT(h, recorded_binding_calls[0].first == 0x8CA9U);
    PP_EXPECT(h, recorded_binding_calls[0].second == 29U);
    PP_EXPECT(h, recorded_binding_calls[1].first == 0x8CA8U);
    PP_EXPECT(h, recorded_binding_calls[1].second == 29U);
}

void restores_framebuffer_draw_read_through_dispatch(pp::tests::Harness& h)
{
    recorded_binding_calls.clear();

    const auto status = pp::renderer::gl::restore_opengl_framebuffer_binding(
        pp::renderer::gl::OpenGlFramebufferBindingState {
            .draw_framebuffer = 31,
            .read_framebuffer = 37,
        },
        pp::renderer::gl::OpenGlFramebufferRestoreDispatch {
            .bind_framebuffer = record_bind_framebuffer,
        });

    PP_EXPECT(h, status.ok());
    PP_EXPECT(h, recorded_binding_calls.size() == 2U);
    PP_EXPECT(h, recorded_binding_calls[0].first == 0x8CA9U);
    PP_EXPECT(h, recorded_binding_calls[0].second == 31U);
    PP_EXPECT(h, recorded_binding_calls[1].first == 0x8CA8U);
    PP_EXPECT(h, recorded_binding_calls[1].second == 37U);
}

void rejects_incomplete_framebuffer_binding_dispatch(pp::tests::Harness& h)
{
    const auto binding = pp::renderer::gl::bind_opengl_framebuffer_for_draw_read(
        29U,
        pp::renderer::gl::OpenGlFramebufferBindDispatch {
            .get_integer = record_get_integer,
        });
    const auto restore = pp::renderer::gl::restore_opengl_framebuffer_binding(
        pp::renderer::gl::OpenGlFramebufferBindingState {},
        pp::renderer::gl::OpenGlFramebufferRestoreDispatch {});

    PP_EXPECT(h, !binding.ok());
    PP_EXPECT(h, binding.status().code == pp::foundation::StatusCode::invalid_argument);
    PP_EXPECT(h, !restore.ok());
    PP_EXPECT(h, restore.code == pp::foundation::StatusCode::invalid_argument);
}

void allocates_and_deletes_render_target_framebuffer(pp::tests::Harness& h)
{
    recorded_integer_queries.clear();
    recorded_generated_framebuffer_counts.clear();
    recorded_binding_calls.clear();
    recorded_framebuffer_attachment_calls.clear();
    recorded_framebuffer_status_queries.clear();
    recorded_deleted_framebuffers.clear();
    configured_framebuffer_status = 0x8CD5U;
    next_framebuffer_id = 44U;

    const auto framebuffer = pp::renderer::gl::allocate_opengl_render_target_framebuffer(
        27U,
        81U,
        pp::renderer::gl::OpenGlRenderTargetFramebufferAllocationDispatch {
            .gen_framebuffers = record_gen_framebuffers,
            .get_integer = record_get_integer,
            .bind_framebuffer = record_bind_framebuffer,
            .framebuffer_texture_2d = record_framebuffer_texture_2d,
            .framebuffer_renderbuffer = record_framebuffer_renderbuffer,
            .check_framebuffer_status = record_check_framebuffer_status,
        });
    const auto deleted = pp::renderer::gl::delete_opengl_framebuffer(
        framebuffer.ok() ? framebuffer.value().framebuffer_id : 0U,
        pp::renderer::gl::OpenGlFramebufferDeleteDispatch {
            .delete_framebuffers = record_delete_framebuffers,
        });

    PP_EXPECT(h, framebuffer.ok());
    PP_EXPECT(h, framebuffer.value().framebuffer_id == 44U);
    PP_EXPECT(h, framebuffer.value().framebuffer_status == 0x8CD5U);
    PP_EXPECT(h, deleted.ok());
    PP_EXPECT(h, recorded_integer_queries.size() == 1U);
    PP_EXPECT(h, recorded_integer_queries[0] == 0x8CA6U);
    PP_EXPECT(h, recorded_generated_framebuffer_counts.size() == 1U);
    PP_EXPECT(h, recorded_generated_framebuffer_counts[0] == 1U);
    PP_EXPECT(h, recorded_binding_calls.size() == 2U);
    PP_EXPECT(h, recorded_binding_calls[0].kind == RecordedOpenGlBindingCall::Kind::bind_framebuffer);
    PP_EXPECT(h, recorded_binding_calls[0].first == 0x8D40U);
    PP_EXPECT(h, recorded_binding_calls[0].second == 44U);
    PP_EXPECT(h, recorded_binding_calls[1].first == 0x8D40U);
    PP_EXPECT(h, recorded_binding_calls[1].second == 7U);
    PP_EXPECT(h, recorded_framebuffer_attachment_calls.size() == 2U);
    PP_EXPECT(h, recorded_framebuffer_attachment_calls[0].target == 0x8D40U);
    PP_EXPECT(h, recorded_framebuffer_attachment_calls[0].attachment == 0x8CE0U);
    PP_EXPECT(h, recorded_framebuffer_attachment_calls[0].texture_target == 0x0DE1U);
    PP_EXPECT(h, recorded_framebuffer_attachment_calls[0].texture == 27U);
    PP_EXPECT(h, recorded_framebuffer_attachment_calls[1].attachment == 0x8D00U);
    PP_EXPECT(h, recorded_framebuffer_attachment_calls[1].texture_target == 0x8D41U);
    PP_EXPECT(h, recorded_framebuffer_attachment_calls[1].texture == 81U);
    PP_EXPECT(h, recorded_framebuffer_status_queries.size() == 1U);
    PP_EXPECT(h, recorded_framebuffer_status_queries[0] == 0x8D40U);
    PP_EXPECT(h, recorded_deleted_framebuffers.size() == 1U);
    PP_EXPECT(h, recorded_deleted_framebuffers[0] == 44U);
}

void reports_incomplete_render_target_framebuffer(pp::tests::Harness& h)
{
    recorded_framebuffer_attachment_calls.clear();
    configured_framebuffer_status = 0x8CD6U;
    next_framebuffer_id = 45U;

    const auto framebuffer = pp::renderer::gl::allocate_opengl_render_target_framebuffer(
        27U,
        0U,
        pp::renderer::gl::OpenGlRenderTargetFramebufferAllocationDispatch {
            .gen_framebuffers = record_gen_framebuffers,
            .get_integer = record_get_integer,
            .bind_framebuffer = record_bind_framebuffer,
            .framebuffer_texture_2d = record_framebuffer_texture_2d,
            .framebuffer_renderbuffer = record_framebuffer_renderbuffer,
            .check_framebuffer_status = record_check_framebuffer_status,
        });

    PP_EXPECT(h, framebuffer.ok());
    PP_EXPECT(h, framebuffer.value().framebuffer_id == 45U);
    PP_EXPECT(h, framebuffer.value().framebuffer_status == 0x8CD6U);
    PP_EXPECT(h, recorded_framebuffer_attachment_calls.size() == 1U);
    PP_EXPECT(h, recorded_framebuffer_attachment_calls[0].texture == 27U);
    configured_framebuffer_status = 0x8CD5U;
}

void rejects_invalid_render_target_framebuffer_dispatch(pp::tests::Harness& h)
{
    const auto missing_dispatch = pp::renderer::gl::allocate_opengl_render_target_framebuffer(
        27U,
        0U,
        pp::renderer::gl::OpenGlRenderTargetFramebufferAllocationDispatch {
            .gen_framebuffers = record_gen_framebuffers,
            .get_integer = record_get_integer,
            .bind_framebuffer = record_bind_framebuffer,
        });
    const auto invalid_texture = pp::renderer::gl::allocate_opengl_render_target_framebuffer(
        0U,
        0U,
        pp::renderer::gl::OpenGlRenderTargetFramebufferAllocationDispatch {
            .gen_framebuffers = record_gen_framebuffers,
            .get_integer = record_get_integer,
            .bind_framebuffer = record_bind_framebuffer,
            .framebuffer_texture_2d = record_framebuffer_texture_2d,
            .framebuffer_renderbuffer = record_framebuffer_renderbuffer,
            .check_framebuffer_status = record_check_framebuffer_status,
        });
    const auto missing_delete_dispatch = pp::renderer::gl::delete_opengl_framebuffer(
        44U,
        pp::renderer::gl::OpenGlFramebufferDeleteDispatch {});

    PP_EXPECT(h, !missing_dispatch.ok());
    PP_EXPECT(h, missing_dispatch.status().code == pp::foundation::StatusCode::invalid_argument);
    PP_EXPECT(h, !invalid_texture.ok());
    PP_EXPECT(h, invalid_texture.status().code == pp::foundation::StatusCode::invalid_argument);
    PP_EXPECT(h, !missing_delete_dispatch.ok());
    PP_EXPECT(h, missing_delete_dispatch.code == pp::foundation::StatusCode::invalid_argument);
}

void allocates_attaches_and_deletes_depth_renderbuffer(pp::tests::Harness& h)
{
    recorded_generated_renderbuffer_counts.clear();
    recorded_bound_renderbuffers.clear();
    recorded_renderbuffer_storage_calls.clear();
    recorded_framebuffer_attachment_calls.clear();
    recorded_deleted_renderbuffers.clear();
    next_renderbuffer_id = 81U;

    const auto renderbuffer = pp::renderer::gl::allocate_opengl_depth_renderbuffer(
        64,
        32,
        pp::renderer::gl::OpenGlDepthRenderbufferAllocationDispatch {
            .gen_renderbuffers = record_gen_renderbuffers,
            .bind_renderbuffer = record_bind_renderbuffer,
            .renderbuffer_storage = record_renderbuffer_storage,
        });
    const auto attach = pp::renderer::gl::attach_opengl_depth_renderbuffer(
        renderbuffer.ok() ? renderbuffer.value() : 0U,
        pp::renderer::gl::OpenGlDepthRenderbufferAttachmentDispatch {
            .framebuffer_renderbuffer = record_framebuffer_renderbuffer,
        });
    const auto detach = pp::renderer::gl::attach_opengl_depth_renderbuffer(
        0U,
        pp::renderer::gl::OpenGlDepthRenderbufferAttachmentDispatch {
            .framebuffer_renderbuffer = record_framebuffer_renderbuffer,
        });
    const auto deleted = pp::renderer::gl::delete_opengl_renderbuffer(
        renderbuffer.ok() ? renderbuffer.value() : 0U,
        pp::renderer::gl::OpenGlRenderbufferDeleteDispatch {
            .delete_renderbuffers = record_delete_renderbuffers,
        });

    PP_EXPECT(h, renderbuffer.ok());
    PP_EXPECT(h, renderbuffer.value() == 81U);
    PP_EXPECT(h, recorded_generated_renderbuffer_counts.size() == 1U);
    PP_EXPECT(h, recorded_generated_renderbuffer_counts[0] == 1U);
    PP_EXPECT(h, recorded_bound_renderbuffers.size() == 4U);
    PP_EXPECT(h, recorded_bound_renderbuffers[0] == 0x8D41U);
    PP_EXPECT(h, recorded_bound_renderbuffers[1] == 81U);
    PP_EXPECT(h, recorded_bound_renderbuffers[2] == 0x8D41U);
    PP_EXPECT(h, recorded_bound_renderbuffers[3] == 0U);
    PP_EXPECT(h, recorded_renderbuffer_storage_calls.size() == 1U);
    PP_EXPECT(h, recorded_renderbuffer_storage_calls[0].target == 0x8D41U);
    PP_EXPECT(h, recorded_renderbuffer_storage_calls[0].internal_format == 0x81A6U);
    PP_EXPECT(h, recorded_renderbuffer_storage_calls[0].width == 64);
    PP_EXPECT(h, recorded_renderbuffer_storage_calls[0].height == 32);
    PP_EXPECT(h, attach.ok());
    PP_EXPECT(h, detach.ok());
    PP_EXPECT(h, recorded_framebuffer_attachment_calls.size() == 2U);
    PP_EXPECT(h, recorded_framebuffer_attachment_calls[0].target == 0x8D40U);
    PP_EXPECT(h, recorded_framebuffer_attachment_calls[0].attachment == 0x8D00U);
    PP_EXPECT(h, recorded_framebuffer_attachment_calls[0].texture_target == 0x8D41U);
    PP_EXPECT(h, recorded_framebuffer_attachment_calls[0].texture == 81U);
    PP_EXPECT(h, recorded_framebuffer_attachment_calls[1].texture == 0U);
    PP_EXPECT(h, deleted.ok());
    PP_EXPECT(h, recorded_deleted_renderbuffers.size() == 1U);
    PP_EXPECT(h, recorded_deleted_renderbuffers[0] == 81U);
}

void rejects_invalid_depth_renderbuffer_dispatch(pp::tests::Harness& h)
{
    const auto missing_allocate_dispatch = pp::renderer::gl::allocate_opengl_depth_renderbuffer(
        64,
        32,
        pp::renderer::gl::OpenGlDepthRenderbufferAllocationDispatch {
            .gen_renderbuffers = record_gen_renderbuffers,
        });
    const auto invalid_dimensions = pp::renderer::gl::allocate_opengl_depth_renderbuffer(
        0,
        32,
        pp::renderer::gl::OpenGlDepthRenderbufferAllocationDispatch {
            .gen_renderbuffers = record_gen_renderbuffers,
            .bind_renderbuffer = record_bind_renderbuffer,
            .renderbuffer_storage = record_renderbuffer_storage,
        });
    const auto missing_attach_dispatch = pp::renderer::gl::attach_opengl_depth_renderbuffer(
        81U,
        pp::renderer::gl::OpenGlDepthRenderbufferAttachmentDispatch {});
    const auto missing_delete_dispatch = pp::renderer::gl::delete_opengl_renderbuffer(
        81U,
        pp::renderer::gl::OpenGlRenderbufferDeleteDispatch {});

    PP_EXPECT(h, !missing_allocate_dispatch.ok());
    PP_EXPECT(h, missing_allocate_dispatch.status().code == pp::foundation::StatusCode::invalid_argument);
    PP_EXPECT(h, !invalid_dimensions.ok());
    PP_EXPECT(h, invalid_dimensions.status().code == pp::foundation::StatusCode::invalid_argument);
    PP_EXPECT(h, !missing_attach_dispatch.ok());
    PP_EXPECT(h, missing_attach_dispatch.code == pp::foundation::StatusCode::invalid_argument);
    PP_EXPECT(h, !missing_delete_dispatch.ok());
    PP_EXPECT(h, missing_delete_dispatch.code == pp::foundation::StatusCode::invalid_argument);
}

void maps_renderer_viewports_and_scissors(pp::tests::Harness& h)
{
    const auto viewport = pp::renderer::gl::viewport_for_renderer_viewport(
        pp::renderer::Viewport {
            .x = 4,
            .y = 8,
            .width = 640U,
            .height = 320U,
            .min_depth = 0.25F,
            .max_depth = 0.75F,
        });
    const auto disabled_scissor = pp::renderer::gl::scissor_rect_for_renderer_scissor(
        pp::renderer::ScissorRect {});
    const auto enabled_scissor = pp::renderer::gl::scissor_rect_for_renderer_scissor(
        pp::renderer::ScissorRect {
            .enabled = true,
            .x = 12,
            .y = 16,
            .width = 128U,
            .height = 64U,
        });

    PP_EXPECT(h, viewport.x == 4);
    PP_EXPECT(h, viewport.y == 8);
    PP_EXPECT(h, viewport.width == 640);
    PP_EXPECT(h, viewport.height == 320);
    PP_EXPECT(h, viewport.min_depth == 0.25F);
    PP_EXPECT(h, viewport.max_depth == 0.75F);
    PP_EXPECT(h, disabled_scissor.enabled == 0U);
    PP_EXPECT(h, disabled_scissor.width == 0);
    PP_EXPECT(h, disabled_scissor.height == 0);
    PP_EXPECT(h, enabled_scissor.enabled == 1U);
    PP_EXPECT(h, enabled_scissor.x == 12);
    PP_EXPECT(h, enabled_scissor.y == 16);
    PP_EXPECT(h, enabled_scissor.width == 128);
    PP_EXPECT(h, enabled_scissor.height == 64);
}

void maps_renderer_blend_state_tokens(pp::tests::Harness& h)
{
    const auto zero = pp::renderer::gl::blend_factor_for_renderer_factor(pp::renderer::BlendFactor::zero);
    const auto one = pp::renderer::gl::blend_factor_for_renderer_factor(pp::renderer::BlendFactor::one);
    const auto source_alpha = pp::renderer::gl::blend_factor_for_renderer_factor(
        pp::renderer::BlendFactor::source_alpha);
    const auto one_minus_source_alpha = pp::renderer::gl::blend_factor_for_renderer_factor(
        pp::renderer::BlendFactor::one_minus_source_alpha);
    const auto destination_alpha = pp::renderer::gl::blend_factor_for_renderer_factor(
        pp::renderer::BlendFactor::destination_alpha);
    const auto one_minus_destination_alpha = pp::renderer::gl::blend_factor_for_renderer_factor(
        pp::renderer::BlendFactor::one_minus_destination_alpha);
    const auto invalid_factor = pp::renderer::gl::blend_factor_for_renderer_factor(
        static_cast<pp::renderer::BlendFactor>(255U));

    const auto add = pp::renderer::gl::blend_equation_for_renderer_op(pp::renderer::BlendOp::add);
    const auto subtract = pp::renderer::gl::blend_equation_for_renderer_op(pp::renderer::BlendOp::subtract);
    const auto reverse_subtract = pp::renderer::gl::blend_equation_for_renderer_op(
        pp::renderer::BlendOp::reverse_subtract);
    const auto invalid_op = pp::renderer::gl::blend_equation_for_renderer_op(
        static_cast<pp::renderer::BlendOp>(255U));

    PP_EXPECT(h, zero.supported);
    PP_EXPECT(h, zero.value == 0U);
    PP_EXPECT(h, one.supported);
    PP_EXPECT(h, one.value == 1U);
    PP_EXPECT(h, source_alpha.supported);
    PP_EXPECT(h, source_alpha.value == 0x0302U);
    PP_EXPECT(h, one_minus_source_alpha.supported);
    PP_EXPECT(h, one_minus_source_alpha.value == 0x0303U);
    PP_EXPECT(h, destination_alpha.supported);
    PP_EXPECT(h, destination_alpha.value == 0x0304U);
    PP_EXPECT(h, one_minus_destination_alpha.supported);
    PP_EXPECT(h, one_minus_destination_alpha.value == 0x0305U);
    PP_EXPECT(h, !invalid_factor.supported);
    PP_EXPECT(h, invalid_factor.value == 0U);

    PP_EXPECT(h, add.supported);
    PP_EXPECT(h, add.value == 0x8006U);
    PP_EXPECT(h, subtract.supported);
    PP_EXPECT(h, subtract.value == 0x800AU);
    PP_EXPECT(h, reverse_subtract.supported);
    PP_EXPECT(h, reverse_subtract.value == 0x800BU);
    PP_EXPECT(h, !invalid_op.supported);
    PP_EXPECT(h, invalid_op.value == 0U);
}

void maps_renderer_color_write_masks(pp::tests::Harness& h)
{
    const auto default_mask = pp::renderer::gl::color_write_mask_for_renderer_blend_state(
        pp::renderer::BlendState {});
    const auto mixed_mask = pp::renderer::gl::color_write_mask_for_renderer_blend_state(
        pp::renderer::BlendState { .write_r = true, .write_g = false, .write_b = true, .write_a = false });
    const auto disabled_mask = pp::renderer::gl::color_write_mask_for_renderer_blend_state(
        pp::renderer::BlendState { .write_r = false, .write_g = false, .write_b = false, .write_a = false });

    PP_EXPECT(h, default_mask.r == 1U);
    PP_EXPECT(h, default_mask.g == 1U);
    PP_EXPECT(h, default_mask.b == 1U);
    PP_EXPECT(h, default_mask.a == 1U);
    PP_EXPECT(h, mixed_mask.r == 1U);
    PP_EXPECT(h, mixed_mask.g == 0U);
    PP_EXPECT(h, mixed_mask.b == 1U);
    PP_EXPECT(h, mixed_mask.a == 0U);
    PP_EXPECT(h, disabled_mask.r == 0U);
    PP_EXPECT(h, disabled_mask.g == 0U);
    PP_EXPECT(h, disabled_mask.b == 0U);
    PP_EXPECT(h, disabled_mask.a == 0U);
}

void maps_renderer_blend_states(pp::tests::Harness& h)
{
    const auto disabled = pp::renderer::gl::blend_state_for_renderer_blend_state(
        pp::renderer::BlendState {});
    const auto alpha_blend = pp::renderer::gl::blend_state_for_renderer_blend_state(
        pp::renderer::BlendState {
            .enabled = true,
            .source_color = pp::renderer::BlendFactor::source_alpha,
            .destination_color = pp::renderer::BlendFactor::one_minus_source_alpha,
            .color_op = pp::renderer::BlendOp::add,
            .source_alpha = pp::renderer::BlendFactor::one,
            .destination_alpha = pp::renderer::BlendFactor::one_minus_destination_alpha,
            .alpha_op = pp::renderer::BlendOp::reverse_subtract,
            .write_r = true,
            .write_g = false,
            .write_b = true,
            .write_a = false,
        });
    const auto invalid_factor = pp::renderer::gl::blend_state_for_renderer_blend_state(
        pp::renderer::BlendState {
            .enabled = true,
            .source_color = static_cast<pp::renderer::BlendFactor>(255U),
        });
    const auto invalid_op = pp::renderer::gl::blend_state_for_renderer_blend_state(
        pp::renderer::BlendState {
            .enabled = true,
            .color_op = static_cast<pp::renderer::BlendOp>(255U),
        });

    PP_EXPECT(h, disabled.supported);
    PP_EXPECT(h, disabled.enabled == 0U);
    PP_EXPECT(h, disabled.source_color_factor == 1U);
    PP_EXPECT(h, disabled.destination_color_factor == 0U);
    PP_EXPECT(h, disabled.color_equation == 0x8006U);
    PP_EXPECT(h, disabled.source_alpha_factor == 1U);
    PP_EXPECT(h, disabled.destination_alpha_factor == 0U);
    PP_EXPECT(h, disabled.alpha_equation == 0x8006U);
    PP_EXPECT(h, disabled.color_write_mask.r == 1U);
    PP_EXPECT(h, disabled.color_write_mask.g == 1U);
    PP_EXPECT(h, disabled.color_write_mask.b == 1U);
    PP_EXPECT(h, disabled.color_write_mask.a == 1U);

    PP_EXPECT(h, alpha_blend.supported);
    PP_EXPECT(h, alpha_blend.enabled == 1U);
    PP_EXPECT(h, alpha_blend.source_color_factor == 0x0302U);
    PP_EXPECT(h, alpha_blend.destination_color_factor == 0x0303U);
    PP_EXPECT(h, alpha_blend.color_equation == 0x8006U);
    PP_EXPECT(h, alpha_blend.source_alpha_factor == 1U);
    PP_EXPECT(h, alpha_blend.destination_alpha_factor == 0x0305U);
    PP_EXPECT(h, alpha_blend.alpha_equation == 0x800BU);
    PP_EXPECT(h, alpha_blend.color_write_mask.r == 1U);
    PP_EXPECT(h, alpha_blend.color_write_mask.g == 0U);
    PP_EXPECT(h, alpha_blend.color_write_mask.b == 1U);
    PP_EXPECT(h, alpha_blend.color_write_mask.a == 0U);

    PP_EXPECT(h, !invalid_factor.supported);
    PP_EXPECT(h, invalid_factor.enabled == 1U);
    PP_EXPECT(h, invalid_factor.source_color_factor == 0U);
    PP_EXPECT(h, !invalid_op.supported);
    PP_EXPECT(h, invalid_op.color_equation == 0U);
}

void maps_renderer_depth_compare_tokens(pp::tests::Harness& h)
{
    const auto never = pp::renderer::gl::compare_function_for_renderer_compare_op(
        pp::renderer::CompareOp::never);
    const auto less = pp::renderer::gl::compare_function_for_renderer_compare_op(
        pp::renderer::CompareOp::less);
    const auto equal = pp::renderer::gl::compare_function_for_renderer_compare_op(
        pp::renderer::CompareOp::equal);
    const auto less_or_equal = pp::renderer::gl::compare_function_for_renderer_compare_op(
        pp::renderer::CompareOp::less_or_equal);
    const auto greater = pp::renderer::gl::compare_function_for_renderer_compare_op(
        pp::renderer::CompareOp::greater);
    const auto not_equal = pp::renderer::gl::compare_function_for_renderer_compare_op(
        pp::renderer::CompareOp::not_equal);
    const auto greater_or_equal = pp::renderer::gl::compare_function_for_renderer_compare_op(
        pp::renderer::CompareOp::greater_or_equal);
    const auto always = pp::renderer::gl::compare_function_for_renderer_compare_op(
        pp::renderer::CompareOp::always);
    const auto invalid = pp::renderer::gl::compare_function_for_renderer_compare_op(
        static_cast<pp::renderer::CompareOp>(255U));

    PP_EXPECT(h, never.supported);
    PP_EXPECT(h, never.value == 0x0200U);
    PP_EXPECT(h, less.supported);
    PP_EXPECT(h, less.value == 0x0201U);
    PP_EXPECT(h, equal.supported);
    PP_EXPECT(h, equal.value == 0x0202U);
    PP_EXPECT(h, less_or_equal.supported);
    PP_EXPECT(h, less_or_equal.value == 0x0203U);
    PP_EXPECT(h, greater.supported);
    PP_EXPECT(h, greater.value == 0x0204U);
    PP_EXPECT(h, not_equal.supported);
    PP_EXPECT(h, not_equal.value == 0x0205U);
    PP_EXPECT(h, greater_or_equal.supported);
    PP_EXPECT(h, greater_or_equal.value == 0x0206U);
    PP_EXPECT(h, always.supported);
    PP_EXPECT(h, always.value == 0x0207U);
    PP_EXPECT(h, !invalid.supported);
    PP_EXPECT(h, invalid.value == 0U);
}

void maps_renderer_depth_states(pp::tests::Harness& h)
{
    const auto disabled = pp::renderer::gl::depth_state_for_renderer_depth_state(
        pp::renderer::DepthState {});
    const auto read_write = pp::renderer::gl::depth_state_for_renderer_depth_state(
        pp::renderer::DepthState {
            .test_enabled = true,
            .write_enabled = true,
            .compare = pp::renderer::CompareOp::greater,
        });
    const auto invalid_compare = pp::renderer::gl::depth_state_for_renderer_depth_state(
        pp::renderer::DepthState {
            .test_enabled = true,
            .write_enabled = false,
            .compare = static_cast<pp::renderer::CompareOp>(255U),
        });

    PP_EXPECT(h, disabled.supported);
    PP_EXPECT(h, disabled.test_enabled == 0U);
    PP_EXPECT(h, disabled.write_enabled == 0U);
    PP_EXPECT(h, disabled.compare_function == 0x0203U);
    PP_EXPECT(h, read_write.supported);
    PP_EXPECT(h, read_write.test_enabled == 1U);
    PP_EXPECT(h, read_write.write_enabled == 1U);
    PP_EXPECT(h, read_write.compare_function == 0x0204U);
    PP_EXPECT(h, !invalid_compare.supported);
    PP_EXPECT(h, invalid_compare.test_enabled == 1U);
    PP_EXPECT(h, invalid_compare.write_enabled == 0U);
    PP_EXPECT(h, invalid_compare.compare_function == 0U);
}

void maps_windows_wgl_core_context_parameters(pp::tests::Harness& h)
{
    const auto config = pp::renderer::gl::windows_wgl_core_context_3_3_config();

    PP_EXPECT(h, config.context_attributes.size() == 9U);
    PP_EXPECT(h, config.context_attributes[0] == 0x2091);
    PP_EXPECT(h, config.context_attributes[1] == 3);
    PP_EXPECT(h, config.context_attributes[2] == 0x2092);
    PP_EXPECT(h, config.context_attributes[3] == 3);
    PP_EXPECT(h, config.context_attributes[4] == 0x2094);
    PP_EXPECT(h, config.context_attributes[5] == 0x0002);
    PP_EXPECT(h, config.context_attributes[6] == 0x9126);
    PP_EXPECT(h, config.context_attributes[7] == 0x00000001);
    PP_EXPECT(h, config.context_attributes[8] == 0);

    PP_EXPECT(h, config.pixel_format_attributes.size() == 15U);
    PP_EXPECT(h, config.pixel_format_attributes[0] == 0x2001);
    PP_EXPECT(h, config.pixel_format_attributes[1] == 1);
    PP_EXPECT(h, config.pixel_format_attributes[2] == 0x2010);
    PP_EXPECT(h, config.pixel_format_attributes[3] == 1);
    PP_EXPECT(h, config.pixel_format_attributes[4] == 0x2011);
    PP_EXPECT(h, config.pixel_format_attributes[5] == 1);
    PP_EXPECT(h, config.pixel_format_attributes[6] == 0x2003);
    PP_EXPECT(h, config.pixel_format_attributes[7] == 0x2027);
    PP_EXPECT(h, config.pixel_format_attributes[8] == 0x2013);
    PP_EXPECT(h, config.pixel_format_attributes[9] == 0x202B);
    PP_EXPECT(h, config.pixel_format_attributes[10] == 0x2014);
    PP_EXPECT(h, config.pixel_format_attributes[11] == 24);
    PP_EXPECT(h, config.pixel_format_attributes[12] == 0x2022);
    PP_EXPECT(h, config.pixel_format_attributes[13] == 16);
    PP_EXPECT(h, config.pixel_format_attributes[14] == 0);
}

void rejects_invalid_shader_attribute_binding_catalogs(pp::tests::Harness& h)
{
    const std::array<pp::renderer::gl::OpenGlAttributeBinding, 0> empty {};
    const std::array unnamed {
        pp::renderer::gl::OpenGlAttributeBinding { .name = "", .location = 0 },
    };
    const std::array null_named {
        pp::renderer::gl::OpenGlAttributeBinding { .name = nullptr, .location = 0 },
    };
    const std::array duplicate_name {
        pp::renderer::gl::OpenGlAttributeBinding { .name = "pos", .location = 0 },
        pp::renderer::gl::OpenGlAttributeBinding { .name = "pos", .location = 1 },
    };
    const std::array duplicate_location {
        pp::renderer::gl::OpenGlAttributeBinding { .name = "col", .location = 3 },
        pp::renderer::gl::OpenGlAttributeBinding { .name = "nor", .location = 3 },
    };

    PP_EXPECT(h, !pp::renderer::gl::validate_shader_attribute_bindings(empty).ok());
    PP_EXPECT(h, !pp::renderer::gl::validate_shader_attribute_bindings(unnamed).ok());
    PP_EXPECT(h, !pp::renderer::gl::validate_shader_attribute_bindings(null_named).ok());
    PP_EXPECT(h, !pp::renderer::gl::validate_shader_attribute_bindings(duplicate_name).ok());
    PP_EXPECT(h, pp::renderer::gl::validate_shader_attribute_bindings(duplicate_location).ok());
}

void exposes_shader_uniform_catalog(pp::tests::Harness& h)
{
    const auto uniforms = pp::renderer::gl::panopainter_shader_uniform_names();

    PP_EXPECT(h, uniforms.size() == 43U);
    PP_EXPECT(h, pp::renderer::gl::validate_shader_uniform_names(uniforms).ok());
    PP_EXPECT(h, std::strcmp(uniforms[0].name, "mvp") == 0);
    PP_EXPECT(h, uniforms[0].id == pp::renderer::gl::shader_uniform_id("mvp"));
    PP_EXPECT(h, uniforms[0].id == 40696U);
    PP_EXPECT(h, std::strcmp(uniforms[1].name, "tex") == 0);
    PP_EXPECT(h, uniforms[1].id == pp::renderer::gl::shader_uniform_id("tex"));
    PP_EXPECT(h, uniforms[1].id == 48854U);
    PP_EXPECT(h, pp::renderer::gl::shader_uniform_id("pattern_contr") == 54920U);
    PP_EXPECT(h, pp::renderer::gl::shader_uniform_id("draw_outline") == 36178U);
}

void rejects_invalid_shader_uniform_catalogs(pp::tests::Harness& h)
{
    const std::array<pp::renderer::gl::OpenGlUniformName, 0> empty {};
    const std::array unnamed {
        pp::renderer::gl::OpenGlUniformName { .name = "", .id = 0 },
    };
    const std::array null_named {
        pp::renderer::gl::OpenGlUniformName { .name = nullptr, .id = 0 },
    };
    const std::array mismatched_hash {
        pp::renderer::gl::OpenGlUniformName { .name = "mvp", .id = 1 },
    };
    const std::array duplicate_name {
        pp::renderer::gl::OpenGlUniformName {
            .name = "mvp",
            .id = pp::renderer::gl::shader_uniform_id("mvp"),
        },
        pp::renderer::gl::OpenGlUniformName {
            .name = "mvp",
            .id = pp::renderer::gl::shader_uniform_id("mvp"),
        },
    };

    PP_EXPECT(h, !pp::renderer::gl::validate_shader_uniform_names(empty).ok());
    PP_EXPECT(h, !pp::renderer::gl::validate_shader_uniform_names(unnamed).ok());
    PP_EXPECT(h, !pp::renderer::gl::validate_shader_uniform_names(null_named).ok());
    PP_EXPECT(h, !pp::renderer::gl::validate_shader_uniform_names(mismatched_hash).ok());
    PP_EXPECT(h, !pp::renderer::gl::validate_shader_uniform_names(duplicate_name).ok());
}

}

int main()
{
    pp::tests::Harness harness;
    harness.run("detects_common_extension_capabilities", detects_common_extension_capabilities);
    harness.run("treats_desktop_gl_float_rendering_as_core", treats_desktop_gl_float_rendering_as_core);
    harness.run("detects_feature_state_from_extension_capabilities", detects_feature_state_from_extension_capabilities);
    harness.run("detects_desktop_feature_state_without_extensions", detects_desktop_feature_state_without_extensions);
    harness.run("detects_gles_texture_float_extensions", detects_gles_texture_float_extensions);
    harness.run("ignores_gles_texture_extensions_for_webgl_runtime", ignores_gles_texture_extensions_for_webgl_runtime);
    harness.run("classifies_current_build_opengl_runtime", classifies_current_build_opengl_runtime);
    harness.run("selects_texture_upload_type_from_internal_format", selects_texture_upload_type_from_internal_format);
    harness.run("maps_image_channel_count_to_texture_format", maps_image_channel_count_to_texture_format);
    harness.run("maps_renderer_texture_formats_to_opengl_tokens", maps_renderer_texture_formats_to_opengl_tokens);
    harness.run("maps_readback_formats", maps_readback_formats);
    harness.run("maps_pixel_buffer_parameters", maps_pixel_buffer_parameters);
    harness.run("names_framebuffer_status_codes", names_framebuffer_status_codes);
    harness.run("maps_framebuffer_render_target_parameters", maps_framebuffer_render_target_parameters);
    harness.run("maps_framebuffer_blit_parameters", maps_framebuffer_blit_parameters);
    harness.run("maps_render_pass_clear_masks", maps_render_pass_clear_masks);
    harness.run("maps_render_pass_clear_values", maps_render_pass_clear_values);
    harness.run("maps_renderer_primitive_topologies_to_draw_modes", maps_renderer_primitive_topologies_to_draw_modes);
    harness.run("maps_shape_index_and_primitive_modes", maps_shape_index_and_primitive_modes);
    harness.run("maps_panopainter_cube_faces_to_texture_targets", maps_panopainter_cube_faces_to_texture_targets);
    harness.run("exposes_default_render_target_texture_parameters", exposes_default_render_target_texture_parameters);
    harness.run("maps_sampler_parameters", maps_sampler_parameters);
    harness.run("maps_renderer_sampler_tokens", maps_renderer_sampler_tokens);
    harness.run("maps_renderer_sampler_states", maps_renderer_sampler_states);
    harness.run("exposes_shader_attribute_binding_catalog", exposes_shader_attribute_binding_catalog);
    harness.run("maps_app_initialization_parameters", maps_app_initialization_parameters);
    harness.run("applies_app_initialization_state", applies_app_initialization_state);
    harness.run("rejects_incomplete_app_initialization_state_dispatch", rejects_incomplete_app_initialization_state_dispatch);
    harness.run("applies_convert_command_state", applies_convert_command_state);
    harness.run("rejects_incomplete_convert_command_state_dispatch", rejects_incomplete_convert_command_state_dispatch);
    harness.run("snapshots_legacy_gl_state", snapshots_legacy_gl_state);
    harness.run("rejects_incomplete_gl_state_snapshot_dispatch", rejects_incomplete_gl_state_snapshot_dispatch);
    harness.run("restores_legacy_gl_state", restores_legacy_gl_state);
    harness.run("rejects_incomplete_gl_state_restore_dispatch", rejects_incomplete_gl_state_restore_dispatch);
    harness.run("queries_app_runtime_info", queries_app_runtime_info);
    harness.run("rejects_incomplete_app_runtime_info_dispatch", rejects_incomplete_app_runtime_info_dispatch);
    harness.run("queries_app_extensions", queries_app_extensions);
    harness.run("converts_null_extension_names_to_empty_strings", converts_null_extension_names_to_empty_strings);
    harness.run("treats_negative_extension_count_as_empty", treats_negative_extension_count_as_empty);
    harness.run("rejects_incomplete_extension_query_dispatch", rejects_incomplete_extension_query_dispatch);
    harness.run("queries_app_capability_detection", queries_app_capability_detection);
    harness.run("query_capability_detection_preserves_webgl_float_policy", query_capability_detection_preserves_webgl_float_policy);
    harness.run("rejects_incomplete_capability_detection_dispatch", rejects_incomplete_capability_detection_dispatch);
    harness.run("clears_app_default_target", clears_app_default_target);
    harness.run("rejects_incomplete_app_clear_dispatch", rejects_incomplete_app_clear_dispatch);
    harness.run("clears_render_target_through_dispatch", clears_render_target_through_dispatch);
    harness.run("clears_color_buffer_with_write_mask_and_restores_previous_mask", clears_color_buffer_with_write_mask_and_restores_previous_mask);
    harness.run("applies_color_write_mask_through_dispatch", applies_color_write_mask_through_dispatch);
    harness.run("rejects_invalid_render_target_clear_dispatch", rejects_invalid_render_target_clear_dispatch);
    harness.run("applies_clear_color_dispatch", applies_clear_color_dispatch);
    harness.run("rejects_incomplete_clear_color_dispatch", rejects_incomplete_clear_color_dispatch);
    harness.run("activates_texture_unit_through_dispatch", activates_texture_unit_through_dispatch);
    harness.run("rejects_incomplete_active_texture_dispatch", rejects_incomplete_active_texture_dispatch);
    harness.run("applies_viewport_dispatch", applies_viewport_dispatch);
    harness.run("rejects_incomplete_viewport_dispatch", rejects_incomplete_viewport_dispatch);
    harness.run("queries_viewport_through_dispatch", queries_viewport_through_dispatch);
    harness.run("rejects_incomplete_viewport_query_dispatch", rejects_incomplete_viewport_query_dispatch);
    harness.run("queries_clear_color_through_dispatch", queries_clear_color_through_dispatch);
    harness.run("rejects_incomplete_clear_color_query_dispatch", rejects_incomplete_clear_color_query_dispatch);
    harness.run("applies_scissor_dispatch", applies_scissor_dispatch);
    harness.run("rejects_incomplete_scissor_dispatch", rejects_incomplete_scissor_dispatch);
    harness.run("applies_scissor_test_dispatch", applies_scissor_test_dispatch);
    harness.run("rejects_incomplete_scissor_test_dispatch", rejects_incomplete_scissor_test_dispatch);
    harness.run("applies_generic_capability_dispatch", applies_generic_capability_dispatch);
    harness.run("rejects_incomplete_generic_capability_dispatch", rejects_incomplete_generic_capability_dispatch);
    harness.run("queries_generic_capability_state", queries_generic_capability_state);
    harness.run("rejects_incomplete_generic_capability_state_query", rejects_incomplete_generic_capability_state_query);
    harness.run("applies_render_platform_hints", applies_render_platform_hints);
    harness.run("rejects_incomplete_render_platform_hint_dispatch", rejects_incomplete_render_platform_hint_dispatch);
    harness.run("applies_debug_output_states", applies_debug_output_states);
    harness.run("rejects_incomplete_debug_output_state_dispatch", rejects_incomplete_debug_output_state_dispatch);
    harness.run("applies_buffer_clear_dispatch", applies_buffer_clear_dispatch);
    harness.run("rejects_incomplete_buffer_clear_dispatch", rejects_incomplete_buffer_clear_dispatch);
    harness.run("allocates_texture_2d_through_dispatch", allocates_texture_2d_through_dispatch);
    harness.run("rejects_invalid_texture_2d_allocation", rejects_invalid_texture_2d_allocation);
    harness.run("deletes_and_binds_texture_2d_through_dispatch", deletes_and_binds_texture_2d_through_dispatch);
    harness.run("deletes_texture_objects_through_dispatch", deletes_texture_objects_through_dispatch);
    harness.run("sets_texture_2d_parameters_through_dispatch", sets_texture_2d_parameters_through_dispatch);
    harness.run("rejects_invalid_texture_2d_parameter_dispatch", rejects_invalid_texture_2d_parameter_dispatch);
    harness.run("allocates_texture_cube_through_dispatch", allocates_texture_cube_through_dispatch);
    harness.run("rejects_invalid_texture_cube_allocation", rejects_invalid_texture_cube_allocation);
    harness.run("binds_texture_cube_through_dispatch", binds_texture_cube_through_dispatch);
    harness.run("creates_sampler_through_dispatch", creates_sampler_through_dispatch);
    harness.run("sets_sampler_parameters_through_dispatch", sets_sampler_parameters_through_dispatch);
    harness.run("sets_sampler_border_color_through_dispatch", sets_sampler_border_color_through_dispatch);
    harness.run("binds_sampler_through_dispatch", binds_sampler_through_dispatch);
    harness.run("rejects_invalid_sampler_dispatch", rejects_invalid_sampler_dispatch);
    harness.run("uses_and_deletes_programs_through_dispatch", uses_and_deletes_programs_through_dispatch);
    harness.run("sets_vector_uniforms_through_dispatch", sets_vector_uniforms_through_dispatch);
    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("creates_indexed_mesh_objects_through_dispatch", creates_indexed_mesh_objects_through_dispatch);
    harness.run("creates_dynamic_mesh_without_initial_vertex_upload", creates_dynamic_mesh_without_initial_vertex_upload);
    harness.run("creates_single_vertex_array_mesh_with_deferred_upload", creates_single_vertex_array_mesh_with_deferred_upload);
    harness.run("updates_draws_and_deletes_mesh_through_dispatch", updates_draws_and_deletes_mesh_through_dispatch);
    harness.run("rejects_invalid_mesh_dispatch", rejects_invalid_mesh_dispatch);
    harness.run("creates_reads_maps_and_deletes_pixel_buffers_through_dispatch", creates_reads_maps_and_deletes_pixel_buffers_through_dispatch);
    harness.run("rejects_invalid_pixel_buffer_dispatch", rejects_invalid_pixel_buffer_dispatch);
    harness.run("updates_texture_2d_through_dispatch", updates_texture_2d_through_dispatch);
    harness.run("rejects_invalid_texture_2d_updates", rejects_invalid_texture_2d_updates);
    harness.run("copies_framebuffer_to_texture_2d_through_dispatch", copies_framebuffer_to_texture_2d_through_dispatch);
    harness.run("copies_framebuffer_to_requested_texture_target", copies_framebuffer_to_requested_texture_target);
    harness.run("skips_zero_sized_framebuffer_to_texture_copies", skips_zero_sized_framebuffer_to_texture_copies);
    harness.run("rejects_invalid_framebuffer_to_texture_copies", rejects_invalid_framebuffer_to_texture_copies);
    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);
    harness.run("reports_incomplete_texture_2d_readback_framebuffer", reports_incomplete_texture_2d_readback_framebuffer);
    harness.run("rejects_incomplete_texture_2d_readback_dispatch", rejects_incomplete_texture_2d_readback_dispatch);
    harness.run("blits_framebuffers_through_dispatch", blits_framebuffers_through_dispatch);
    harness.run("rejects_invalid_framebuffer_blits", rejects_invalid_framebuffer_blits);
    harness.run("reads_back_framebuffer_through_dispatch", reads_back_framebuffer_through_dispatch);
    harness.run("rejects_invalid_framebuffer_readbacks", rejects_invalid_framebuffer_readbacks);
    harness.run("binds_framebuffer_draw_read_through_dispatch", binds_framebuffer_draw_read_through_dispatch);
    harness.run("restores_framebuffer_draw_read_through_dispatch", restores_framebuffer_draw_read_through_dispatch);
    harness.run("rejects_incomplete_framebuffer_binding_dispatch", rejects_incomplete_framebuffer_binding_dispatch);
    harness.run("allocates_and_deletes_render_target_framebuffer", allocates_and_deletes_render_target_framebuffer);
    harness.run("reports_incomplete_render_target_framebuffer", reports_incomplete_render_target_framebuffer);
    harness.run("rejects_invalid_render_target_framebuffer_dispatch", rejects_invalid_render_target_framebuffer_dispatch);
    harness.run("allocates_attaches_and_deletes_depth_renderbuffer", allocates_attaches_and_deletes_depth_renderbuffer);
    harness.run("rejects_invalid_depth_renderbuffer_dispatch", rejects_invalid_depth_renderbuffer_dispatch);
    harness.run("maps_renderer_viewports_and_scissors", maps_renderer_viewports_and_scissors);
    harness.run("maps_renderer_blend_state_tokens", maps_renderer_blend_state_tokens);
    harness.run("maps_renderer_color_write_masks", maps_renderer_color_write_masks);
    harness.run("maps_renderer_blend_states", maps_renderer_blend_states);
    harness.run("maps_renderer_depth_compare_tokens", maps_renderer_depth_compare_tokens);
    harness.run("maps_renderer_depth_states", maps_renderer_depth_states);
    harness.run("maps_windows_wgl_core_context_parameters", maps_windows_wgl_core_context_parameters);
    harness.run("rejects_invalid_shader_attribute_binding_catalogs", rejects_invalid_shader_attribute_binding_catalogs);
    harness.run("exposes_shader_uniform_catalog", exposes_shader_uniform_catalog);
    harness.run("rejects_invalid_shader_uniform_catalogs", rejects_invalid_shader_uniform_catalogs);
    return harness.finish();
}
