Update auto-generated opengl loaders & use shader prelude in gfx effects

This commit is contained in:
Nikita Lisitsa 2026-07-05 13:36:59 +03:00
parent b989eba66d
commit dd07244a40
16 changed files with 99 additions and 87 deletions

View file

@ -147,6 +147,10 @@ typedef unsigned short GLhalfNV;
typedef GLintptr GLvdpauSurfaceNV; typedef GLintptr GLvdpauSurfaceNV;
typedef void (GLVULKANPROCNV)(void); typedef void (GLVULKANPROCNV)(void);
#include <unordered_set>
#include <string>
#include <string_view>
namespace gl namespace gl
{ {
@ -1865,14 +1869,13 @@ namespace gl
const char * api(); const char * api();
int major_version(); int major_version();
int minor_version(); int minor_version();
std::unordered_set<std::string> const & extensions();
std::string_view shader_prelude();
bool has_extension(const char * name);
bool ext_ARB_compute_shader(); bool ext_ARB_compute_shader();
bool ext_ARB_shader_image_load_store(); bool ext_ARB_shader_image_load_store();
bool ext_ARB_texture_filter_anisotropic(); bool ext_ARB_texture_filter_anisotropic();
const char * shader_prefix();
} // namespace sys } // namespace sys
} // namespace gl } // namespace gl

View file

@ -11,6 +11,8 @@
#include <stdio.h> #include <stdio.h>
#elif defined(_WIN32) #elif defined(_WIN32)
#include <windows.h> #include <windows.h>
#elif defined(__ANDROID__) || defined(__EMSCRIPTEN__)
#include <EGL/egl.h>
#else #else
#include <GL/glx.h> #include <GL/glx.h>
#endif #endif
@ -78,6 +80,13 @@ namespace gl
return reinterpret_cast<void*>(GetProcAddress(image, reinterpret_cast<LPCSTR>(name))); return reinterpret_cast<void*>(GetProcAddress(image, reinterpret_cast<LPCSTR>(name)));
} }
#elif defined(__ANDROID__) || defined(__EMSCRIPTEN__)
static void * get_proc_address(const char *func)
{
return reinterpret_cast<void *>(eglGetProcAddress(func));
}
#else // GLX #else // GLX
static void * get_proc_address(const char *func) static void * get_proc_address(const char *func)
@ -1244,7 +1253,7 @@ namespace gl
return true; return true;
} }
static std::unordered_set<std::string> extensions; static std::unordered_set<std::string> extensions_set;
bool initialize() bool initialize()
{ {
if (!load_core()) return false; if (!load_core()) return false;
@ -1252,13 +1261,13 @@ namespace gl
GLint num_extensions; GLint num_extensions;
internal::glGetIntegerv(0x821D, &num_extensions); internal::glGetIntegerv(0x821D, &num_extensions);
for (GLint i = 0; i < num_extensions; ++i) for (GLint i = 0; i < num_extensions; ++i)
extensions.insert(reinterpret_cast<const char *>(internal::glGetStringi(0x1F03, i))); extensions_set.insert(reinterpret_cast<const char *>(internal::glGetStringi(0x1F03, i)));
if (extensions.count("GL_ARB_compute_shader") > 0) if (extensions_set.count("GL_ARB_compute_shader") > 0)
ext_GL_ARB_compute_shader_loaded = load_ext_GL_ARB_compute_shader(); ext_GL_ARB_compute_shader_loaded = load_ext_GL_ARB_compute_shader();
if (extensions.count("GL_ARB_shader_image_load_store") > 0) if (extensions_set.count("GL_ARB_shader_image_load_store") > 0)
ext_GL_ARB_shader_image_load_store_loaded = load_ext_GL_ARB_shader_image_load_store(); ext_GL_ARB_shader_image_load_store_loaded = load_ext_GL_ARB_shader_image_load_store();
if (extensions.count("GL_ARB_texture_filter_anisotropic") > 0) if (extensions_set.count("GL_ARB_texture_filter_anisotropic") > 0)
ext_GL_ARB_texture_filter_anisotropic_loaded = load_ext_GL_ARB_texture_filter_anisotropic(); ext_GL_ARB_texture_filter_anisotropic_loaded = load_ext_GL_ARB_texture_filter_anisotropic();
return true; return true;
@ -1270,19 +1279,16 @@ namespace gl
int minor_version(){ return 3; } int minor_version(){ return 3; }
bool has_extension(const char * name){ return extensions.contains(name); } std::unordered_set<std::string> const & extensions(){ return extensions_set; }
std::string_view shader_prelude(){ return R"(#version 330 core
)";
}
bool ext_ARB_compute_shader(){ return ext_GL_ARB_compute_shader_loaded; } bool ext_ARB_compute_shader(){ return ext_GL_ARB_compute_shader_loaded; }
bool ext_ARB_shader_image_load_store(){ return ext_GL_ARB_shader_image_load_store_loaded; } bool ext_ARB_shader_image_load_store(){ return ext_GL_ARB_shader_image_load_store_loaded; }
bool ext_ARB_texture_filter_anisotropic(){ return ext_GL_ARB_texture_filter_anisotropic_loaded; } bool ext_ARB_texture_filter_anisotropic(){ return ext_GL_ARB_texture_filter_anisotropic_loaded; }
const char * shader_prefix()
{
return R"(#version 330 core
)";
}
} // namespace sys } // namespace sys
} // namespace gl } // namespace gl

View file

@ -147,6 +147,10 @@ typedef unsigned short GLhalfNV;
typedef GLintptr GLvdpauSurfaceNV; typedef GLintptr GLvdpauSurfaceNV;
typedef void (GLVULKANPROCNV)(void); typedef void (GLVULKANPROCNV)(void);
#include <unordered_set>
#include <string>
#include <string_view>
namespace gl namespace gl
{ {
@ -1288,9 +1292,13 @@ namespace gl
// GL_EXT_texture_filter_anisotropic // GL_EXT_texture_filter_anisotropic
constexpr GLenum TEXTURE_MAX_ANISOTROPY = 0x84FE; constexpr GLenum TEXTURE_MAX_ANISOTROPY_EXT = 0x84FE;
constexpr GLenum MAX_TEXTURE_MAX_ANISOTROPY = 0x84FF; constexpr GLenum MAX_TEXTURE_MAX_ANISOTROPY_EXT = 0x84FF;
// Manually added compatibility constants
constexpr GLenum TEXTURE_MAX_ANISOTROPY = TEXTURE_MAX_ANISOTROPY_EXT;
constexpr GLenum MAX_TEXTURE_MAX_ANISOTROPY = MAX_TEXTURE_MAX_ANISOTROPY_EXT;
namespace sys namespace sys
{ {
@ -1299,12 +1307,11 @@ namespace gl
const char * api(); const char * api();
int major_version(); int major_version();
int minor_version(); int minor_version();
std::unordered_set<std::string> const & extensions();
std::string_view shader_prelude();
bool has_extension(const char * name);
bool ext_EXT_texture_filter_anisotropic(); bool ext_EXT_texture_filter_anisotropic();
const char * shader_prefix();
} // namespace sys } // namespace sys
} // namespace gl } // namespace gl

View file

@ -11,9 +11,8 @@
#include <stdio.h> #include <stdio.h>
#elif defined(_WIN32) #elif defined(_WIN32)
#include <windows.h> #include <windows.h>
#elif defined(__EMSCRIPTEN__) #elif defined(__ANDROID__) || defined(__EMSCRIPTEN__)
#include <EGL/egl.h> #include <EGL/egl.h>
#include <GLES3/gl3.h>
#else #else
#include <GL/glx.h> #include <GL/glx.h>
#endif #endif
@ -81,7 +80,7 @@ namespace gl
return reinterpret_cast<void*>(GetProcAddress(image, reinterpret_cast<LPCSTR>(name))); return reinterpret_cast<void*>(GetProcAddress(image, reinterpret_cast<LPCSTR>(name)));
} }
#elif defined(__EMSCRIPTEN__) #elif defined(__ANDROID__) || defined(__EMSCRIPTEN__)
static void * get_proc_address(const char *func) static void * get_proc_address(const char *func)
{ {
@ -868,7 +867,7 @@ namespace gl
return true; return true;
} }
static std::unordered_set<std::string> extensions; static std::unordered_set<std::string> extensions_set;
bool initialize() bool initialize()
{ {
if (!load_core()) return false; if (!load_core()) return false;
@ -876,9 +875,9 @@ namespace gl
GLint num_extensions; GLint num_extensions;
internal::glGetIntegerv(0x821D, &num_extensions); internal::glGetIntegerv(0x821D, &num_extensions);
for (GLint i = 0; i < num_extensions; ++i) for (GLint i = 0; i < num_extensions; ++i)
extensions.insert(reinterpret_cast<const char *>(internal::glGetStringi(0x1F03, i))); extensions_set.insert(reinterpret_cast<const char *>(internal::glGetStringi(0x1F03, i)));
if (extensions.count("GL_EXT_texture_filter_anisotropic") > 0) if (extensions_set.count("GL_EXT_texture_filter_anisotropic") > 0)
ext_GL_EXT_texture_filter_anisotropic_loaded = load_ext_GL_EXT_texture_filter_anisotropic(); ext_GL_EXT_texture_filter_anisotropic_loaded = load_ext_GL_EXT_texture_filter_anisotropic();
return true; return true;
@ -890,22 +889,19 @@ namespace gl
int minor_version(){ return 0; } int minor_version(){ return 0; }
bool has_extension(const char * name){ return extensions.contains(name); } std::unordered_set<std::string> const & extensions(){ return extensions_set; }
bool ext_EXT_texture_filter_anisotropic(){ return ext_GL_EXT_texture_filter_anisotropic_loaded; } std::string_view shader_prelude(){ return R"(#version 300 es
const char * shader_prefix()
{
return R"(#version 300 es
precision highp int; precision highp int;
precision highp float; precision highp float;
precision highp sampler2D; precision highp sampler2D;
precision highp usampler2D; precision highp usampler2D;
)"; )";
} }
bool ext_EXT_texture_filter_anisotropic(){ return ext_GL_EXT_texture_filter_anisotropic_loaded; }
} // namespace sys } // namespace sys
} // namespace gl } // namespace gl

View file

@ -147,6 +147,10 @@ typedef unsigned short GLhalfNV;
typedef GLintptr GLvdpauSurfaceNV; typedef GLintptr GLvdpauSurfaceNV;
typedef void (GLVULKANPROCNV)(void); typedef void (GLVULKANPROCNV)(void);
#include <unordered_set>
#include <string>
#include <string_view>
namespace gl namespace gl
{ {
@ -1949,17 +1953,14 @@ namespace gl
const char * api(); const char * api();
int major_version(); int major_version();
int minor_version(); int minor_version();
std::unordered_set<std::string> const & extensions();
std::string_view shader_prelude();
bool has_extension(const char * name);
bool ext_ARB_compute_shader(); bool ext_ARB_compute_shader();
bool ext_ARB_shader_image_load_store(); bool ext_ARB_shader_image_load_store();
bool ext_ARB_texture_filter_anisotropic(); bool ext_ARB_texture_filter_anisotropic();
const char * shader_prefix();
} // namespace sys } // namespace sys
} // namespace gl } // namespace gl
#define PSEMEK_GLES 1

View file

@ -1235,7 +1235,7 @@ namespace gl
return true; return true;
} }
static std::unordered_set<std::string> extensions; static std::unordered_set<std::string> extensions_set;
bool initialize() bool initialize()
{ {
if (!load_core()) return false; if (!load_core()) return false;
@ -1243,13 +1243,13 @@ namespace gl
GLint num_extensions; GLint num_extensions;
internal::glGetIntegerv(0x821D, &num_extensions); internal::glGetIntegerv(0x821D, &num_extensions);
for (GLint i = 0; i < num_extensions; ++i) for (GLint i = 0; i < num_extensions; ++i)
extensions.insert(reinterpret_cast<const char *>(internal::glGetStringi(0x1F03, i))); extensions_set.insert(reinterpret_cast<const char *>(internal::glGetStringi(0x1F03, i)));
if (extensions.count("GL_ARB_compute_shader") > 0) if (extensions_set.count("GL_ARB_compute_shader") > 0)
ext_GL_ARB_compute_shader_loaded = load_ext_GL_ARB_compute_shader(); ext_GL_ARB_compute_shader_loaded = load_ext_GL_ARB_compute_shader();
if (extensions.count("GL_ARB_shader_image_load_store") > 0) if (extensions_set.count("GL_ARB_shader_image_load_store") > 0)
ext_GL_ARB_shader_image_load_store_loaded = load_ext_GL_ARB_shader_image_load_store(); ext_GL_ARB_shader_image_load_store_loaded = load_ext_GL_ARB_shader_image_load_store();
if (extensions.count("GL_ARB_texture_filter_anisotropic") > 0) if (extensions_set.count("GL_ARB_texture_filter_anisotropic") > 0)
ext_GL_ARB_texture_filter_anisotropic_loaded = load_ext_GL_ARB_texture_filter_anisotropic(); ext_GL_ARB_texture_filter_anisotropic_loaded = load_ext_GL_ARB_texture_filter_anisotropic();
return true; return true;
@ -1261,24 +1261,21 @@ namespace gl
int minor_version(){ return 2; } int minor_version(){ return 2; }
bool has_extension(const char * name){ return extensions.contains(name); } std::unordered_set<std::string> const & extensions(){ return extensions_set; }
bool ext_ARB_compute_shader(){ return ext_GL_ARB_compute_shader_loaded; } std::string_view shader_prelude(){ return R"(#version 320 es
bool ext_ARB_shader_image_load_store(){ return ext_GL_ARB_shader_image_load_store_loaded; }
bool ext_ARB_texture_filter_anisotropic(){ return ext_GL_ARB_texture_filter_anisotropic_loaded; }
const char * shader_prefix()
{
return R"(#version 320 es
precision highp int; precision highp int;
precision highp float; precision highp float;
precision highp sampler2D; precision highp sampler2D;
precision highp usampler2D; precision highp usampler2D;
)"; )";
} }
bool ext_ARB_compute_shader(){ return ext_GL_ARB_compute_shader_loaded; }
bool ext_ARB_shader_image_load_store(){ return ext_GL_ARB_shader_image_load_store_loaded; }
bool ext_ARB_texture_filter_anisotropic(){ return ext_GL_ARB_texture_filter_anisotropic_loaded; }
} // namespace sys } // namespace sys
} // namespace gl } // namespace gl

View file

@ -13,9 +13,10 @@
"loader_namespace": "sys", "loader_namespace": "sys",
"internal_namespace": "internal", "internal_namespace": "internal",
"internal_prefix": "", "internal_prefix": "",
"shader_prelude": true,
"undef": 4, "undef": 4,
"strip": true, "strip": true,
"out_header": "gl.hpp", "out_header": "api/gl33/include/psemek/gfx/gl.hpp",
"out_source": "gl.cpp", "out_source": "api/gl33/source/gl.cpp",
"include": "<psemek/gfx/gl.hpp>" "include": "<psemek/gfx/gl.hpp>"
} }

View file

@ -1,6 +1,6 @@
{ {
"spec_file": "gl.xml", "spec_file": "gl.xml",
"api": "gles2", "api": "gles3",
"version": "3.0", "version": "3.0",
"profile": "core", "profile": "core",
"extensions": [ "extensions": [
@ -11,9 +11,10 @@
"loader_namespace": "sys", "loader_namespace": "sys",
"internal_namespace": "internal", "internal_namespace": "internal",
"internal_prefix": "", "internal_prefix": "",
"shader_prelude": true,
"undef": 4, "undef": 4,
"strip": true, "strip": true,
"out_header": "gl.hpp", "out_header": "api/gles30-emscripten/include/psemek/gfx/gl.hpp",
"out_source": "gl.cpp", "out_source": "api/gles30-emscripten/source/gl.cpp",
"include": "<psemek/gfx/gl.hpp>" "include": "<psemek/gfx/gl.hpp>"
} }

View file

@ -1,6 +1,6 @@
{ {
"spec_file": "gl.xml", "spec_file": "gl.xml",
"api": "gles2", "api": "gles3",
"version": "3.2", "version": "3.2",
"profile": "core", "profile": "core",
"extensions": [ "extensions": [
@ -13,9 +13,10 @@
"loader_namespace": "sys", "loader_namespace": "sys",
"internal_namespace": "internal", "internal_namespace": "internal",
"internal_prefix": "", "internal_prefix": "",
"shader_prelude": true,
"undef": 4, "undef": 4,
"strip": true, "strip": true,
"out_header": "gl.hpp", "out_header": "api/gles32/include/psemek/gfx/gl.hpp",
"out_source": "gl.cpp", "out_source": "api/gles32/source/gl.cpp",
"include": "<psemek/gfx/gl.hpp>" "include": "<psemek/gfx/gl.hpp>"
} }

View file

@ -60,8 +60,11 @@ namespace psemek::gfx
, part(part) , part(part)
{} {}
#ifndef PSEMEK_GLES
using texture_view_1d = basic_texture_view<1, gl::TEXTURE_1D>; using texture_view_1d = basic_texture_view<1, gl::TEXTURE_1D>;
using texture_view_1d_array = basic_texture_view<2, gl::TEXTURE_1D_ARRAY>; using texture_view_1d_array = basic_texture_view<2, gl::TEXTURE_1D_ARRAY>;
#endif
using texture_view_2d = basic_texture_view<2, gl::TEXTURE_2D>; using texture_view_2d = basic_texture_view<2, gl::TEXTURE_2D>;
using texture_view_2d_array = basic_texture_view<3, gl::TEXTURE_2D_ARRAY>; using texture_view_2d_array = basic_texture_view<3, gl::TEXTURE_2D_ARRAY>;
using texture_view_3d = basic_texture_view<3, gl::TEXTURE_3D>; using texture_view_3d = basic_texture_view<3, gl::TEXTURE_3D>;

View file

@ -1,6 +1,7 @@
#include <psemek/gfx/effect/blur.hpp> #include <psemek/gfx/effect/blur.hpp>
#include <psemek/gfx/program.hpp> #include <psemek/gfx/program.hpp>
#include <psemek/gfx/array.hpp> #include <psemek/gfx/array.hpp>
#include <psemek/gfx/gl.hpp>
#include <psemek/util/to_string.hpp> #include <psemek/util/to_string.hpp>
@ -12,8 +13,7 @@ namespace psemek::gfx
{ {
static char const blur_vs[] = static char const blur_vs[] =
R"(#version 330 R"(
const vec4 vertices[6] = vec4[6]( const vec4 vertices[6] = vec4[6](
vec4(-1.0, -1.0, 0.0, 1.0), vec4(-1.0, -1.0, 0.0, 1.0),
vec4( 1.0, -1.0, 0.0, 1.0), vec4( 1.0, -1.0, 0.0, 1.0),
@ -34,8 +34,7 @@ void main()
)"; )";
static char const blur_fs_template[] = static char const blur_fs_template[] =
R"(#version 330 R"(
#define BLUR_SIZE @BLUR_SIZE@ #define BLUR_SIZE @BLUR_SIZE@
#define BLUR_COEFFS_COUNT 2 * BLUR_SIZE + 1 #define BLUR_COEFFS_COUNT 2 * BLUR_SIZE + 1
@ -118,7 +117,7 @@ void main()
gfx::array array; gfx::array array;
blur_impl(int size, float sigma, float gamma, bool horizontal) blur_impl(int size, float sigma, float gamma, bool horizontal)
: program(blur_vs, generate_blur_fs_source(size, sigma, horizontal)) : program(std::string(gl::sys::shader_prelude()) + blur_vs, std::string(gl::sys::shader_prelude()) + generate_blur_fs_source(size, sigma, horizontal))
{ {
program.bind(); program.bind();
program["u_texture"] = 0; program["u_texture"] = 0;

View file

@ -2,13 +2,13 @@
#include <psemek/gfx/program.hpp> #include <psemek/gfx/program.hpp>
#include <psemek/gfx/array.hpp> #include <psemek/gfx/array.hpp>
#include <psemek/gfx/error.hpp> #include <psemek/gfx/error.hpp>
#include <psemek/gfx/gl.hpp>
namespace psemek::gfx namespace psemek::gfx
{ {
static char const fxaa_vs[] = static char const fxaa_vs[] =
R"(#version 330 R"(
const vec4 vertices[6] = vec4[6]( const vec4 vertices[6] = vec4[6](
vec4(-1.0, -1.0, 0.0, 1.0), vec4(-1.0, -1.0, 0.0, 1.0),
vec4( 1.0, -1.0, 0.0, 1.0), vec4( 1.0, -1.0, 0.0, 1.0),
@ -34,8 +34,7 @@ void main()
// TODO: refactor // TODO: refactor
static char const fxaa_fs[] = static char const fxaa_fs[] =
R"(#version 330 R"(
uniform sampler2D u_input; uniform sampler2D u_input;
uniform vec2 u_d; uniform vec2 u_d;
@ -207,7 +206,7 @@ void main()
struct fxaa::impl struct fxaa::impl
{ {
gfx::program program{fxaa_vs, fxaa_fs}; gfx::program program{std::string(gl::sys::shader_prelude()) + fxaa_vs, std::string(gl::sys::shader_prelude()) + fxaa_fs};
gfx::array array; gfx::array array;
}; };

View file

@ -2,13 +2,13 @@
#include <psemek/gfx/program.hpp> #include <psemek/gfx/program.hpp>
#include <psemek/gfx/array.hpp> #include <psemek/gfx/array.hpp>
#include <psemek/gfx/error.hpp> #include <psemek/gfx/error.hpp>
#include <psemek/gfx/gl.hpp>
namespace psemek::gfx namespace psemek::gfx
{ {
static char const gamma_vs[] = static char const gamma_vs[] =
R"(#version 330 R"(
const vec4 vertices[6] = vec4[6]( const vec4 vertices[6] = vec4[6](
vec4(-1.0, -1.0, 0.0, 1.0), vec4(-1.0, -1.0, 0.0, 1.0),
vec4( 1.0, -1.0, 0.0, 1.0), vec4( 1.0, -1.0, 0.0, 1.0),
@ -30,8 +30,7 @@ void main()
)"; )";
static char const gamma_fs[] = static char const gamma_fs[] =
R"(#version 330 R"(
uniform float u_gamma; uniform float u_gamma;
uniform sampler2D u_input; uniform sampler2D u_input;
@ -49,7 +48,7 @@ void main()
struct gamma_correction::impl struct gamma_correction::impl
{ {
gfx::program program{gamma_vs, gamma_fs}; gfx::program program{std::string(gl::sys::shader_prelude()) + gamma_vs, std::string(gl::sys::shader_prelude()) + gamma_fs};
gfx::array array; gfx::array array;
}; };

View file

@ -1,6 +1,7 @@
#include <psemek/gfx/effect/overlay.hpp> #include <psemek/gfx/effect/overlay.hpp>
#include <psemek/gfx/program.hpp> #include <psemek/gfx/program.hpp>
#include <psemek/gfx/array.hpp> #include <psemek/gfx/array.hpp>
#include <psemek/gfx/gl.hpp>
#include <psemek/util/pimpl.hpp> #include <psemek/util/pimpl.hpp>
@ -8,8 +9,7 @@ namespace psemek::gfx
{ {
static char const overlay_vs[] = static char const overlay_vs[] =
R"(#version 330 R"(
const vec4 vertices[6] = vec4[6]( const vec4 vertices[6] = vec4[6](
vec4(-1.0, -1.0, 0.0, 1.0), vec4(-1.0, -1.0, 0.0, 1.0),
vec4( 1.0, -1.0, 0.0, 1.0), vec4( 1.0, -1.0, 0.0, 1.0),
@ -30,8 +30,7 @@ void main()
)"; )";
static char const overlay_fs[] = static char const overlay_fs[] =
R"(#version 330 R"(
uniform sampler2D u_texture; uniform sampler2D u_texture;
in vec2 texcoord; in vec2 texcoord;
@ -46,7 +45,7 @@ void main()
struct overlay::impl struct overlay::impl
{ {
gfx::program program{overlay_vs, overlay_fs}; gfx::program program{std::string(gl::sys::shader_prelude()) + overlay_vs, std::string(gl::sys::shader_prelude()) + overlay_fs};
gfx::array array; gfx::array array;
}; };

View file

@ -6,8 +6,8 @@ namespace psemek::gfx
std::optional<std::size_t> available_memory() std::optional<std::size_t> available_memory()
{ {
static bool const nvx = gl::sys::has_extension("GL_NVX_gpu_memory_info"); static bool const nvx = gl::sys::extensions().contains("GL_NVX_gpu_memory_info");
static bool const ati = gl::sys::has_extension("GL_ATI_meminfo"); static bool const ati = gl::sys::extensions().contains("GL_ATI_meminfo");
if (nvx) if (nvx)
{ {

View file

@ -107,7 +107,7 @@ void main()
namespace psemek::gfx namespace psemek::gfx
{ {
static std::string shader_prefix = gl::sys::shader_prefix(); static std::string shader_prefix = std::string{gl::sys::shader_prelude()};
struct painter::impl struct painter::impl
{ {