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

#include <array>
#include <limits>

namespace pp::renderer::gl {

namespace {

constexpr std::uint32_t gl_unsigned_byte = 0x1401U;
constexpr std::uint32_t gl_unsigned_short = 0x1403U;
constexpr std::uint32_t gl_unsigned_int = 0x1405U;
constexpr std::uint32_t gl_float = 0x1406U;
constexpr std::uint32_t gl_half_float = 0x140BU;
constexpr std::uint32_t gl_no_error = 0U;
constexpr std::uint32_t gl_invalid_enum = 0x0500U;
constexpr std::uint32_t gl_invalid_value = 0x0501U;
constexpr std::uint32_t gl_invalid_operation = 0x0502U;
constexpr std::uint32_t gl_out_of_memory = 0x0505U;
constexpr std::uint32_t gl_invalid_framebuffer_operation = 0x0506U;
constexpr std::uint32_t gl_false = 0U;
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_triangle_strip = 0x0005U;
constexpr std::uint32_t gl_fragment_shader = 0x8B30U;
constexpr std::uint32_t gl_vertex_shader = 0x8B31U;
constexpr std::uint32_t gl_compile_status = 0x8B81U;
constexpr std::uint32_t gl_link_status = 0x8B82U;
constexpr std::uint32_t gl_active_uniforms = 0x8B86U;
constexpr std::uint32_t gl_extensions = 0x1F03U;
constexpr std::uint32_t gl_num_extensions = 0x821DU;
constexpr std::uint32_t gl_version = 0x1F02U;
constexpr std::uint32_t gl_vendor = 0x1F00U;
constexpr std::uint32_t gl_renderer = 0x1F01U;
constexpr std::uint32_t gl_shading_language_version = 0x8B8CU;
constexpr std::uint32_t gl_debug_output_synchronous = 0x8242U;
constexpr std::uint32_t gl_debug_output = 0x92E0U;
constexpr std::uint32_t gl_debug_severity_high = 0x9146U;
constexpr std::uint32_t gl_debug_severity_medium = 0x9147U;
constexpr std::uint32_t gl_debug_severity_low = 0x9148U;
constexpr std::uint32_t gl_debug_severity_notification = 0x826BU;
constexpr std::uint32_t gl_zero = 0U;
constexpr std::uint32_t gl_one = 1U;
constexpr std::uint32_t gl_src_alpha = 0x0302U;
constexpr std::uint32_t gl_one_minus_src_alpha = 0x0303U;
constexpr std::uint32_t gl_dst_alpha = 0x0304U;
constexpr std::uint32_t gl_one_minus_dst_alpha = 0x0305U;
constexpr std::uint32_t gl_line_smooth = 0x0B20U;
constexpr std::uint32_t gl_viewport = 0x0BA2U;
constexpr std::uint32_t gl_blend = 0x0BE2U;
constexpr std::uint32_t gl_color_clear_value = 0x0C22U;
constexpr std::uint32_t gl_scissor_test = 0x0C11U;
constexpr std::uint32_t gl_depth_test = 0x0B71U;
constexpr std::uint32_t gl_never = 0x0200U;
constexpr std::uint32_t gl_less = 0x0201U;
constexpr std::uint32_t gl_equal = 0x0202U;
constexpr std::uint32_t gl_lequal = 0x0203U;
constexpr std::uint32_t gl_greater = 0x0204U;
constexpr std::uint32_t gl_notequal = 0x0205U;
constexpr std::uint32_t gl_gequal = 0x0206U;
constexpr std::uint32_t gl_always = 0x0207U;
constexpr std::uint32_t gl_current_program = 0x8B8DU;
constexpr std::uint32_t gl_active_texture = 0x84E0U;
constexpr std::uint32_t gl_texture_binding_2d = 0x8069U;
constexpr std::uint32_t gl_texture_binding_cube_map = 0x8514U;
constexpr std::uint32_t gl_sampler_binding = 0x8919U;
constexpr std::uint32_t gl_func_add = 0x8006U;
constexpr std::uint32_t gl_max = 0x8008U;
constexpr std::uint32_t gl_func_subtract = 0x800AU;
constexpr std::uint32_t gl_func_reverse_subtract = 0x800BU;
constexpr std::uint32_t gl_program_point_size = 0x8642U;
constexpr std::uint32_t gl_array_buffer = 0x8892U;
constexpr std::uint32_t gl_element_array_buffer = 0x8893U;
constexpr std::uint32_t gl_static_draw = 0x88E4U;
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;
constexpr std::uint32_t gl_rgba32f = 0x8814U;
constexpr std::uint32_t gl_rgba16f = 0x881AU;
constexpr std::uint32_t gl_depth_stencil = 0x84F9U;
constexpr std::uint32_t gl_unsigned_int_24_8 = 0x84FAU;
constexpr std::uint32_t gl_depth24_stencil8 = 0x88F0U;
constexpr std::uint32_t gl_texture_2d = 0x0DE1U;
constexpr std::uint32_t gl_renderbuffer = 0x8D41U;
constexpr std::uint32_t gl_depth_component24 = 0x81A6U;
constexpr std::uint32_t gl_framebuffer = 0x8D40U;
constexpr std::uint32_t gl_read_framebuffer = 0x8CA8U;
constexpr std::uint32_t gl_draw_framebuffer = 0x8CA9U;
constexpr std::uint32_t gl_draw_framebuffer_binding = 0x8CA6U;
constexpr std::uint32_t gl_read_framebuffer_binding = 0x8CAAU;
constexpr std::uint32_t gl_color_attachment0 = 0x8CE0U;
constexpr std::uint32_t gl_depth_attachment = 0x8D00U;
constexpr std::uint32_t gl_framebuffer_complete = 0x8CD5U;
constexpr std::uint32_t gl_framebuffer_incomplete_attachment = 0x8CD6U;
constexpr std::uint32_t gl_framebuffer_incomplete_missing_attachment = 0x8CD7U;
constexpr std::uint32_t gl_framebuffer_incomplete_draw_buffer = 0x8CDBU;
constexpr std::uint32_t gl_framebuffer_incomplete_read_buffer = 0x8CDCU;
constexpr std::uint32_t gl_framebuffer_unsupported = 0x8CDDU;
constexpr std::uint32_t gl_framebuffer_undefined = 0x8219U;
constexpr std::uint32_t gl_framebuffer_incomplete_multisample = 0x8D56U;
constexpr std::uint32_t gl_color_buffer_bit = 0x00004000U;
constexpr std::uint32_t gl_depth_buffer_bit = 0x00000100U;
constexpr std::uint32_t gl_stencil_buffer_bit = 0x00000400U;
constexpr std::uint32_t gl_color_writemask = 0x0C23U;
constexpr std::uint32_t gl_texture_cube_map = 0x8513U;
constexpr std::uint32_t gl_texture_cube_map_positive_x = 0x8515U;
constexpr std::uint32_t gl_texture_cube_map_negative_x = 0x8516U;
constexpr std::uint32_t gl_texture_cube_map_positive_y = 0x8517U;
constexpr std::uint32_t gl_texture_cube_map_negative_y = 0x8518U;
constexpr std::uint32_t gl_texture_cube_map_positive_z = 0x8519U;
constexpr std::uint32_t gl_texture_cube_map_negative_z = 0x851AU;
constexpr std::uint32_t gl_linear = 0x2601U;
constexpr std::uint32_t gl_nearest_mipmap_nearest = 0x2700U;
constexpr std::uint32_t gl_linear_mipmap_nearest = 0x2701U;
constexpr std::uint32_t gl_nearest_mipmap_linear = 0x2702U;
constexpr std::uint32_t gl_linear_mipmap_linear = 0x2703U;
constexpr std::uint32_t gl_nearest = 0x2600U;
constexpr std::uint32_t gl_texture_mag_filter = 0x2800U;
constexpr std::uint32_t gl_texture_min_filter = 0x2801U;
constexpr std::uint32_t gl_texture_wrap_s = 0x2802U;
constexpr std::uint32_t gl_texture_wrap_t = 0x2803U;
constexpr std::uint32_t gl_texture_wrap_r = 0x8072U;
constexpr std::uint32_t gl_texture_border_color = 0x1004U;
constexpr std::uint32_t gl_repeat = 0x2901U;
constexpr std::uint32_t gl_mirrored_repeat = 0x8370U;
constexpr std::uint32_t gl_clamp_to_border = 0x812DU;
constexpr std::uint32_t gl_clamp_to_edge = 0x812FU;
constexpr std::uint32_t gl_texture0 = 0x84C0U;
constexpr std::uint32_t gl_pixel_pack_buffer = 0x88EBU;
constexpr std::uint32_t gl_pixel_unpack_buffer = 0x88ECU;
constexpr std::uint32_t gl_stream_read = 0x88E1U;
constexpr std::uint32_t gl_map_read_bit = 0x0001U;
constexpr std::uint8_t gl_boolean_false = 0U;
constexpr std::uint8_t gl_boolean_true = 1U;
constexpr std::int32_t wgl_draw_to_window_arb = 0x2001;
constexpr std::int32_t wgl_acceleration_arb = 0x2003;
constexpr std::int32_t wgl_support_opengl_arb = 0x2010;
constexpr std::int32_t wgl_double_buffer_arb = 0x2011;
constexpr std::int32_t wgl_pixel_type_arb = 0x2013;
constexpr std::int32_t wgl_color_bits_arb = 0x2014;
constexpr std::int32_t wgl_depth_bits_arb = 0x2022;
constexpr std::int32_t wgl_full_acceleration_arb = 0x2027;
constexpr std::int32_t wgl_type_rgba_arb = 0x202B;
constexpr std::int32_t wgl_context_major_version_arb = 0x2091;
constexpr std::int32_t wgl_context_minor_version_arb = 0x2092;
constexpr std::int32_t wgl_context_flags_arb = 0x2094;
constexpr std::int32_t wgl_context_forward_compatible_bit_arb = 0x0002;
constexpr std::int32_t wgl_context_profile_mask_arb = 0x9126;
constexpr std::int32_t wgl_context_core_profile_bit_arb = 0x00000001;

[[nodiscard]] bool contains(std::string_view text, std::string_view needle) noexcept
{
    return text.find(needle) != std::string_view::npos;
}

}

OpenGlCapabilities detect_opengl_capabilities(
    std::span<const std::string_view> extensions,
    OpenGlRuntime runtime) noexcept
{
    OpenGlCapabilities capabilities;

    if (runtime.desktop_gl) {
        capabilities.float32_textures = true;
        capabilities.float32_linear = true;
        capabilities.float16_textures = true;
    }

    for (const auto extension : extensions) {
        if (contains(extension, "shader_framebuffer_fetch")) {
            capabilities.framebuffer_fetch = true;
        }

        if (contains(extension, "map_buffer_alignment")) {
            capabilities.map_buffer_alignment = true;
        }

        if (runtime.gles && !runtime.web) {
            if (contains(extension, "texture_float") || contains(extension, "color_buffer_float")) {
                capabilities.float32_textures = true;
            }

            if (contains(extension, "texture_float_linear")) {
                capabilities.float32_linear = true;
            }

            if (contains(extension, "texture_half_float") || contains(extension, "color_buffer_half_float")) {
                capabilities.float16_textures = true;
            }
        }
    }

    return capabilities;
}

OpenGlRuntime opengl_runtime_for_current_build() noexcept
{
    OpenGlRuntime runtime;
#if defined(PP_RENDERER_GL_RUNTIME_DESKTOP)
    runtime.desktop_gl = true;
#endif
#if defined(PP_RENDERER_GL_RUNTIME_GLES)
    runtime.gles = true;
#endif
#if defined(PP_RENDERER_GL_RUNTIME_WEB)
    runtime.web = true;
#endif
    return runtime;
}

pp::renderer::RenderDeviceFeatures render_device_features(OpenGlCapabilities capabilities) noexcept
{
    return pp::renderer::RenderDeviceFeatures {
        .framebuffer_fetch = capabilities.framebuffer_fetch,
        .texture_copy = true,
        .render_target_blit = true,
        .frame_capture = true,
        .float16_render_targets = capabilities.float16_textures,
        .float32_render_targets = capabilities.float32_textures,
        .float32_linear_filtering = capabilities.float32_linear,
    };
}

OpenGlFeatureState detect_opengl_feature_state(
    std::span<const std::string_view> extensions,
    OpenGlRuntime runtime) noexcept
{
    const auto capabilities = detect_opengl_capabilities(extensions, runtime);
    return OpenGlFeatureState {
        .capabilities = capabilities,
        .features = render_device_features(capabilities),
    };
}

OpenGlInitialState panopainter_initial_state() noexcept
{
    return OpenGlInitialState {
        .depth_test_enabled = false,
        .depth_test_state = depth_test_state(),
        .source_color_factor = source_alpha_blend_factor(),
        .destination_color_factor = one_minus_source_alpha_blend_factor(),
        .color_equation = add_blend_equation(),
        .alpha_equation = max_blend_equation(),
    };
}

pp::foundation::Status apply_panopainter_initial_state(OpenGlStateDispatch dispatch) noexcept
{
    if (dispatch.enable == nullptr
        || dispatch.disable == nullptr
        || dispatch.blend_func == nullptr
        || dispatch.blend_equation_separate == nullptr)
    {
        return pp::foundation::Status::invalid_argument("OpenGL state dispatch callbacks must not be null");
    }

    const auto state = panopainter_initial_state();
    if (state.depth_test_enabled)
        dispatch.enable(state.depth_test_state);
    else
        dispatch.disable(state.depth_test_state);

    dispatch.blend_func(state.source_color_factor, state.destination_color_factor);
    dispatch.blend_equation_separate(state.color_equation, state.alpha_equation);
    return pp::foundation::Status::success();
}

OpenGlConvertCommandState panopainter_convert_command_state() noexcept
{
    return OpenGlConvertCommandState {
        .depth_test_enabled = false,
        .program_point_size_enabled = true,
        .depth_test_state = depth_test_state(),
        .program_point_size_state = program_point_size_state(),
        .source_color_factor = source_alpha_blend_factor(),
        .destination_color_factor = one_minus_source_alpha_blend_factor(),
        .blend_equation = add_blend_equation(),
    };
}

pp::foundation::Status apply_panopainter_convert_command_state(
    OpenGlConvertCommandStateDispatch dispatch) noexcept
{
    if (dispatch.enable == nullptr
        || dispatch.disable == nullptr
        || dispatch.blend_func == nullptr
        || dispatch.blend_equation == nullptr)
    {
        return pp::foundation::Status::invalid_argument(
            "OpenGL convert command state dispatch callbacks must not be null");
    }

    const auto state = panopainter_convert_command_state();
    if (state.depth_test_enabled)
        dispatch.enable(state.depth_test_state);
    else
        dispatch.disable(state.depth_test_state);

    if (state.program_point_size_enabled)
        dispatch.enable(state.program_point_size_state);
    else
        dispatch.disable(state.program_point_size_state);

    dispatch.blend_func(state.source_color_factor, state.destination_color_factor);
    dispatch.blend_equation(state.blend_equation);
    return pp::foundation::Status::success();
}

pp::foundation::Result<OpenGlSavedState> snapshot_opengl_state(OpenGlStateSnapshotDispatch dispatch) noexcept
{
    if (dispatch.is_enabled == nullptr
        || dispatch.get_integer == nullptr
        || dispatch.get_float == nullptr
        || dispatch.active_texture == nullptr) {
        return pp::foundation::Result<OpenGlSavedState>::failure(
            pp::foundation::Status::invalid_argument("OpenGL state snapshot dispatch callbacks must not be null"));
    }

    OpenGlSavedState state {};
    state.blend_enabled = dispatch.is_enabled(blend_state());
    state.depth_test_enabled = dispatch.is_enabled(depth_test_state());
    state.scissor_test_enabled = dispatch.is_enabled(scissor_test_state());
    dispatch.get_integer(viewport_query(), state.viewport.data());
    dispatch.get_float(color_clear_value_query(), state.clear_color.data());
    dispatch.get_integer(current_program_query(), &state.program);
    dispatch.get_integer(draw_framebuffer_binding_query(), &state.draw_framebuffer);
    dispatch.get_integer(read_framebuffer_binding_query(), &state.read_framebuffer);
    dispatch.get_integer(active_texture_query(), &state.active_texture);
    dispatch.get_integer(texture_binding_cube_map_query(), &state.cube_map_binding);

    for (std::size_t i = 0; i < state.texture_2d_bindings.size(); ++i) {
        dispatch.active_texture(active_texture_unit(static_cast<std::uint32_t>(i)));
        dispatch.get_integer(texture_binding_2d_query(), &state.texture_2d_bindings[i]);
        dispatch.get_integer(sampler_binding_query(), &state.sampler_bindings[i]);
    }

    return pp::foundation::Result<OpenGlSavedState>::success(state);
}

pp::foundation::Status restore_opengl_state(
    const OpenGlSavedState& state,
    OpenGlStateRestoreDispatch dispatch) noexcept
{
    if (dispatch.enable == nullptr
        || dispatch.disable == nullptr
        || dispatch.viewport == nullptr
        || dispatch.clear_color == nullptr
        || dispatch.bind_framebuffer == nullptr
        || dispatch.use_program == nullptr
        || dispatch.active_texture == nullptr
        || dispatch.bind_texture == nullptr
        || dispatch.bind_sampler == nullptr) {
        return pp::foundation::Status::invalid_argument("OpenGL state restore dispatch callbacks must not be null");
    }

    (state.blend_enabled != 0U ? dispatch.enable : dispatch.disable)(blend_state());
    (state.depth_test_enabled != 0U ? dispatch.enable : dispatch.disable)(depth_test_state());
    (state.scissor_test_enabled != 0U ? dispatch.enable : dispatch.disable)(scissor_test_state());
    dispatch.viewport(state.viewport[0], state.viewport[1], state.viewport[2], state.viewport[3]);
    dispatch.clear_color(state.clear_color[0], state.clear_color[1], state.clear_color[2], state.clear_color[3]);
    dispatch.bind_framebuffer(draw_framebuffer_target(), static_cast<std::uint32_t>(state.draw_framebuffer));
    dispatch.bind_framebuffer(read_framebuffer_target(), static_cast<std::uint32_t>(state.read_framebuffer));
    dispatch.use_program(static_cast<std::uint32_t>(state.program));

    for (std::size_t i = 0; i < state.texture_2d_bindings.size(); ++i) {
        dispatch.active_texture(active_texture_unit(static_cast<std::uint32_t>(i)));
        dispatch.bind_texture(texture_2d_target(), static_cast<std::uint32_t>(state.texture_2d_bindings[i]));
        dispatch.bind_sampler(static_cast<std::uint32_t>(i), static_cast<std::uint32_t>(state.sampler_bindings[i]));
    }

    dispatch.active_texture(static_cast<std::uint32_t>(state.active_texture));
    dispatch.bind_texture(texture_cube_map_target(), static_cast<std::uint32_t>(state.cube_map_binding));
    return pp::foundation::Status::success();
}

pp::foundation::Result<OpenGlRuntimeInfo> query_opengl_runtime_info(
    OpenGlRuntimeInfoDispatch dispatch) noexcept
{
    if (dispatch.get_string == nullptr) {
        return pp::foundation::Result<OpenGlRuntimeInfo>::failure(
            pp::foundation::Status::invalid_argument("OpenGL runtime info string callback must not be null"));
    }

    return pp::foundation::Result<OpenGlRuntimeInfo>::success(OpenGlRuntimeInfo {
        .version = dispatch.get_string(version_string_name()),
        .vendor = dispatch.get_string(vendor_string_name()),
        .renderer = dispatch.get_string(renderer_string_name()),
        .shading_language_version = dispatch.get_string(shading_language_version_string_name()),
    });
}

pp::foundation::Result<std::vector<std::string>> query_opengl_extensions(
    OpenGlExtensionQueryDispatch dispatch)
{
    if (dispatch.get_integer == nullptr || dispatch.get_string_indexed == nullptr) {
        return pp::foundation::Result<std::vector<std::string>>::failure(
            pp::foundation::Status::invalid_argument("OpenGL extension query callbacks must not be null"));
    }

    std::int32_t extension_count = 0;
    dispatch.get_integer(extension_count_query(), &extension_count);
    if (extension_count <= 0) {
        return pp::foundation::Result<std::vector<std::string>>::success({});
    }

    std::vector<std::string> extensions;
    extensions.reserve(static_cast<std::size_t>(extension_count));
    for (std::int32_t index = 0; index < extension_count; ++index) {
        const char* extension = dispatch.get_string_indexed(
            extension_string_name(),
            static_cast<std::uint32_t>(index));
        extensions.emplace_back(extension != nullptr ? extension : "");
    }
    return pp::foundation::Result<std::vector<std::string>>::success(std::move(extensions));
}

pp::foundation::Result<OpenGlCapabilityDetectionResult> query_opengl_capability_detection(
    OpenGlExtensionQueryDispatch dispatch,
    OpenGlRuntime runtime)
{
    auto extensions_result = query_opengl_extensions(dispatch);
    if (!extensions_result.ok()) {
        return pp::foundation::Result<OpenGlCapabilityDetectionResult>::failure(extensions_result.status());
    }

    auto extensions = std::move(extensions_result.value());
    std::vector<std::string_view> extension_views;
    extension_views.reserve(extensions.size());
    for (const auto& extension : extensions) {
        extension_views.push_back(extension);
    }

    return pp::foundation::Result<OpenGlCapabilityDetectionResult>::success(OpenGlCapabilityDetectionResult {
        .extensions = std::move(extensions),
        .feature_state = detect_opengl_feature_state(extension_views, runtime),
    });
}

OpenGlDefaultClear panopainter_default_clear() noexcept
{
    return OpenGlDefaultClear {
        .color = { 0.1F, 0.1F, 0.1F, 1.0F },
        .mask = framebuffer_color_buffer_mask(),
    };
}

pp::foundation::Status clear_panopainter_default_target(OpenGlClearDispatch dispatch) noexcept
{
    if (dispatch.clear_color == nullptr || dispatch.clear == nullptr) {
        return pp::foundation::Status::invalid_argument("OpenGL clear dispatch callbacks must not be null");
    }

    const auto clear = panopainter_default_clear();
    dispatch.clear_color(clear.color[0], clear.color[1], clear.color[2], clear.color[3]);
    dispatch.clear(clear.mask);
    return pp::foundation::Status::success();
}

pp::foundation::Status clear_opengl_render_target(
    OpenGlDefaultClear clear,
    OpenGlClearDispatch dispatch) noexcept
{
    if (dispatch.clear_color == nullptr || dispatch.clear == nullptr) {
        return pp::foundation::Status::invalid_argument("OpenGL render-target clear dispatch callbacks must not be null");
    }

    if (clear.mask == 0U) {
        return pp::foundation::Status::invalid_argument("OpenGL render-target clear mask is invalid");
    }

    dispatch.clear_color(clear.color[0], clear.color[1], clear.color[2], clear.color[3]);
    dispatch.clear(clear.mask);
    return pp::foundation::Status::success();
}

pp::foundation::Status apply_opengl_clear_color(
    std::array<float, 4> color,
    OpenGlClearColorDispatch dispatch) noexcept
{
    if (dispatch.clear_color == nullptr) {
        return pp::foundation::Status::invalid_argument("OpenGL clear-color dispatch callback must not be null");
    }

    dispatch.clear_color(color[0], color[1], color[2], color[3]);
    return pp::foundation::Status::success();
}

pp::foundation::Status clear_opengl_color_buffer_with_write_mask(
    OpenGlColorMaskedClear clear,
    OpenGlColorMaskedClearDispatch dispatch) noexcept
{
    if (dispatch.get_boolean == nullptr
        || dispatch.color_mask == nullptr
        || dispatch.clear_color == nullptr
        || dispatch.clear == nullptr) {
        return pp::foundation::Status::invalid_argument(
            "OpenGL color-masked clear dispatch callbacks must not be null");
    }

    std::array<std::uint8_t, 4> old_mask {};
    dispatch.get_boolean(color_write_mask_query(), old_mask.data());

    dispatch.color_mask(clear.mask.r, clear.mask.g, clear.mask.b, clear.mask.a);
    dispatch.clear_color(clear.color[0], clear.color[1], clear.color[2], clear.color[3]);
    dispatch.clear(framebuffer_color_buffer_mask());
    dispatch.color_mask(old_mask[0], old_mask[1], old_mask[2], old_mask[3]);
    return pp::foundation::Status::success();
}

pp::foundation::Status apply_opengl_color_write_mask(
    OpenGlColorWriteMask mask,
    OpenGlColorWriteMaskDispatch dispatch) noexcept
{
    if (dispatch.color_mask == nullptr) {
        return pp::foundation::Status::invalid_argument("OpenGL color-write-mask dispatch callback must not be null");
    }

    dispatch.color_mask(mask.r, mask.g, mask.b, mask.a);
    return pp::foundation::Status::success();
}

pp::foundation::Status activate_opengl_texture_unit(
    std::uint32_t unit_index,
    OpenGlActiveTextureDispatch dispatch) noexcept
{
    if (dispatch.active_texture == nullptr) {
        return pp::foundation::Status::invalid_argument("OpenGL active texture dispatch callback must not be null");
    }

    dispatch.active_texture(active_texture_unit(unit_index));
    return pp::foundation::Status::success();
}

pp::foundation::Status apply_opengl_viewport(
    OpenGlViewportRect viewport,
    OpenGlViewportDispatch dispatch) noexcept
{
    if (dispatch.viewport == nullptr) {
        return pp::foundation::Status::invalid_argument("OpenGL viewport dispatch callback must not be null");
    }

    dispatch.viewport(viewport.x, viewport.y, viewport.width, viewport.height);
    return pp::foundation::Status::success();
}

pp::foundation::Result<OpenGlViewportRect> query_opengl_viewport(
    OpenGlViewportQueryDispatch dispatch) noexcept
{
    if (dispatch.get_integer == nullptr) {
        return pp::foundation::Result<OpenGlViewportRect>::failure(
            pp::foundation::Status::invalid_argument("OpenGL viewport query dispatch callback must not be null"));
    }

    std::array<std::int32_t, 4> viewport {};
    dispatch.get_integer(viewport_query(), viewport.data());
    return pp::foundation::Result<OpenGlViewportRect>::success(OpenGlViewportRect {
        .x = viewport[0],
        .y = viewport[1],
        .width = viewport[2],
        .height = viewport[3],
    });
}

pp::foundation::Result<std::array<float, 4>> query_opengl_clear_color(
    OpenGlClearColorQueryDispatch dispatch) noexcept
{
    if (dispatch.get_float == nullptr) {
        return pp::foundation::Result<std::array<float, 4>>::failure(
            pp::foundation::Status::invalid_argument("OpenGL clear-color query dispatch callback must not be null"));
    }

    std::array<float, 4> color {};
    dispatch.get_float(color_clear_value_query(), color.data());
    return pp::foundation::Result<std::array<float, 4>>::success(color);
}

pp::foundation::Status apply_opengl_scissor_rect(
    OpenGlScissorRect scissor,
    OpenGlScissorDispatch dispatch) noexcept
{
    if (dispatch.enable == nullptr || dispatch.disable == nullptr || dispatch.scissor == nullptr) {
        return pp::foundation::Status::invalid_argument("OpenGL scissor dispatch callbacks must not be null");
    }

    if (scissor.enabled != 0U) {
        dispatch.enable(scissor_test_state());
        dispatch.scissor(scissor.x, scissor.y, scissor.width, scissor.height);
    } else {
        dispatch.disable(scissor_test_state());
    }
    return pp::foundation::Status::success();
}

pp::foundation::Status apply_opengl_scissor_test(
    bool enabled,
    OpenGlScissorTestDispatch dispatch) noexcept
{
    if (dispatch.enable == nullptr || dispatch.disable == nullptr) {
        return pp::foundation::Status::invalid_argument("OpenGL scissor test dispatch callbacks must not be null");
    }

    if (enabled) {
        dispatch.enable(scissor_test_state());
    } else {
        dispatch.disable(scissor_test_state());
    }
    return pp::foundation::Status::success();
}

pp::foundation::Status apply_opengl_capability(
    std::uint32_t state,
    bool enabled,
    OpenGlCapabilityDispatch dispatch) noexcept
{
    if (dispatch.enable == nullptr || dispatch.disable == nullptr) {
        return pp::foundation::Status::invalid_argument("OpenGL capability dispatch callbacks must not be null");
    }

    if (enabled) {
        dispatch.enable(state);
    } else {
        dispatch.disable(state);
    }
    return pp::foundation::Status::success();
}

pp::foundation::Result<bool> query_opengl_capability_state(
    std::uint32_t state,
    OpenGlCapabilityStateQueryDispatch dispatch) noexcept
{
    if (dispatch.is_enabled == nullptr) {
        return pp::foundation::Result<bool>::failure(
            pp::foundation::Status::invalid_argument("OpenGL capability state query callback must not be null"));
    }

    return pp::foundation::Result<bool>::success(dispatch.is_enabled(state) != 0U);
}

pp::foundation::Status apply_opengl_render_platform_hints(
    OpenGlRenderPlatformHintDispatch dispatch) noexcept
{
    if (dispatch.enable == nullptr) {
        return pp::foundation::Status::invalid_argument(
            "OpenGL render platform hint dispatch callback must not be null");
    }

    dispatch.enable(program_point_size_state());
    dispatch.enable(line_smooth_state());
    return pp::foundation::Status::success();
}

pp::foundation::Status apply_opengl_debug_output_states(
    OpenGlDebugOutputStateDispatch dispatch) noexcept
{
    if (dispatch.enable == nullptr) {
        return pp::foundation::Status::invalid_argument(
            "OpenGL debug output state dispatch callback must not be null");
    }

    dispatch.enable(debug_output_state());
    dispatch.enable(debug_output_synchronous_state());
    return pp::foundation::Status::success();
}

pp::foundation::Status clear_opengl_buffers(
    std::uint32_t mask,
    OpenGlBufferClearDispatch dispatch) noexcept
{
    if (dispatch.clear == nullptr) {
        return pp::foundation::Status::invalid_argument("OpenGL buffer clear dispatch callback must not be null");
    }

    dispatch.clear(mask);
    return pp::foundation::Status::success();
}

pp::foundation::Result<std::uint32_t> allocate_opengl_texture_2d(
    OpenGlTexture2DAllocation allocation,
    OpenGlTexture2DAllocationDispatch dispatch) noexcept
{
    if (dispatch.gen_textures == nullptr
        || dispatch.bind_texture == nullptr
        || dispatch.tex_image_2d == nullptr) {
        return pp::foundation::Result<std::uint32_t>::failure(
            pp::foundation::Status::invalid_argument("OpenGL texture allocation dispatch callbacks must not be null"));
    }

    if (allocation.width <= 0
        || allocation.height <= 0
        || allocation.internal_format == 0
        || allocation.pixel_format == 0U
        || allocation.component_type == 0U) {
        return pp::foundation::Result<std::uint32_t>::failure(
            pp::foundation::Status::invalid_argument("OpenGL texture allocation parameters are invalid"));
    }

    std::uint32_t texture_id = 0U;
    dispatch.gen_textures(1U, &texture_id);
    if (texture_id == 0U) {
        return pp::foundation::Result<std::uint32_t>::failure(
            pp::foundation::Status::out_of_range("OpenGL texture allocation returned id 0"));
    }

    dispatch.bind_texture(texture_2d_target(), texture_id);
    dispatch.tex_image_2d(
        texture_2d_target(),
        0,
        allocation.internal_format,
        allocation.width,
        allocation.height,
        0,
        allocation.pixel_format,
        allocation.component_type,
        allocation.data);
    dispatch.bind_texture(texture_2d_target(), default_framebuffer_id());
    return pp::foundation::Result<std::uint32_t>::success(texture_id);
}

pp::foundation::Status delete_opengl_texture_2d(
    std::uint32_t texture_id,
    OpenGlTexture2DDeleteDispatch dispatch) noexcept
{
    if (dispatch.delete_textures == nullptr) {
        return pp::foundation::Status::invalid_argument("OpenGL texture delete dispatch callback must not be null");
    }

    if (texture_id == 0U) {
        return pp::foundation::Status::success();
    }

    dispatch.delete_textures(1U, &texture_id);
    return pp::foundation::Status::success();
}

pp::foundation::Status delete_opengl_texture_objects(
    std::span<const std::uint32_t> texture_ids,
    OpenGlTexture2DDeleteDispatch dispatch) noexcept
{
    if (dispatch.delete_textures == nullptr) {
        return pp::foundation::Status::invalid_argument("OpenGL texture delete dispatch callback must not be null");
    }

    if (texture_ids.empty()) {
        return pp::foundation::Status::success();
    }

    dispatch.delete_textures(static_cast<std::uint32_t>(texture_ids.size()), texture_ids.data());
    return pp::foundation::Status::success();
}

pp::foundation::Result<std::uint32_t> allocate_opengl_texture_cube(
    OpenGlTextureCubeAllocation allocation,
    OpenGlTextureCubeAllocationDispatch dispatch) noexcept
{
    if (dispatch.gen_textures == nullptr
        || dispatch.bind_texture == nullptr
        || dispatch.tex_image_2d == nullptr) {
        return pp::foundation::Result<std::uint32_t>::failure(
            pp::foundation::Status::invalid_argument(
                "OpenGL cube texture allocation dispatch callbacks must not be null"));
    }

    if (allocation.resolution <= 0
        || allocation.internal_format == 0
        || allocation.pixel_format == 0U
        || allocation.component_type == 0U) {
        return pp::foundation::Result<std::uint32_t>::failure(
            pp::foundation::Status::invalid_argument("OpenGL cube texture allocation parameters are invalid"));
    }

    std::uint32_t texture_id = 0U;
    dispatch.gen_textures(1U, &texture_id);
    if (texture_id == 0U) {
        return pp::foundation::Result<std::uint32_t>::failure(
            pp::foundation::Status::out_of_range("OpenGL cube texture allocation returned id 0"));
    }

    dispatch.bind_texture(texture_cube_map_target(), texture_id);
    for (std::uint32_t face_index = 0U; face_index < 6U; ++face_index) {
        dispatch.tex_image_2d(
            cube_map_allocation_face_texture_target(face_index),
            0,
            allocation.internal_format,
            allocation.resolution,
            allocation.resolution,
            0,
            allocation.pixel_format,
            allocation.component_type,
            nullptr);
    }
    return pp::foundation::Result<std::uint32_t>::success(texture_id);
}

pp::foundation::Status bind_opengl_texture_2d(
    std::uint32_t texture_id,
    OpenGlTexture2DBindDispatch dispatch) noexcept
{
    if (dispatch.bind_texture == nullptr) {
        return pp::foundation::Status::invalid_argument("OpenGL texture bind dispatch callback must not be null");
    }

    dispatch.bind_texture(texture_2d_target(), texture_id);
    return pp::foundation::Status::success();
}

pp::foundation::Status bind_opengl_texture_cube(
    std::uint32_t texture_id,
    OpenGlTexture2DBindDispatch dispatch) noexcept
{
    if (dispatch.bind_texture == nullptr) {
        return pp::foundation::Status::invalid_argument("OpenGL cube texture bind dispatch callback must not be null");
    }

    dispatch.bind_texture(texture_cube_map_target(), texture_id);
    return pp::foundation::Status::success();
}

pp::foundation::Status update_opengl_texture_2d(
    OpenGlTexture2DUpdate update,
    OpenGlTexture2DUpdateDispatch dispatch) noexcept
{
    if (dispatch.bind_texture == nullptr || dispatch.tex_sub_image_2d == nullptr) {
        return pp::foundation::Status::invalid_argument("OpenGL texture update dispatch callbacks must not be null");
    }

    if (update.texture_id == 0U
        || update.x < 0
        || update.y < 0
        || update.width <= 0
        || update.height <= 0
        || update.pixel_format == 0U
        || update.component_type == 0U
        || update.data == nullptr) {
        return pp::foundation::Status::invalid_argument("OpenGL texture update parameters are invalid");
    }

    dispatch.bind_texture(texture_2d_target(), update.texture_id);
    dispatch.tex_sub_image_2d(
        texture_2d_target(),
        0,
        update.x,
        update.y,
        update.width,
        update.height,
        update.pixel_format,
        update.component_type,
        update.data);
    return pp::foundation::Status::success();
}

pp::foundation::Status set_opengl_texture_2d_parameters(
    std::uint32_t texture_id,
    std::span<const OpenGlTextureParameter> parameters,
    OpenGlTexture2DParameterDispatch dispatch) noexcept
{
    if (dispatch.bind_texture == nullptr || dispatch.tex_parameter_f == nullptr) {
        return pp::foundation::Status::invalid_argument("OpenGL texture parameter dispatch callbacks must not be null");
    }

    if (texture_id == 0U || parameters.empty()) {
        return pp::foundation::Status::invalid_argument("OpenGL texture parameter request is invalid");
    }

    for (const auto parameter : parameters) {
        if (parameter.name == 0U) {
            return pp::foundation::Status::invalid_argument("OpenGL texture parameter name is invalid");
        }
    }

    dispatch.bind_texture(texture_2d_target(), texture_id);
    for (const auto parameter : parameters) {
        dispatch.tex_parameter_f(texture_2d_target(), parameter.name, static_cast<float>(parameter.value));
    }
    dispatch.bind_texture(texture_2d_target(), default_framebuffer_id());
    return pp::foundation::Status::success();
}

pp::foundation::Status copy_opengl_framebuffer_to_texture_2d(
    OpenGlTexture2DFramebufferCopy copy,
    OpenGlTexture2DFramebufferCopyDispatch dispatch) noexcept
{
    if (dispatch.copy_tex_sub_image_2d == nullptr) {
        return pp::foundation::Status::invalid_argument(
            "OpenGL framebuffer-to-texture copy dispatch callback must not be null");
    }

    if (copy.width < 0 || copy.height < 0) {
        return pp::foundation::Status::invalid_argument(
            "OpenGL framebuffer-to-texture copy dimensions are invalid");
    }

    if (copy.width == 0 || copy.height == 0) {
        return pp::foundation::Status::success();
    }

    const auto texture_target = copy.texture_target == 0U ? texture_2d_target() : copy.texture_target;
    dispatch.copy_tex_sub_image_2d(
        texture_target,
        copy.level,
        copy.destination_x,
        copy.destination_y,
        copy.source_x,
        copy.source_y,
        copy.width,
        copy.height);
    return pp::foundation::Status::success();
}

pp::foundation::Status generate_opengl_texture_2d_mipmaps(
    std::uint32_t texture_id,
    OpenGlTexture2DMipmapDispatch dispatch) noexcept
{
    if (dispatch.bind_texture == nullptr || dispatch.generate_mipmap == nullptr) {
        return pp::foundation::Status::invalid_argument("OpenGL texture mipmap dispatch callbacks must not be null");
    }

    if (texture_id == 0U) {
        return pp::foundation::Status::invalid_argument("OpenGL texture mipmap target is invalid");
    }

    dispatch.bind_texture(texture_2d_target(), texture_id);
    dispatch.generate_mipmap(texture_2d_target());
    dispatch.bind_texture(texture_2d_target(), default_framebuffer_id());
    return pp::foundation::Status::success();
}

pp::foundation::Result<OpenGlTexture2DReadbackResult> readback_opengl_texture_2d(
    OpenGlTexture2DReadback readback,
    OpenGlTexture2DReadbackDispatch dispatch) noexcept
{
    if (dispatch.bind_texture == nullptr
        || dispatch.gen_framebuffers == nullptr
        || dispatch.get_integer == nullptr
        || dispatch.bind_framebuffer == nullptr
        || dispatch.framebuffer_texture_2d == nullptr
        || dispatch.check_framebuffer_status == nullptr
        || dispatch.read_pixels == nullptr
        || dispatch.delete_framebuffers == nullptr) {
        return pp::foundation::Result<OpenGlTexture2DReadbackResult>::failure(
            pp::foundation::Status::invalid_argument("OpenGL texture readback dispatch callbacks must not be null"));
    }

    if (readback.texture_id == 0U
        || readback.width <= 0
        || readback.height <= 0
        || readback.format.pixel_format == 0U
        || readback.format.component_type == 0U
        || readback.format.bytes_per_pixel == 0U
        || readback.pixels == nullptr) {
        return pp::foundation::Result<OpenGlTexture2DReadbackResult>::failure(
            pp::foundation::Status::invalid_argument("OpenGL texture readback parameters are invalid"));
    }

    std::uint32_t framebuffer_id = 0U;
    dispatch.gen_framebuffers(1U, &framebuffer_id);
    if (framebuffer_id == 0U) {
        return pp::foundation::Result<OpenGlTexture2DReadbackResult>::failure(
            pp::foundation::Status::out_of_range("OpenGL framebuffer allocation returned id 0"));
    }

    std::int32_t previous_framebuffer = 0;
    dispatch.get_integer(draw_framebuffer_binding_query(), &previous_framebuffer);
    dispatch.bind_texture(texture_2d_target(), readback.texture_id);
    dispatch.bind_framebuffer(framebuffer_target(), framebuffer_id);
    dispatch.framebuffer_texture_2d(
        framebuffer_target(),
        framebuffer_color_attachment(),
        texture_2d_target(),
        readback.texture_id,
        0);

    const auto framebuffer_status = dispatch.check_framebuffer_status(framebuffer_target());
    OpenGlTexture2DReadbackResult result {
        .framebuffer_status = framebuffer_status,
        .pixels_read = false,
    };
    if (framebuffer_status == framebuffer_complete_status()) {
        dispatch.read_pixels(
            0,
            0,
            readback.width,
            readback.height,
            readback.format.pixel_format,
            readback.format.component_type,
            readback.pixels);
        result.pixels_read = true;
    }

    dispatch.bind_framebuffer(framebuffer_target(), static_cast<std::uint32_t>(previous_framebuffer));
    dispatch.delete_framebuffers(1U, &framebuffer_id);
    return pp::foundation::Result<OpenGlTexture2DReadbackResult>::success(result);
}

pp::foundation::Status blit_opengl_framebuffer(
    OpenGlFramebufferBlit blit,
    OpenGlFramebufferBlitDispatch dispatch) noexcept
{
    if (dispatch.get_integer == nullptr
        || dispatch.bind_framebuffer == nullptr
        || dispatch.blit_framebuffer == nullptr) {
        return pp::foundation::Status::invalid_argument("OpenGL framebuffer blit dispatch callbacks must not be null");
    }

    if (blit.source_rect.x1 <= blit.source_rect.x0
        || blit.source_rect.y1 <= blit.source_rect.y0
        || blit.destination_rect.x1 <= blit.destination_rect.x0
        || blit.destination_rect.y1 <= blit.destination_rect.y0
        || blit.mask == 0U
        || blit.filter == 0U) {
        return pp::foundation::Status::invalid_argument("OpenGL framebuffer blit parameters are invalid");
    }

    std::int32_t previous_draw_framebuffer = 0;
    std::int32_t previous_read_framebuffer = 0;
    dispatch.get_integer(draw_framebuffer_binding_query(), &previous_draw_framebuffer);
    dispatch.get_integer(read_framebuffer_binding_query(), &previous_read_framebuffer);
    dispatch.bind_framebuffer(draw_framebuffer_target(), blit.destination_framebuffer);
    dispatch.bind_framebuffer(read_framebuffer_target(), blit.source_framebuffer);
    dispatch.blit_framebuffer(
        blit.source_rect.x0,
        blit.source_rect.y0,
        blit.source_rect.x1,
        blit.source_rect.y1,
        blit.destination_rect.x0,
        blit.destination_rect.y0,
        blit.destination_rect.x1,
        blit.destination_rect.y1,
        blit.mask,
        blit.filter);
    dispatch.bind_framebuffer(draw_framebuffer_target(), static_cast<std::uint32_t>(previous_draw_framebuffer));
    dispatch.bind_framebuffer(read_framebuffer_target(), static_cast<std::uint32_t>(previous_read_framebuffer));
    return pp::foundation::Status::success();
}

pp::foundation::Status readback_opengl_framebuffer(
    OpenGlFramebufferReadback readback,
    OpenGlFramebufferReadbackDispatch dispatch) noexcept
{
    if (dispatch.get_integer == nullptr
        || dispatch.bind_framebuffer == nullptr
        || dispatch.read_pixels == nullptr) {
        return pp::foundation::Status::invalid_argument(
            "OpenGL framebuffer readback dispatch callbacks must not be null");
    }

    if (readback.width <= 0
        || readback.height <= 0
        || readback.format.pixel_format == 0U
        || readback.format.component_type == 0U
        || readback.format.bytes_per_pixel == 0U
        || readback.pixels == nullptr) {
        return pp::foundation::Status::invalid_argument("OpenGL framebuffer readback parameters are invalid");
    }

    std::int32_t previous_read_framebuffer = 0;
    dispatch.get_integer(read_framebuffer_binding_query(), &previous_read_framebuffer);
    dispatch.bind_framebuffer(read_framebuffer_target(), readback.framebuffer);
    dispatch.read_pixels(
        readback.x,
        readback.y,
        readback.width,
        readback.height,
        readback.format.pixel_format,
        readback.format.component_type,
        readback.pixels);
    dispatch.bind_framebuffer(read_framebuffer_target(), static_cast<std::uint32_t>(previous_read_framebuffer));
    return pp::foundation::Status::success();
}

pp::foundation::Result<OpenGlFramebufferBindingState> bind_opengl_framebuffer_for_draw_read(
    std::uint32_t framebuffer,
    OpenGlFramebufferBindDispatch dispatch) noexcept
{
    if (dispatch.get_integer == nullptr || dispatch.bind_framebuffer == nullptr) {
        return pp::foundation::Result<OpenGlFramebufferBindingState>::failure(
            pp::foundation::Status::invalid_argument("OpenGL framebuffer bind dispatch callbacks must not be null"));
    }

    OpenGlFramebufferBindingState binding {};
    dispatch.get_integer(draw_framebuffer_binding_query(), &binding.draw_framebuffer);
    dispatch.get_integer(read_framebuffer_binding_query(), &binding.read_framebuffer);
    dispatch.bind_framebuffer(draw_framebuffer_target(), framebuffer);
    dispatch.bind_framebuffer(read_framebuffer_target(), framebuffer);
    return pp::foundation::Result<OpenGlFramebufferBindingState>::success(binding);
}

pp::foundation::Status restore_opengl_framebuffer_binding(
    OpenGlFramebufferBindingState binding,
    OpenGlFramebufferRestoreDispatch dispatch) noexcept
{
    if (dispatch.bind_framebuffer == nullptr) {
        return pp::foundation::Status::invalid_argument("OpenGL framebuffer restore dispatch callback must not be null");
    }

    dispatch.bind_framebuffer(draw_framebuffer_target(), static_cast<std::uint32_t>(binding.draw_framebuffer));
    dispatch.bind_framebuffer(read_framebuffer_target(), static_cast<std::uint32_t>(binding.read_framebuffer));
    return pp::foundation::Status::success();
}

pp::foundation::Result<OpenGlRenderTargetFramebufferAllocationResult> allocate_opengl_render_target_framebuffer(
    std::uint32_t texture_id,
    std::uint32_t depth_renderbuffer_id,
    OpenGlRenderTargetFramebufferAllocationDispatch dispatch) noexcept
{
    if (dispatch.gen_framebuffers == nullptr
        || dispatch.get_integer == nullptr
        || dispatch.bind_framebuffer == nullptr
        || dispatch.framebuffer_texture_2d == nullptr
        || dispatch.framebuffer_renderbuffer == nullptr
        || dispatch.check_framebuffer_status == nullptr) {
        return pp::foundation::Result<OpenGlRenderTargetFramebufferAllocationResult>::failure(
            pp::foundation::Status::invalid_argument(
                "OpenGL render-target framebuffer allocation dispatch callbacks must not be null"));
    }

    if (texture_id == 0U) {
        return pp::foundation::Result<OpenGlRenderTargetFramebufferAllocationResult>::failure(
            pp::foundation::Status::invalid_argument("OpenGL render-target framebuffer texture id is invalid"));
    }

    std::int32_t old_framebuffer = 0;
    dispatch.get_integer(draw_framebuffer_binding_query(), &old_framebuffer);

    std::uint32_t framebuffer_id = 0U;
    dispatch.gen_framebuffers(1U, &framebuffer_id);
    if (framebuffer_id == 0U) {
        dispatch.bind_framebuffer(framebuffer_target(), static_cast<std::uint32_t>(old_framebuffer));
        return pp::foundation::Result<OpenGlRenderTargetFramebufferAllocationResult>::failure(
            pp::foundation::Status::out_of_range("OpenGL render-target framebuffer allocation returned id 0"));
    }

    dispatch.bind_framebuffer(framebuffer_target(), framebuffer_id);
    dispatch.framebuffer_texture_2d(
        framebuffer_target(),
        framebuffer_color_attachment(),
        texture_2d_target(),
        texture_id,
        0);
    if (depth_renderbuffer_id != 0U) {
        dispatch.framebuffer_renderbuffer(
            framebuffer_target(),
            framebuffer_depth_attachment(),
            renderbuffer_target(),
            depth_renderbuffer_id);
    }

    const auto framebuffer_status = dispatch.check_framebuffer_status(framebuffer_target());
    dispatch.bind_framebuffer(framebuffer_target(), static_cast<std::uint32_t>(old_framebuffer));
    return pp::foundation::Result<OpenGlRenderTargetFramebufferAllocationResult>::success(
        OpenGlRenderTargetFramebufferAllocationResult {
            .framebuffer_id = framebuffer_id,
            .framebuffer_status = framebuffer_status,
        });
}

pp::foundation::Status delete_opengl_framebuffer(
    std::uint32_t framebuffer_id,
    OpenGlFramebufferDeleteDispatch dispatch) noexcept
{
    if (dispatch.delete_framebuffers == nullptr) {
        return pp::foundation::Status::invalid_argument("OpenGL framebuffer delete dispatch callback must not be null");
    }

    if (framebuffer_id == 0U) {
        return pp::foundation::Status::success();
    }

    dispatch.delete_framebuffers(1U, &framebuffer_id);
    return pp::foundation::Status::success();
}

pp::foundation::Result<std::uint32_t> allocate_opengl_pixel_buffer(
    OpenGlPixelBufferAllocationDispatch dispatch) noexcept
{
    if (dispatch.gen_buffers == nullptr) {
        return pp::foundation::Result<std::uint32_t>::failure(
            pp::foundation::Status::invalid_argument("OpenGL pixel-buffer allocation dispatch callback must not be null"));
    }

    std::uint32_t buffer_id = 0U;
    dispatch.gen_buffers(1U, &buffer_id);
    if (buffer_id == 0U) {
        return pp::foundation::Result<std::uint32_t>::failure(
            pp::foundation::Status::out_of_range("OpenGL pixel-buffer allocation returned id 0"));
    }

    return pp::foundation::Result<std::uint32_t>::success(buffer_id);
}

pp::foundation::Result<std::uint32_t> readback_opengl_framebuffer_to_pixel_buffer(
    std::int32_t width,
    std::int32_t height,
    OpenGlReadbackFormat format,
    OpenGlPixelBufferReadbackDispatch dispatch) noexcept
{
    if (dispatch.gen_buffers == nullptr
        || dispatch.bind_buffer == nullptr
        || dispatch.buffer_data == nullptr
        || dispatch.read_pixels == nullptr) {
        return pp::foundation::Result<std::uint32_t>::failure(
            pp::foundation::Status::invalid_argument("OpenGL pixel-buffer readback dispatch callbacks must not be null"));
    }

    if (width <= 0
        || height <= 0
        || format.pixel_format == 0U
        || format.component_type == 0U
        || format.bytes_per_pixel == 0U) {
        return pp::foundation::Result<std::uint32_t>::failure(
            pp::foundation::Status::invalid_argument("OpenGL pixel-buffer readback parameters are invalid"));
    }

    const auto buffer_id = allocate_opengl_pixel_buffer(OpenGlPixelBufferAllocationDispatch {
        .gen_buffers = dispatch.gen_buffers,
    });
    if (!buffer_id.ok()) {
        return buffer_id;
    }

    const auto target = pixel_pack_buffer_target();
    dispatch.bind_buffer(target, buffer_id.value());
    dispatch.buffer_data(
        target,
        static_cast<std::intptr_t>(readback_byte_count(
            format,
            static_cast<std::uint32_t>(width),
            static_cast<std::uint32_t>(height))),
        nullptr,
        pixel_buffer_stream_read_usage());
    dispatch.read_pixels(0, 0, width, height, format.pixel_format, format.component_type, nullptr);
    dispatch.bind_buffer(target, 0U);

    return pp::foundation::Result<std::uint32_t>::success(buffer_id.value());
}

pp::foundation::Result<void*> map_opengl_pixel_buffer(
    std::uint32_t buffer_id,
    std::int32_t width,
    std::int32_t height,
    OpenGlReadbackFormat format,
    OpenGlPixelBufferMapDispatch dispatch) noexcept
{
    if (dispatch.bind_buffer == nullptr || dispatch.map_buffer_range == nullptr) {
        return pp::foundation::Result<void*>::failure(
            pp::foundation::Status::invalid_argument("OpenGL pixel-buffer map dispatch callbacks must not be null"));
    }

    if (buffer_id == 0U
        || width <= 0
        || height <= 0
        || format.pixel_format == 0U
        || format.component_type == 0U
        || format.bytes_per_pixel == 0U) {
        return pp::foundation::Result<void*>::failure(
            pp::foundation::Status::invalid_argument("OpenGL pixel-buffer map parameters are invalid"));
    }

    const auto target = pixel_pack_buffer_target();
    dispatch.bind_buffer(target, buffer_id);
    void* const mapped = dispatch.map_buffer_range(
        target,
        0,
        static_cast<std::intptr_t>(readback_byte_count(
            format,
            static_cast<std::uint32_t>(width),
            static_cast<std::uint32_t>(height))),
        pixel_buffer_map_read_access());
    dispatch.bind_buffer(target, 0U);

    if (mapped == nullptr) {
        return pp::foundation::Result<void*>::failure(
            pp::foundation::Status::out_of_range("OpenGL pixel-buffer map returned null"));
    }

    return pp::foundation::Result<void*>::success(mapped);
}

pp::foundation::Status unmap_opengl_pixel_buffer(
    std::uint32_t buffer_id,
    OpenGlPixelBufferUnmapDispatch dispatch) noexcept
{
    if (dispatch.bind_buffer == nullptr || dispatch.unmap_buffer == nullptr) {
        return pp::foundation::Status::invalid_argument("OpenGL pixel-buffer unmap dispatch callbacks must not be null");
    }

    if (buffer_id == 0U) {
        return pp::foundation::Status::invalid_argument("OpenGL pixel-buffer unmap buffer id is invalid");
    }

    const auto target = pixel_pack_buffer_target();
    dispatch.bind_buffer(target, buffer_id);
    dispatch.unmap_buffer(target);
    dispatch.bind_buffer(target, 0U);
    return pp::foundation::Status::success();
}

pp::foundation::Status delete_opengl_pixel_buffer(
    std::uint32_t buffer_id,
    OpenGlPixelBufferDeleteDispatch dispatch) noexcept
{
    if (dispatch.delete_buffers == nullptr) {
        return pp::foundation::Status::invalid_argument("OpenGL pixel-buffer delete dispatch callback must not be null");
    }

    if (buffer_id == 0U) {
        return pp::foundation::Status::success();
    }

    dispatch.delete_buffers(1U, &buffer_id);
    return pp::foundation::Status::success();
}

pp::foundation::Result<std::uint32_t> allocate_opengl_depth_renderbuffer(
    std::int32_t width,
    std::int32_t height,
    OpenGlDepthRenderbufferAllocationDispatch dispatch) noexcept
{
    if (dispatch.gen_renderbuffers == nullptr
        || dispatch.bind_renderbuffer == nullptr
        || dispatch.renderbuffer_storage == nullptr) {
        return pp::foundation::Result<std::uint32_t>::failure(
            pp::foundation::Status::invalid_argument(
                "OpenGL depth renderbuffer allocation dispatch callbacks must not be null"));
    }

    if (width <= 0 || height <= 0) {
        return pp::foundation::Result<std::uint32_t>::failure(
            pp::foundation::Status::invalid_argument("OpenGL depth renderbuffer dimensions are invalid"));
    }

    std::uint32_t renderbuffer_id = 0U;
    dispatch.gen_renderbuffers(1U, &renderbuffer_id);
    if (renderbuffer_id == 0U) {
        return pp::foundation::Result<std::uint32_t>::failure(
            pp::foundation::Status::out_of_range("OpenGL renderbuffer allocation returned id 0"));
    }

    dispatch.bind_renderbuffer(renderbuffer_target(), renderbuffer_id);
    dispatch.renderbuffer_storage(renderbuffer_target(), depth_component24_format(), width, height);
    dispatch.bind_renderbuffer(renderbuffer_target(), default_framebuffer_id());
    return pp::foundation::Result<std::uint32_t>::success(renderbuffer_id);
}

pp::foundation::Status delete_opengl_renderbuffer(
    std::uint32_t renderbuffer_id,
    OpenGlRenderbufferDeleteDispatch dispatch) noexcept
{
    if (dispatch.delete_renderbuffers == nullptr) {
        return pp::foundation::Status::invalid_argument("OpenGL renderbuffer delete callback must not be null");
    }

    if (renderbuffer_id == 0U) {
        return pp::foundation::Status::success();
    }

    dispatch.delete_renderbuffers(1U, &renderbuffer_id);
    return pp::foundation::Status::success();
}

pp::foundation::Status attach_opengl_depth_renderbuffer(
    std::uint32_t renderbuffer_id,
    OpenGlDepthRenderbufferAttachmentDispatch dispatch) noexcept
{
    if (dispatch.framebuffer_renderbuffer == nullptr) {
        return pp::foundation::Status::invalid_argument(
            "OpenGL depth renderbuffer attachment callback must not be null");
    }

    dispatch.framebuffer_renderbuffer(
        framebuffer_target(),
        framebuffer_depth_attachment(),
        renderbuffer_target(),
        renderbuffer_id);
    return pp::foundation::Status::success();
}

pp::foundation::Result<std::uint32_t> create_opengl_sampler(
    std::span<const OpenGlTextureParameter> parameters,
    OpenGlSamplerCreateDispatch dispatch) noexcept
{
    if (dispatch.gen_samplers == nullptr || dispatch.sampler_parameter_i == nullptr) {
        return pp::foundation::Result<std::uint32_t>::failure(
            pp::foundation::Status::invalid_argument("OpenGL sampler create dispatch callbacks must not be null"));
    }

    if (parameters.empty()) {
        return pp::foundation::Result<std::uint32_t>::failure(
            pp::foundation::Status::invalid_argument("OpenGL sampler parameters are invalid"));
    }

    for (const auto parameter : parameters) {
        if (parameter.name == 0U) {
            return pp::foundation::Result<std::uint32_t>::failure(
                pp::foundation::Status::invalid_argument("OpenGL sampler parameter name is invalid"));
        }
    }

    std::uint32_t sampler_id = 0U;
    dispatch.gen_samplers(1U, &sampler_id);
    if (sampler_id == 0U) {
        return pp::foundation::Result<std::uint32_t>::failure(
            pp::foundation::Status::out_of_range("OpenGL sampler allocation returned id 0"));
    }

    const auto status = set_opengl_sampler_parameters(
        sampler_id,
        parameters,
        OpenGlSamplerParameterDispatch {
            .sampler_parameter_i = dispatch.sampler_parameter_i,
        });
    if (!status.ok()) {
        return pp::foundation::Result<std::uint32_t>::failure(status);
    }

    return pp::foundation::Result<std::uint32_t>::success(sampler_id);
}

pp::foundation::Status set_opengl_sampler_parameters(
    std::uint32_t sampler_id,
    std::span<const OpenGlTextureParameter> parameters,
    OpenGlSamplerParameterDispatch dispatch) noexcept
{
    if (dispatch.sampler_parameter_i == nullptr) {
        return pp::foundation::Status::invalid_argument("OpenGL sampler parameter dispatch callback must not be null");
    }

    if (sampler_id == 0U || parameters.empty()) {
        return pp::foundation::Status::invalid_argument("OpenGL sampler parameters are invalid");
    }

    for (const auto parameter : parameters) {
        if (parameter.name == 0U) {
            return pp::foundation::Status::invalid_argument("OpenGL sampler parameter name is invalid");
        }
        dispatch.sampler_parameter_i(
            sampler_id,
            parameter.name,
            static_cast<std::int32_t>(parameter.value));
    }
    return pp::foundation::Status::success();
}

pp::foundation::Status set_opengl_sampler_border_color(
    std::uint32_t sampler_id,
    std::uint32_t parameter,
    const float* rgba,
    OpenGlSamplerBorderDispatch dispatch) noexcept
{
    if (dispatch.sampler_parameter_fv == nullptr) {
        return pp::foundation::Status::invalid_argument(
            "OpenGL sampler border color dispatch callback must not be null");
    }

    if (sampler_id == 0U || parameter == 0U || rgba == nullptr) {
        return pp::foundation::Status::invalid_argument("OpenGL sampler border color parameters are invalid");
    }

    dispatch.sampler_parameter_fv(sampler_id, parameter, rgba);
    return pp::foundation::Status::success();
}

pp::foundation::Status bind_opengl_sampler_object(
    std::uint32_t unit,
    std::uint32_t sampler_id,
    OpenGlSamplerBindDispatch dispatch) noexcept
{
    if (dispatch.bind_sampler == nullptr) {
        return pp::foundation::Status::invalid_argument("OpenGL sampler bind dispatch callback must not be null");
    }

    dispatch.bind_sampler(unit, sampler_id);
    return pp::foundation::Status::success();
}

pp::foundation::Status use_opengl_program(
    std::uint32_t program_id,
    OpenGlProgramUseDispatch dispatch) noexcept
{
    if (dispatch.use_program == nullptr) {
        return pp::foundation::Status::invalid_argument("OpenGL program use dispatch callback must not be null");
    }

    dispatch.use_program(program_id);
    return pp::foundation::Status::success();
}

pp::foundation::Status delete_opengl_program(
    std::uint32_t program_id,
    OpenGlProgramDeleteDispatch dispatch) noexcept
{
    if (dispatch.use_program == nullptr || dispatch.delete_program == nullptr) {
        return pp::foundation::Status::invalid_argument("OpenGL program delete dispatch callbacks must not be null");
    }

    if (program_id == 0U) {
        return pp::foundation::Status::success();
    }

    dispatch.use_program(default_framebuffer_id());
    dispatch.delete_program(program_id);
    return pp::foundation::Status::success();
}

pp::foundation::Status set_opengl_uniform_vec4(
    std::int32_t location,
    const float* values,
    OpenGlUniformVec4Dispatch dispatch) noexcept
{
    if (dispatch.uniform_4fv == nullptr) {
        return pp::foundation::Status::invalid_argument("OpenGL vec4 uniform dispatch callback must not be null");
    }

    if (values == nullptr) {
        return pp::foundation::Status::invalid_argument("OpenGL vec4 uniform values must not be null");
    }

    dispatch.uniform_4fv(location, 1, values);
    return pp::foundation::Status::success();
}

pp::foundation::Status set_opengl_uniform_vec3(
    std::int32_t location,
    const float* values,
    OpenGlUniformVec3Dispatch dispatch) noexcept
{
    if (dispatch.uniform_3fv == nullptr) {
        return pp::foundation::Status::invalid_argument("OpenGL vec3 uniform dispatch callback must not be null");
    }

    if (values == nullptr) {
        return pp::foundation::Status::invalid_argument("OpenGL vec3 uniform values must not be null");
    }

    dispatch.uniform_3fv(location, 1, values);
    return pp::foundation::Status::success();
}

pp::foundation::Status set_opengl_uniform_vec2(
    std::int32_t location,
    const float* values,
    OpenGlUniformVec2Dispatch dispatch) noexcept
{
    if (dispatch.uniform_2fv == nullptr) {
        return pp::foundation::Status::invalid_argument("OpenGL vec2 uniform dispatch callback must not be null");
    }

    if (values == nullptr) {
        return pp::foundation::Status::invalid_argument("OpenGL vec2 uniform values must not be null");
    }

    dispatch.uniform_2fv(location, 1, values);
    return pp::foundation::Status::success();
}

pp::foundation::Status set_opengl_uniform_mat4(
    std::int32_t location,
    const float* values,
    OpenGlUniformMat4Dispatch dispatch) noexcept
{
    if (dispatch.uniform_matrix_4fv == nullptr) {
        return pp::foundation::Status::invalid_argument("OpenGL mat4 uniform dispatch callback must not be null");
    }

    if (values == nullptr) {
        return pp::foundation::Status::invalid_argument("OpenGL mat4 uniform values must not be null");
    }

    dispatch.uniform_matrix_4fv(
        location,
        1,
        static_cast<std::uint8_t>(matrix_uniform_not_transposed()),
        values);
    return pp::foundation::Status::success();
}

pp::foundation::Status set_opengl_uniform_int(
    std::int32_t location,
    std::int32_t value,
    OpenGlUniformIntDispatch dispatch) noexcept
{
    if (dispatch.uniform_1i == nullptr) {
        return pp::foundation::Status::invalid_argument("OpenGL int uniform dispatch callback must not be null");
    }

    dispatch.uniform_1i(location, value);
    return pp::foundation::Status::success();
}

pp::foundation::Status set_opengl_uniform_float(
    std::int32_t location,
    float value,
    OpenGlUniformFloatDispatch dispatch) noexcept
{
    if (dispatch.uniform_1f == nullptr) {
        return pp::foundation::Status::invalid_argument("OpenGL float uniform dispatch callback must not be null");
    }

    dispatch.uniform_1f(location, value);
    return pp::foundation::Status::success();
}

pp::foundation::Result<std::int32_t> get_opengl_attribute_location(
    std::uint32_t program_id,
    const char* attribute_name,
    OpenGlAttributeLocationDispatch dispatch) noexcept
{
    if (dispatch.get_attrib_location == nullptr) {
        return pp::foundation::Result<std::int32_t>::failure(
            pp::foundation::Status::invalid_argument(
                "OpenGL attribute-location dispatch callback must not be null"));
    }

    if (program_id == 0U || attribute_name == nullptr || attribute_name[0] == '\0') {
        return pp::foundation::Result<std::int32_t>::failure(
            pp::foundation::Status::invalid_argument("OpenGL attribute-location parameters are invalid"));
    }

    return pp::foundation::Result<std::int32_t>::success(
        dispatch.get_attrib_location(program_id, attribute_name));
}

pp::foundation::Result<OpenGlShaderCompileInfo> compile_opengl_shader_source(
    std::uint32_t stage,
    const char* source,
    char* info_log,
    std::int32_t info_log_capacity,
    OpenGlShaderCompileDispatch dispatch) noexcept
{
    if (dispatch.create_shader == nullptr
        || dispatch.shader_source == nullptr
        || dispatch.compile_shader == nullptr
        || dispatch.get_shader_integer == nullptr
        || dispatch.get_shader_info_log == nullptr) {
        return pp::foundation::Result<OpenGlShaderCompileInfo>::failure(
            pp::foundation::Status::invalid_argument("OpenGL shader compile dispatch callbacks must not be null"));
    }

    if (stage == 0U || source == nullptr || source[0] == '\0' || info_log == nullptr || info_log_capacity <= 0) {
        return pp::foundation::Result<OpenGlShaderCompileInfo>::failure(
            pp::foundation::Status::invalid_argument("OpenGL shader compile parameters are invalid"));
    }

    const auto shader_id = dispatch.create_shader(stage);
    if (shader_id == 0U) {
        return pp::foundation::Result<OpenGlShaderCompileInfo>::failure(
            pp::foundation::Status::out_of_range("OpenGL shader allocation returned id 0"));
    }

    const char* const sources[] { source };
    dispatch.shader_source(shader_id, 1, sources);
    dispatch.compile_shader(shader_id);

    OpenGlShaderCompileInfo info {
        .shader_id = shader_id,
    };
    dispatch.get_shader_integer(shader_id, shader_compile_status_query(), &info.compile_status);
    dispatch.get_shader_info_log(shader_id, info_log_capacity, &info.info_log_length, info_log);
    return pp::foundation::Result<OpenGlShaderCompileInfo>::success(info);
}

pp::foundation::Status delete_opengl_shader(
    std::uint32_t shader_id,
    OpenGlShaderDeleteDispatch dispatch) noexcept
{
    if (dispatch.delete_shader == nullptr) {
        return pp::foundation::Status::invalid_argument("OpenGL shader delete dispatch callback must not be null");
    }

    if (shader_id == 0U) {
        return pp::foundation::Status::success();
    }

    dispatch.delete_shader(shader_id);
    return pp::foundation::Status::success();
}

pp::foundation::Result<OpenGlProgramLinkInfo> link_opengl_shader_program(
    std::uint32_t vertex_shader_id,
    std::uint32_t fragment_shader_id,
    std::span<const OpenGlAttributeBinding> attribute_bindings,
    char* info_log,
    std::int32_t info_log_capacity,
    OpenGlProgramLinkDispatch dispatch) noexcept
{
    if (dispatch.create_program == nullptr
        || dispatch.attach_shader == nullptr
        || dispatch.delete_shader == nullptr
        || dispatch.link_program == nullptr
        || dispatch.get_attrib_location == nullptr
        || dispatch.bind_attrib_location == nullptr
        || dispatch.get_program_integer == nullptr
        || dispatch.get_program_info_log == nullptr) {
        return pp::foundation::Result<OpenGlProgramLinkInfo>::failure(
            pp::foundation::Status::invalid_argument("OpenGL program link dispatch callbacks must not be null"));
    }

    if (vertex_shader_id == 0U
        || fragment_shader_id == 0U
        || attribute_bindings.empty()
        || info_log == nullptr
        || info_log_capacity <= 0) {
        return pp::foundation::Result<OpenGlProgramLinkInfo>::failure(
            pp::foundation::Status::invalid_argument("OpenGL program link parameters are invalid"));
    }

    const auto program_id = dispatch.create_program();
    if (program_id == 0U) {
        return pp::foundation::Result<OpenGlProgramLinkInfo>::failure(
            pp::foundation::Status::out_of_range("OpenGL program allocation returned id 0"));
    }

    dispatch.attach_shader(program_id, vertex_shader_id);
    dispatch.attach_shader(program_id, fragment_shader_id);
    dispatch.delete_shader(vertex_shader_id);
    dispatch.delete_shader(fragment_shader_id);
    dispatch.link_program(program_id);

    for (const auto& binding : attribute_bindings) {
        if (binding.name != nullptr && dispatch.get_attrib_location(program_id, binding.name) != -1) {
            dispatch.bind_attrib_location(program_id, binding.location, binding.name);
        }
    }

    OpenGlProgramLinkInfo info {
        .program_id = program_id,
    };
    dispatch.link_program(program_id);
    dispatch.get_program_integer(program_id, program_link_status_query(), &info.link_status);
    dispatch.get_program_info_log(program_id, info_log_capacity, &info.info_log_length, info_log);
    return pp::foundation::Result<OpenGlProgramLinkInfo>::success(info);
}

pp::foundation::Result<std::int32_t> query_opengl_program_integer(
    std::uint32_t program_id,
    std::uint32_t query,
    OpenGlProgramIntegerDispatch dispatch) noexcept
{
    if (dispatch.get_program_integer == nullptr) {
        return pp::foundation::Result<std::int32_t>::failure(
            pp::foundation::Status::invalid_argument("OpenGL program integer dispatch callback must not be null"));
    }

    if (program_id == 0U || query == 0U) {
        return pp::foundation::Result<std::int32_t>::failure(
            pp::foundation::Status::invalid_argument("OpenGL program integer parameters are invalid"));
    }

    std::int32_t value = 0;
    dispatch.get_program_integer(program_id, query, &value);
    return pp::foundation::Result<std::int32_t>::success(value);
}

pp::foundation::Result<OpenGlActiveUniformInfo> get_opengl_active_uniform(
    std::uint32_t program_id,
    std::uint32_t index,
    char* name,
    std::int32_t name_capacity,
    OpenGlActiveUniformDispatch dispatch) noexcept
{
    if (dispatch.get_active_uniform == nullptr) {
        return pp::foundation::Result<OpenGlActiveUniformInfo>::failure(
            pp::foundation::Status::invalid_argument("OpenGL active uniform dispatch callback must not be null"));
    }

    if (program_id == 0U || name == nullptr || name_capacity <= 0) {
        return pp::foundation::Result<OpenGlActiveUniformInfo>::failure(
            pp::foundation::Status::invalid_argument("OpenGL active uniform parameters are invalid"));
    }

    OpenGlActiveUniformInfo info {};
    dispatch.get_active_uniform(
        program_id,
        index,
        name_capacity,
        &info.length,
        &info.size,
        &info.type,
        name);
    if (info.length >= 0 && info.length < name_capacity) {
        name[info.length] = '\0';
    } else {
        name[name_capacity - 1] = '\0';
    }
    return pp::foundation::Result<OpenGlActiveUniformInfo>::success(info);
}

pp::foundation::Result<std::int32_t> get_opengl_uniform_location(
    std::uint32_t program_id,
    const char* uniform_name,
    OpenGlUniformLocationDispatch dispatch) noexcept
{
    if (dispatch.get_uniform_location == nullptr) {
        return pp::foundation::Result<std::int32_t>::failure(
            pp::foundation::Status::invalid_argument("OpenGL uniform-location dispatch callback must not be null"));
    }

    if (program_id == 0U || uniform_name == nullptr || uniform_name[0] == '\0') {
        return pp::foundation::Result<std::int32_t>::failure(
            pp::foundation::Status::invalid_argument("OpenGL uniform-location parameters are invalid"));
    }

    return pp::foundation::Result<std::int32_t>::success(
        dispatch.get_uniform_location(program_id, uniform_name));
}

pp::foundation::Result<OpenGlMeshObjects> create_opengl_mesh_objects(
    OpenGlMeshUpload upload,
    OpenGlMeshCreateDispatch dispatch) noexcept
{
    if (dispatch.gen_buffers == nullptr
        || dispatch.bind_buffer == nullptr
        || dispatch.buffer_data == nullptr
        || dispatch.gen_vertex_arrays == nullptr
        || dispatch.bind_vertex_array == nullptr
        || dispatch.enable_vertex_attrib_array == nullptr
        || dispatch.vertex_attrib_pointer == nullptr) {
        return pp::foundation::Result<OpenGlMeshObjects>::failure(
            pp::foundation::Status::invalid_argument("OpenGL mesh create dispatch callbacks must not be null"));
    }

    if (upload.vertex_byte_count < 0
        || (upload.vertex_byte_count > 0 && upload.vertex_data == nullptr)
        || upload.index_byte_count < 0
        || (upload.index_byte_count > 0 && upload.index_data == nullptr)
        || (!upload.indexed && upload.index_byte_count != 0)
        || upload.vertex_array_count == 0U
        || upload.vertex_array_count > 2U
        || upload.attributes.empty()) {
        return pp::foundation::Result<OpenGlMeshObjects>::failure(
            pp::foundation::Status::invalid_argument("OpenGL mesh upload parameters are invalid"));
    }

    for (const auto& attribute : upload.attributes) {
        if (attribute.component_count <= 0 || attribute.component_type == 0U || attribute.stride <= 0) {
            return pp::foundation::Result<OpenGlMeshObjects>::failure(
                pp::foundation::Status::invalid_argument("OpenGL mesh vertex attribute parameters are invalid"));
        }
    }

    std::array<std::uint32_t, 2> buffers {};
    const auto buffer_count = upload.indexed ? 2U : 1U;
    dispatch.gen_buffers(buffer_count, buffers.data());
    if (buffers[0] == 0U || (upload.indexed && buffers[1] == 0U)) {
        return pp::foundation::Result<OpenGlMeshObjects>::failure(
            pp::foundation::Status::out_of_range("OpenGL mesh buffer allocation returned id 0"));
    }

    if (upload.indexed && upload.index_byte_count > 0) {
        dispatch.bind_buffer(element_array_buffer_target(), buffers[1]);
        dispatch.buffer_data(element_array_buffer_target(), upload.index_byte_count, upload.index_data, static_draw_buffer_usage());
    }
    if (upload.vertex_byte_count > 0) {
        dispatch.bind_buffer(array_buffer_target(), buffers[0]);
        dispatch.buffer_data(array_buffer_target(), upload.vertex_byte_count, upload.vertex_data, static_draw_buffer_usage());
    }
    dispatch.bind_buffer(element_array_buffer_target(), 0U);
    dispatch.bind_buffer(array_buffer_target(), 0U);

    std::array<std::uint32_t, 2> vertex_arrays {};
    dispatch.gen_vertex_arrays(upload.vertex_array_count, vertex_arrays.data());
    if (vertex_arrays[0] == 0U || (upload.vertex_array_count > 1U && vertex_arrays[1] == 0U)) {
        return pp::foundation::Result<OpenGlMeshObjects>::failure(
            pp::foundation::Status::out_of_range("OpenGL mesh vertex array allocation returned id 0"));
    }

    for (std::uint32_t vertex_array_index = 0U; vertex_array_index < upload.vertex_array_count; ++vertex_array_index) {
        const auto vertex_array = vertex_arrays[vertex_array_index];
        dispatch.bind_vertex_array(vertex_array);
        for (const auto& attribute : upload.attributes) {
            dispatch.enable_vertex_attrib_array(attribute.index);
        }
        if (upload.indexed) {
            dispatch.bind_buffer(element_array_buffer_target(), buffers[1]);
        }
        dispatch.bind_buffer(array_buffer_target(), buffers[0]);
        for (const auto& attribute : upload.attributes) {
            dispatch.vertex_attrib_pointer(
                attribute.index,
                attribute.component_count,
                attribute.component_type,
                attribute.normalized,
                attribute.stride,
                reinterpret_cast<const void*>(attribute.offset));
        }
    }
    dispatch.bind_vertex_array(0U);

    return pp::foundation::Result<OpenGlMeshObjects>::success(OpenGlMeshObjects {
        .vertex_buffer = buffers[0],
        .index_buffer = buffers[1],
        .vertex_arrays = vertex_arrays,
    });
}

pp::foundation::Status upload_opengl_buffer_data(
    OpenGlBufferUpload upload,
    OpenGlBufferUploadDispatch dispatch) noexcept
{
    if (dispatch.bind_buffer == nullptr || dispatch.buffer_data == nullptr) {
        return pp::foundation::Status::invalid_argument("OpenGL buffer upload dispatch callbacks must not be null");
    }

    if (upload.target == 0U
        || upload.buffer_id == 0U
        || upload.byte_count < 0
        || (upload.byte_count > 0 && upload.data == nullptr)
        || upload.usage == 0U) {
        return pp::foundation::Status::invalid_argument("OpenGL buffer upload parameters are invalid");
    }

    dispatch.bind_buffer(upload.target, upload.buffer_id);
    dispatch.buffer_data(upload.target, upload.byte_count, upload.data, upload.usage);
    dispatch.bind_buffer(upload.target, 0U);
    return pp::foundation::Status::success();
}

pp::foundation::Status draw_opengl_mesh(
    OpenGlMeshDraw draw,
    OpenGlMeshDrawDispatch dispatch) noexcept
{
    if (dispatch.bind_vertex_array == nullptr
        || dispatch.draw_elements == nullptr
        || dispatch.draw_arrays == nullptr) {
        return pp::foundation::Status::invalid_argument("OpenGL mesh draw dispatch callbacks must not be null");
    }

    if (draw.vertex_array == 0U || draw.count < 0 || (draw.indexed && draw.index_type == 0U)) {
        return pp::foundation::Status::invalid_argument("OpenGL mesh draw parameters are invalid");
    }

    dispatch.bind_vertex_array(draw.vertex_array);
    if (draw.indexed) {
        dispatch.draw_elements(draw.mode, draw.count, draw.index_type, draw.index_offset);
    } else {
        dispatch.draw_arrays(draw.mode, 0, draw.count);
    }
    dispatch.bind_vertex_array(0U);
    return pp::foundation::Status::success();
}

pp::foundation::Status delete_opengl_mesh_objects(
    OpenGlMeshDelete objects,
    OpenGlMeshDeleteDispatch dispatch) noexcept
{
    if (dispatch.delete_buffers == nullptr || dispatch.delete_vertex_arrays == nullptr) {
        return pp::foundation::Status::invalid_argument("OpenGL mesh delete dispatch callbacks must not be null");
    }

    if (objects.buffers[0] != 0U || objects.buffers[1] != 0U) {
        dispatch.delete_buffers(1U, &objects.buffers[0]);
        dispatch.delete_buffers(1U, &objects.buffers[1]);
    }
    if (objects.vertex_arrays[0] != 0U || objects.vertex_arrays[1] != 0U) {
        dispatch.delete_vertex_arrays(1U, &objects.vertex_arrays[0]);
        dispatch.delete_vertex_arrays(1U, &objects.vertex_arrays[1]);
    }
    return pp::foundation::Status::success();
}

std::uint32_t extension_count_query() noexcept
{
    return gl_num_extensions;
}

std::uint32_t extension_string_name() noexcept
{
    return gl_extensions;
}

std::uint32_t no_error_code() noexcept
{
    return gl_no_error;
}

const char* opengl_error_name(std::uint32_t error_code) noexcept
{
    switch (error_code) {
    case gl_no_error:
        return "GL_NO_ERROR";
    case gl_invalid_enum:
        return "GL_INVALID_ENUM";
    case gl_invalid_value:
        return "GL_INVALID_VALUE";
    case gl_invalid_operation:
        return "GL_INVALID_OPERATION";
    case gl_invalid_framebuffer_operation:
        return "GL_INVALID_FRAMEBUFFER_OPERATION";
    case gl_out_of_memory:
        return "GL_OUT_OF_MEMORY";
    default:
        return "Unknown";
    }
}

std::uint32_t texture_upload_type_for_internal_format(std::uint32_t internal_format) noexcept
{
    switch (internal_format) {
    case gl_rgba32f:
        return gl_float;
    case gl_rgba16f:
        return gl_half_float;
    default:
        return gl_unsigned_byte;
    }
}

std::uint32_t unsigned_byte_component_type() noexcept
{
    return gl_unsigned_byte;
}

std::uint32_t rgba_pixel_format() noexcept
{
    return gl_rgba;
}

OpenGlPixelFormat texture_format_for_channel_count(std::uint32_t channel_count) noexcept
{
    switch (channel_count) {
    case 1:
        return OpenGlPixelFormat { .internal_format = gl_r8, .pixel_format = gl_red, .channel_count = 1 };
    case 2:
        return OpenGlPixelFormat { .internal_format = gl_rg8, .pixel_format = gl_rg, .channel_count = 2 };
    case 3:
        return OpenGlPixelFormat { .internal_format = gl_rgb8, .pixel_format = gl_rgb, .channel_count = 3 };
    case 4:
        return OpenGlPixelFormat { .internal_format = gl_rgba8, .pixel_format = gl_rgba, .channel_count = 4 };
    default:
        return OpenGlPixelFormat {};
    }
}

OpenGlRendererTextureFormat texture_format_for_renderer_format(pp::renderer::TextureFormat format) noexcept
{
    switch (format) {
    case pp::renderer::TextureFormat::rgba8:
        return OpenGlRendererTextureFormat {
            .internal_format = gl_rgba8,
            .pixel_format = gl_rgba,
            .component_type = gl_unsigned_byte,
            .bytes_per_pixel = 4U,
        };
    case pp::renderer::TextureFormat::r8:
        return OpenGlRendererTextureFormat {
            .internal_format = gl_r8,
            .pixel_format = gl_red,
            .component_type = gl_unsigned_byte,
            .bytes_per_pixel = 1U,
        };
    case pp::renderer::TextureFormat::depth24_stencil8:
        return OpenGlRendererTextureFormat {
            .internal_format = gl_depth24_stencil8,
            .pixel_format = gl_depth_stencil,
            .component_type = gl_unsigned_int_24_8,
            .bytes_per_pixel = 4U,
        };
    default:
        return OpenGlRendererTextureFormat {};
    }
}

OpenGlReadbackFormat rgba8_readback_format() noexcept
{
    return OpenGlReadbackFormat {
        .pixel_format = gl_rgba,
        .component_type = gl_unsigned_byte,
        .bytes_per_pixel = 4U,
    };
}

OpenGlReadbackFormat rgba32f_readback_format() noexcept
{
    return OpenGlReadbackFormat {
        .pixel_format = gl_rgba,
        .component_type = gl_float,
        .bytes_per_pixel = 16U,
    };
}

std::uint64_t readback_byte_count(
    OpenGlReadbackFormat format,
    std::uint32_t width,
    std::uint32_t height) noexcept
{
    if (format.bytes_per_pixel == 0U) {
        return 0U;
    }

    const auto wide_width = static_cast<std::uint64_t>(width);
    const auto wide_height = static_cast<std::uint64_t>(height);
    if (wide_width != 0U && wide_height > std::numeric_limits<std::uint64_t>::max() / wide_width) {
        return 0U;
    }

    const auto pixels = wide_width * wide_height;
    if (pixels != 0U && format.bytes_per_pixel > std::numeric_limits<std::uint64_t>::max() / pixels) {
        return 0U;
    }

    return pixels * format.bytes_per_pixel;
}

std::uint32_t pixel_pack_buffer_target() noexcept
{
    return gl_pixel_pack_buffer;
}

std::uint32_t pixel_unpack_buffer_target() noexcept
{
    return gl_pixel_unpack_buffer;
}

std::uint32_t pixel_buffer_stream_read_usage() noexcept
{
    return gl_stream_read;
}

std::uint32_t pixel_buffer_map_read_access() noexcept
{
    return gl_map_read_bit;
}

std::uint32_t texture_2d_target() noexcept
{
    return gl_texture_2d;
}

std::uint32_t renderbuffer_target() noexcept
{
    return gl_renderbuffer;
}

std::uint32_t depth_component24_format() noexcept
{
    return gl_depth_component24;
}

std::uint32_t framebuffer_target() noexcept
{
    return gl_framebuffer;
}

std::uint32_t draw_framebuffer_target() noexcept
{
    return gl_draw_framebuffer;
}

std::uint32_t read_framebuffer_target() noexcept
{
    return gl_read_framebuffer;
}

std::uint32_t draw_framebuffer_binding_query() noexcept
{
    return gl_draw_framebuffer_binding;
}

std::uint32_t read_framebuffer_binding_query() noexcept
{
    return gl_read_framebuffer_binding;
}

std::uint32_t framebuffer_color_attachment() noexcept
{
    return gl_color_attachment0;
}

std::uint32_t framebuffer_depth_attachment() noexcept
{
    return gl_depth_attachment;
}

std::uint32_t framebuffer_complete_status() noexcept
{
    return gl_framebuffer_complete;
}

std::uint32_t default_framebuffer_id() noexcept
{
    return 0U;
}

const char* framebuffer_status_name(std::uint32_t status) noexcept
{
    switch (status) {
    case gl_framebuffer_complete:
        return "GL_FRAMEBUFFER_COMPLETE";
    case gl_framebuffer_undefined:
        return "GL_FRAMEBUFFER_UNDEFINED";
    case gl_framebuffer_incomplete_attachment:
        return "GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT";
    case gl_framebuffer_incomplete_missing_attachment:
        return "GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT";
    case gl_framebuffer_incomplete_draw_buffer:
        return "GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER";
    case gl_framebuffer_incomplete_read_buffer:
        return "GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER";
    case gl_framebuffer_unsupported:
        return "GL_FRAMEBUFFER_UNSUPPORTED";
    case gl_framebuffer_incomplete_multisample:
        return "GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE";
    default:
        return "UNKNOWN";
    }
}

std::uint32_t framebuffer_color_buffer_mask() noexcept
{
    return gl_color_buffer_bit;
}

std::uint32_t framebuffer_depth_buffer_mask() noexcept
{
    return gl_depth_buffer_bit;
}

std::uint32_t framebuffer_stencil_buffer_mask() noexcept
{
    return gl_stencil_buffer_bit;
}

std::uint32_t clear_mask_for_render_pass(pp::renderer::RenderPassDesc desc) noexcept
{
    std::uint32_t mask = 0U;
    if (desc.clear_color_enabled) {
        mask |= gl_color_buffer_bit;
    }

    if (desc.clear_depth_enabled) {
        mask |= gl_depth_buffer_bit;
    }

    if (desc.clear_stencil_enabled) {
        mask |= gl_stencil_buffer_bit;
    }

    return mask;
}

OpenGlClearValues clear_values_for_render_pass(pp::renderer::RenderPassDesc desc) noexcept
{
    return OpenGlClearValues {
        .color = {
            desc.clear_color.r,
            desc.clear_color.g,
            desc.clear_color.b,
            desc.clear_color.a,
        },
        .depth = desc.clear_depth,
        .stencil = desc.clear_stencil,
    };
}

std::uint32_t color_write_mask_query() noexcept
{
    return gl_color_writemask;
}

std::uint32_t framebuffer_blit_filter(bool linear) noexcept
{
    return linear ? gl_linear : gl_nearest;
}

OpenGlEnumMapping blit_filter_for_renderer_filter(pp::renderer::BlitFilter filter) noexcept
{
    switch (filter) {
    case pp::renderer::BlitFilter::nearest:
        return OpenGlEnumMapping { .value = gl_nearest, .supported = true };
    case pp::renderer::BlitFilter::linear:
        return OpenGlEnumMapping { .value = gl_linear, .supported = true };
    default:
        return OpenGlEnumMapping {};
    }
}

std::uint32_t primitive_mode_for_renderer_topology(pp::renderer::PrimitiveTopology topology) noexcept
{
    switch (topology) {
    case pp::renderer::PrimitiveTopology::triangles:
        return gl_triangles;
    case pp::renderer::PrimitiveTopology::triangle_strip:
        return gl_triangle_strip;
    case pp::renderer::PrimitiveTopology::lines:
        return gl_lines;
    default:
        return 0U;
    }
}

std::uint32_t index_type_for_index_size(std::uint32_t index_size_bytes) noexcept
{
    switch (index_size_bytes) {
    case 2:
        return gl_unsigned_short;
    case 4:
        return gl_unsigned_int;
    default:
        return 0U;
    }
}

std::uint32_t primitive_mode_for_fill_count(std::uint32_t vertex_or_index_count) noexcept
{
    if (vertex_or_index_count == 1U) {
        return gl_points;
    }

    if (vertex_or_index_count == 2U) {
        return gl_lines;
    }

    return gl_triangles;
}

std::uint32_t primitive_mode_for_stroke_count(std::uint32_t vertex_or_index_count) noexcept
{
    if (vertex_or_index_count == 1U) {
        return gl_points;
    }

    return gl_lines;
}

std::uint32_t array_buffer_target() noexcept
{
    return gl_array_buffer;
}

std::uint32_t element_array_buffer_target() noexcept
{
    return gl_element_array_buffer;
}

std::uint32_t static_draw_buffer_usage() noexcept
{
    return gl_static_draw;
}

std::uint32_t vertex_attribute_float_component_type() noexcept
{
    return gl_float;
}

std::uint32_t vertex_attribute_not_normalized() noexcept
{
    return gl_false;
}

std::uint32_t vertex_shader_stage() noexcept
{
    return gl_vertex_shader;
}

std::uint32_t fragment_shader_stage() noexcept
{
    return gl_fragment_shader;
}

std::uint32_t shader_compile_status_query() noexcept
{
    return gl_compile_status;
}

std::uint32_t program_link_status_query() noexcept
{
    return gl_link_status;
}

std::uint32_t active_uniform_count_query() noexcept
{
    return gl_active_uniforms;
}

std::uint32_t matrix_uniform_not_transposed() noexcept
{
    return gl_false;
}

std::uint32_t debug_severity_notification() noexcept
{
    return gl_debug_severity_notification;
}

std::uint32_t debug_severity_low() noexcept
{
    return gl_debug_severity_low;
}

std::uint32_t debug_severity_medium() noexcept
{
    return gl_debug_severity_medium;
}

std::uint32_t debug_severity_high() noexcept
{
    return gl_debug_severity_high;
}

std::uint32_t debug_output_state() noexcept
{
    return gl_debug_output;
}

std::uint32_t debug_output_synchronous_state() noexcept
{
    return gl_debug_output_synchronous;
}

std::uint32_t version_string_name() noexcept
{
    return gl_version;
}

std::uint32_t vendor_string_name() noexcept
{
    return gl_vendor;
}

std::uint32_t renderer_string_name() noexcept
{
    return gl_renderer;
}

std::uint32_t shading_language_version_string_name() noexcept
{
    return gl_shading_language_version;
}

std::uint32_t viewport_query() noexcept
{
    return gl_viewport;
}

OpenGlViewportRect viewport_for_renderer_viewport(pp::renderer::Viewport viewport) noexcept
{
    return OpenGlViewportRect {
        .x = viewport.x,
        .y = viewport.y,
        .width = static_cast<std::int32_t>(viewport.width),
        .height = static_cast<std::int32_t>(viewport.height),
        .min_depth = viewport.min_depth,
        .max_depth = viewport.max_depth,
    };
}

std::uint32_t color_clear_value_query() noexcept
{
    return gl_color_clear_value;
}

std::uint32_t current_program_query() noexcept
{
    return gl_current_program;
}

std::uint32_t active_texture_query() noexcept
{
    return gl_active_texture;
}

std::uint32_t texture_binding_2d_query() noexcept
{
    return gl_texture_binding_2d;
}

std::uint32_t texture_binding_cube_map_query() noexcept
{
    return gl_texture_binding_cube_map;
}

std::uint32_t sampler_binding_query() noexcept
{
    return gl_sampler_binding;
}

std::uint32_t blend_state() noexcept
{
    return gl_blend;
}

std::uint32_t depth_test_state() noexcept
{
    return gl_depth_test;
}

OpenGlEnumMapping compare_function_for_renderer_compare_op(pp::renderer::CompareOp op) noexcept
{
    switch (op) {
    case pp::renderer::CompareOp::never:
        return OpenGlEnumMapping { .value = gl_never, .supported = true };
    case pp::renderer::CompareOp::less:
        return OpenGlEnumMapping { .value = gl_less, .supported = true };
    case pp::renderer::CompareOp::equal:
        return OpenGlEnumMapping { .value = gl_equal, .supported = true };
    case pp::renderer::CompareOp::less_or_equal:
        return OpenGlEnumMapping { .value = gl_lequal, .supported = true };
    case pp::renderer::CompareOp::greater:
        return OpenGlEnumMapping { .value = gl_greater, .supported = true };
    case pp::renderer::CompareOp::not_equal:
        return OpenGlEnumMapping { .value = gl_notequal, .supported = true };
    case pp::renderer::CompareOp::greater_or_equal:
        return OpenGlEnumMapping { .value = gl_gequal, .supported = true };
    case pp::renderer::CompareOp::always:
        return OpenGlEnumMapping { .value = gl_always, .supported = true };
    default:
        return OpenGlEnumMapping {};
    }
}

OpenGlDepthState depth_state_for_renderer_depth_state(pp::renderer::DepthState state) noexcept
{
    const auto compare_function = compare_function_for_renderer_compare_op(state.compare);
    return OpenGlDepthState {
        .test_enabled = state.test_enabled ? gl_boolean_true : gl_boolean_false,
        .write_enabled = state.write_enabled ? gl_boolean_true : gl_boolean_false,
        .compare_function = compare_function.value,
        .supported = compare_function.supported,
    };
}

std::uint32_t scissor_test_state() noexcept
{
    return gl_scissor_test;
}

OpenGlScissorRect scissor_rect_for_renderer_scissor(pp::renderer::ScissorRect scissor) noexcept
{
    return OpenGlScissorRect {
        .enabled = scissor.enabled ? gl_boolean_true : gl_boolean_false,
        .x = scissor.x,
        .y = scissor.y,
        .width = static_cast<std::int32_t>(scissor.width),
        .height = static_cast<std::int32_t>(scissor.height),
    };
}

std::uint32_t program_point_size_state() noexcept
{
    return gl_program_point_size;
}

std::uint32_t line_smooth_state() noexcept
{
    return gl_line_smooth;
}

std::uint32_t source_alpha_blend_factor() noexcept
{
    return gl_src_alpha;
}

std::uint32_t one_minus_source_alpha_blend_factor() noexcept
{
    return gl_one_minus_src_alpha;
}

std::uint32_t add_blend_equation() noexcept
{
    return gl_func_add;
}

std::uint32_t max_blend_equation() noexcept
{
    return gl_max;
}

OpenGlEnumMapping blend_factor_for_renderer_factor(pp::renderer::BlendFactor factor) noexcept
{
    switch (factor) {
    case pp::renderer::BlendFactor::zero:
        return OpenGlEnumMapping { .value = gl_zero, .supported = true };
    case pp::renderer::BlendFactor::one:
        return OpenGlEnumMapping { .value = gl_one, .supported = true };
    case pp::renderer::BlendFactor::source_alpha:
        return OpenGlEnumMapping { .value = gl_src_alpha, .supported = true };
    case pp::renderer::BlendFactor::one_minus_source_alpha:
        return OpenGlEnumMapping { .value = gl_one_minus_src_alpha, .supported = true };
    case pp::renderer::BlendFactor::destination_alpha:
        return OpenGlEnumMapping { .value = gl_dst_alpha, .supported = true };
    case pp::renderer::BlendFactor::one_minus_destination_alpha:
        return OpenGlEnumMapping { .value = gl_one_minus_dst_alpha, .supported = true };
    default:
        return OpenGlEnumMapping {};
    }
}

OpenGlEnumMapping blend_equation_for_renderer_op(pp::renderer::BlendOp op) noexcept
{
    switch (op) {
    case pp::renderer::BlendOp::add:
        return OpenGlEnumMapping { .value = gl_func_add, .supported = true };
    case pp::renderer::BlendOp::subtract:
        return OpenGlEnumMapping { .value = gl_func_subtract, .supported = true };
    case pp::renderer::BlendOp::reverse_subtract:
        return OpenGlEnumMapping { .value = gl_func_reverse_subtract, .supported = true };
    default:
        return OpenGlEnumMapping {};
    }
}

OpenGlBlendState blend_state_for_renderer_blend_state(pp::renderer::BlendState state) noexcept
{
    const auto source_color = blend_factor_for_renderer_factor(state.source_color);
    const auto destination_color = blend_factor_for_renderer_factor(state.destination_color);
    const auto color_op = blend_equation_for_renderer_op(state.color_op);
    const auto source_alpha = blend_factor_for_renderer_factor(state.source_alpha);
    const auto destination_alpha = blend_factor_for_renderer_factor(state.destination_alpha);
    const auto alpha_op = blend_equation_for_renderer_op(state.alpha_op);

    return OpenGlBlendState {
        .enabled = state.enabled ? gl_boolean_true : gl_boolean_false,
        .source_color_factor = source_color.value,
        .destination_color_factor = destination_color.value,
        .color_equation = color_op.value,
        .source_alpha_factor = source_alpha.value,
        .destination_alpha_factor = destination_alpha.value,
        .alpha_equation = alpha_op.value,
        .color_write_mask = color_write_mask_for_renderer_blend_state(state),
        .supported = source_color.supported
            && destination_color.supported
            && color_op.supported
            && source_alpha.supported
            && destination_alpha.supported
            && alpha_op.supported,
    };
}

std::uint32_t rgba8_internal_format() noexcept
{
    return gl_rgba8;
}

std::uint32_t rgba16f_internal_format() noexcept
{
    return gl_rgba16f;
}

std::uint32_t rgba32f_internal_format() noexcept
{
    return gl_rgba32f;
}

std::uint8_t color_write_disabled() noexcept
{
    return gl_boolean_false;
}

std::uint8_t color_write_enabled() noexcept
{
    return gl_boolean_true;
}

OpenGlColorWriteMask color_write_mask_for_renderer_blend_state(pp::renderer::BlendState state) noexcept
{
    return OpenGlColorWriteMask {
        .r = state.write_r ? gl_boolean_true : gl_boolean_false,
        .g = state.write_g ? gl_boolean_true : gl_boolean_false,
        .b = state.write_b ? gl_boolean_true : gl_boolean_false,
        .a = state.write_a ? gl_boolean_true : gl_boolean_false,
    };
}

std::uint32_t linear_texture_filter() noexcept
{
    return gl_linear;
}

std::uint32_t linear_mipmap_linear_texture_filter() noexcept
{
    return gl_linear_mipmap_linear;
}

std::uint32_t nearest_texture_filter() noexcept
{
    return gl_nearest;
}

std::uint32_t repeat_texture_wrap() noexcept
{
    return gl_repeat;
}

std::uint32_t clamp_to_edge_texture_wrap() noexcept
{
    return gl_clamp_to_edge;
}

std::uint32_t clamp_to_border_texture_wrap() noexcept
{
    return gl_clamp_to_border;
}

OpenGlEnumMapping sampler_filter_for_renderer_filter(pp::renderer::SamplerFilter filter) noexcept
{
    switch (filter) {
    case pp::renderer::SamplerFilter::nearest:
        return OpenGlEnumMapping { .value = gl_nearest, .supported = true };
    case pp::renderer::SamplerFilter::linear:
        return OpenGlEnumMapping { .value = gl_linear, .supported = true };
    default:
        return OpenGlEnumMapping {};
    }
}

OpenGlEnumMapping sampler_min_filter_for_renderer_filters(
    pp::renderer::SamplerFilter min_filter,
    pp::renderer::SamplerFilter mip_filter) noexcept
{
    switch (min_filter) {
    case pp::renderer::SamplerFilter::nearest:
        switch (mip_filter) {
        case pp::renderer::SamplerFilter::nearest:
            return OpenGlEnumMapping { .value = gl_nearest_mipmap_nearest, .supported = true };
        case pp::renderer::SamplerFilter::linear:
            return OpenGlEnumMapping { .value = gl_nearest_mipmap_linear, .supported = true };
        default:
            return OpenGlEnumMapping {};
        }
    case pp::renderer::SamplerFilter::linear:
        switch (mip_filter) {
        case pp::renderer::SamplerFilter::nearest:
            return OpenGlEnumMapping { .value = gl_linear_mipmap_nearest, .supported = true };
        case pp::renderer::SamplerFilter::linear:
            return OpenGlEnumMapping { .value = gl_linear_mipmap_linear, .supported = true };
        default:
            return OpenGlEnumMapping {};
        }
    default:
        return OpenGlEnumMapping {};
    }
}

OpenGlEnumMapping sampler_address_mode_for_renderer_mode(pp::renderer::SamplerAddressMode mode) noexcept
{
    switch (mode) {
    case pp::renderer::SamplerAddressMode::clamp_to_edge:
        return OpenGlEnumMapping { .value = gl_clamp_to_edge, .supported = true };
    case pp::renderer::SamplerAddressMode::repeat:
        return OpenGlEnumMapping { .value = gl_repeat, .supported = true };
    case pp::renderer::SamplerAddressMode::mirrored_repeat:
        return OpenGlEnumMapping { .value = gl_mirrored_repeat, .supported = true };
    case pp::renderer::SamplerAddressMode::clamp_to_border:
        return OpenGlEnumMapping { .value = gl_clamp_to_border, .supported = true };
    default:
        return OpenGlEnumMapping {};
    }
}

OpenGlSamplerState sampler_state_for_renderer_sampler_desc(pp::renderer::SamplerDesc desc) noexcept
{
    const auto min_filter = sampler_min_filter_for_renderer_filters(desc.min_filter, desc.mip_filter);
    const auto mag_filter = sampler_filter_for_renderer_filter(desc.mag_filter);
    const auto address_u = sampler_address_mode_for_renderer_mode(desc.address_u);
    const auto address_v = sampler_address_mode_for_renderer_mode(desc.address_v);
    const auto address_w = sampler_address_mode_for_renderer_mode(desc.address_w);

    return OpenGlSamplerState {
        .min_filter = min_filter.value,
        .mag_filter = mag_filter.value,
        .wrap_s = address_u.value,
        .wrap_t = address_v.value,
        .wrap_r = address_w.value,
        .supported = min_filter.supported
            && mag_filter.supported
            && address_u.supported
            && address_v.supported
            && address_w.supported,
    };
}

std::uint32_t active_texture_unit(std::uint32_t unit_index) noexcept
{
    return gl_texture0 + unit_index;
}

std::uint32_t texture_cube_map_target() noexcept
{
    return gl_texture_cube_map;
}

std::uint32_t cube_map_allocation_face_texture_target(std::uint32_t face_index) noexcept
{
    if (face_index >= 6U) {
        return 0U;
    }

    return gl_texture_cube_map_positive_x + face_index;
}

std::span<const std::uint32_t> panopainter_cube_face_texture_targets() noexcept
{
    static constexpr std::array<std::uint32_t, 6> targets {
        gl_texture_cube_map_negative_z, // front
        gl_texture_cube_map_negative_x, // right
        gl_texture_cube_map_positive_z, // back
        gl_texture_cube_map_positive_x, // left
        gl_texture_cube_map_negative_y, // top
        gl_texture_cube_map_positive_y, // bottom
    };

    return targets;
}

std::uint32_t cube_face_texture_target(std::uint32_t face_index) noexcept
{
    const auto targets = panopainter_cube_face_texture_targets();
    if (face_index >= targets.size()) {
        return 0U;
    }

    return targets[face_index];
}

std::span<const OpenGlTextureParameter> default_render_target_texture_parameters() noexcept
{
    static constexpr std::array<OpenGlTextureParameter, 4> parameters {
        OpenGlTextureParameter { .name = gl_texture_mag_filter, .value = gl_linear },
        OpenGlTextureParameter { .name = gl_texture_min_filter, .value = gl_linear },
        OpenGlTextureParameter { .name = gl_texture_wrap_s, .value = gl_clamp_to_edge },
        OpenGlTextureParameter { .name = gl_texture_wrap_t, .value = gl_clamp_to_edge },
    };

    return parameters;
}

std::array<OpenGlTextureParameter, 5> sampler_parameters_for_filter_wrap(
    std::uint32_t filter,
    std::uint32_t wrap) noexcept
{
    return {
        OpenGlTextureParameter { .name = gl_texture_wrap_s, .value = wrap },
        OpenGlTextureParameter { .name = gl_texture_wrap_t, .value = wrap },
        OpenGlTextureParameter { .name = gl_texture_wrap_r, .value = wrap },
        OpenGlTextureParameter { .name = gl_texture_min_filter, .value = filter },
        OpenGlTextureParameter { .name = gl_texture_mag_filter, .value = filter },
    };
}

std::array<OpenGlTextureParameter, 2> sampler_filter_parameters(
    std::uint32_t filter_min,
    std::uint32_t filter_mag) noexcept
{
    return {
        OpenGlTextureParameter { .name = gl_texture_min_filter, .value = filter_min },
        OpenGlTextureParameter { .name = gl_texture_mag_filter, .value = filter_mag },
    };
}

std::uint32_t sampler_border_color_parameter_name() noexcept
{
    return gl_texture_border_color;
}

OpenGlWindowsWglContextConfig windows_wgl_core_context_3_3_config() noexcept
{
    return {
        .context_attributes = {
            wgl_context_major_version_arb, 3,
            wgl_context_minor_version_arb, 3,
            wgl_context_flags_arb, wgl_context_forward_compatible_bit_arb,
            wgl_context_profile_mask_arb, wgl_context_core_profile_bit_arb,
            0,
        },
        .pixel_format_attributes = {
            wgl_draw_to_window_arb, static_cast<std::int32_t>(gl_boolean_true),
            wgl_support_opengl_arb, static_cast<std::int32_t>(gl_boolean_true),
            wgl_double_buffer_arb, static_cast<std::int32_t>(gl_boolean_true),
            wgl_acceleration_arb, wgl_full_acceleration_arb,
            wgl_pixel_type_arb, wgl_type_rgba_arb,
            wgl_color_bits_arb, 24,
            wgl_depth_bits_arb, 16,
            0,
        },
    };
}

}
