From fc326115fa154bc19f3f10d7c2c4e57710ef1e0d Mon Sep 17 00:00:00 2001 From: spl3g Date: Sun, 9 Nov 2025 20:43:50 +0300 Subject: Move to sokol (broke the wave screen) --- flake.nix | 8 +- nob.c | 4 +- src/clay_renderer_SDL3.c | 347 - src/clay_renderer_SDL3.h | 43 - src/custom_elements.c | 255 + src/custom_elements.h | 32 + src/main.c | 270 +- src/ui.c | 18 +- src/ui.h | 9 +- third-party/arena.h | 60 +- third-party/fontstash/fontstash.h | 1770 +++ third-party/fontstash/gl3corefontstash.h | 240 + third-party/fontstash/glfontstash.h | 146 + third-party/fontstash/stb_truetype.h | 4545 ++++++ third-party/sokol/sokol_app.h | 13242 +++++++++++++++++ third-party/sokol/sokol_clay.h | 611 + third-party/sokol/sokol_fontstash.h | 2289 +++ third-party/sokol/sokol_gfx.h | 22948 +++++++++++++++++++++++++++++ third-party/sokol/sokol_gl.h | 4821 ++++++ third-party/sokol/sokol_glue.h | 162 + third-party/sokol/sokol_log.h | 334 + 21 files changed, 51554 insertions(+), 600 deletions(-) delete mode 100644 src/clay_renderer_SDL3.c delete mode 100644 src/clay_renderer_SDL3.h create mode 100644 src/custom_elements.c create mode 100644 src/custom_elements.h create mode 100644 third-party/fontstash/fontstash.h create mode 100644 third-party/fontstash/gl3corefontstash.h create mode 100644 third-party/fontstash/glfontstash.h create mode 100644 third-party/fontstash/stb_truetype.h create mode 100644 third-party/sokol/sokol_app.h create mode 100644 third-party/sokol/sokol_clay.h create mode 100644 third-party/sokol/sokol_fontstash.h create mode 100644 third-party/sokol/sokol_gfx.h create mode 100644 third-party/sokol/sokol_gl.h create mode 100644 third-party/sokol/sokol_glue.h create mode 100644 third-party/sokol/sokol_log.h diff --git a/flake.nix b/flake.nix index 1ff7361..e103ae2 100644 --- a/flake.nix +++ b/flake.nix @@ -23,9 +23,7 @@ libs = with pkgs; [ alsa-lib - sdl3.dev - sdl3-ttf - sdl3-image + libGL.dev ]; in pkgs.mkShell { @@ -38,6 +36,10 @@ man-pages man-pages-posix + + xorg.libX11 + xorg.libXi + xorg.libXcursor ] ++ libs; shellHook = '' diff --git a/nob.c b/nob.c index 8ab8e14..1449a80 100644 --- a/nob.c +++ b/nob.c @@ -9,7 +9,7 @@ #define nob_cc_include_path(cmd, path) cmd_append(cmd, temp_sprintf("-I%s", path)) #define nob_cc_no_link(cmd) cmd_append(cmd, "-c") -#define nob_cc_my_flags(cmd) cmd_append(cmd, "-Wall", "-Wextra", "-ggdb") +#define nob_cc_my_flags(cmd) cmd_append(cmd, "-Wall", "-Wextra", "-Wno-unused-function", "-ggdb") typedef struct { Nob_String_View *items; @@ -92,7 +92,7 @@ int main(int argc, char **argv) { nob_cmd_append(&cmd, input_sb.items); } - nob_cmd_append(&cmd, "-lm", "-lasound", "-lSDL3", "-lSDL3_ttf"); + nob_cmd_append(&cmd, "-lm", "-lasound", "-lGL", "-lX11", "-lXcursor", "-lXi"); nob_cc_output(&cmd, BUILD_FOLDER "crynth"); if (!nob_cmd_run(&cmd)) diff --git a/src/clay_renderer_SDL3.c b/src/clay_renderer_SDL3.c deleted file mode 100644 index 7e13859..0000000 --- a/src/clay_renderer_SDL3.c +++ /dev/null @@ -1,347 +0,0 @@ -#include "clay_renderer_SDL3.h" -#include - -/* Global for convenience. Even in 4K this is enough for smooth curves (low radius or rect size coupled with - * no AA or low resolution might make it appear as jagged curves) */ -static int NUM_CIRCLE_SEGMENTS = 16; - -//all rendering is performed by a single SDL call, avoiding multiple RenderRect + plumbing choice for circles. -static void SDL_Clay_RenderFillRoundedRect(Clay_SDL3RendererData *rendererData, const SDL_FRect rect, const float cornerRadius, const Clay_Color _color) { - const SDL_FColor color = { _color.r/255, _color.g/255, _color.b/255, _color.a/255 }; - - int indexCount = 0, vertexCount = 0; - - const float minRadius = SDL_min(rect.w, rect.h) / 2.0f; - const float clampedRadius = SDL_min(cornerRadius, minRadius); - - const int numCircleSegments = SDL_max(NUM_CIRCLE_SEGMENTS, (int) clampedRadius * 0.5f); - - int totalVertices = 4 + (4 * (numCircleSegments * 2)) + 2*4; - int totalIndices = 6 + (4 * (numCircleSegments * 3)) + 6*4; - - SDL_Vertex vertices[totalVertices]; - int indices[totalIndices]; - - //define center rectangle - vertices[vertexCount++] = (SDL_Vertex){ {rect.x + clampedRadius, rect.y + clampedRadius}, color, {0, 0} }; //0 center TL - vertices[vertexCount++] = (SDL_Vertex){ {rect.x + rect.w - clampedRadius, rect.y + clampedRadius}, color, {1, 0} }; //1 center TR - vertices[vertexCount++] = (SDL_Vertex){ {rect.x + rect.w - clampedRadius, rect.y + rect.h - clampedRadius}, color, {1, 1} }; //2 center BR - vertices[vertexCount++] = (SDL_Vertex){ {rect.x + clampedRadius, rect.y + rect.h - clampedRadius}, color, {0, 1} }; //3 center BL - - indices[indexCount++] = 0; - indices[indexCount++] = 1; - indices[indexCount++] = 3; - indices[indexCount++] = 1; - indices[indexCount++] = 2; - indices[indexCount++] = 3; - - //define rounded corners as triangle fans - const float step = (SDL_PI_F/2) / numCircleSegments; - for (int i = 0; i < numCircleSegments; i++) { - const float angle1 = (float)i * step; - const float angle2 = ((float)i + 1.0f) * step; - - for (int j = 0; j < 4; j++) { // Iterate over four corners - float cx, cy, signX, signY; - - switch (j) { - case 0: cx = rect.x + clampedRadius; cy = rect.y + clampedRadius; signX = -1; signY = -1; break; // Top-left - case 1: cx = rect.x + rect.w - clampedRadius; cy = rect.y + clampedRadius; signX = 1; signY = -1; break; // Top-right - case 2: cx = rect.x + rect.w - clampedRadius; cy = rect.y + rect.h - clampedRadius; signX = 1; signY = 1; break; // Bottom-right - case 3: cx = rect.x + clampedRadius; cy = rect.y + rect.h - clampedRadius; signX = -1; signY = 1; break; // Bottom-left - default: return; - } - - vertices[vertexCount++] = (SDL_Vertex){ {cx + SDL_cosf(angle1) * clampedRadius * signX, cy + SDL_sinf(angle1) * clampedRadius * signY}, color, {0, 0} }; - vertices[vertexCount++] = (SDL_Vertex){ {cx + SDL_cosf(angle2) * clampedRadius * signX, cy + SDL_sinf(angle2) * clampedRadius * signY}, color, {0, 0} }; - - indices[indexCount++] = j; // Connect to corresponding central rectangle vertex - indices[indexCount++] = vertexCount - 2; - indices[indexCount++] = vertexCount - 1; - } - } - - //Define edge rectangles - // Top edge - vertices[vertexCount++] = (SDL_Vertex){ {rect.x + clampedRadius, rect.y}, color, {0, 0} }; //TL - vertices[vertexCount++] = (SDL_Vertex){ {rect.x + rect.w - clampedRadius, rect.y}, color, {1, 0} }; //TR - - indices[indexCount++] = 0; - indices[indexCount++] = vertexCount - 2; //TL - indices[indexCount++] = vertexCount - 1; //TR - indices[indexCount++] = 1; - indices[indexCount++] = 0; - indices[indexCount++] = vertexCount - 1; //TR - // Right edge - vertices[vertexCount++] = (SDL_Vertex){ {rect.x + rect.w, rect.y + clampedRadius}, color, {1, 0} }; //RT - vertices[vertexCount++] = (SDL_Vertex){ {rect.x + rect.w, rect.y + rect.h - clampedRadius}, color, {1, 1} }; //RB - - indices[indexCount++] = 1; - indices[indexCount++] = vertexCount - 2; //RT - indices[indexCount++] = vertexCount - 1; //RB - indices[indexCount++] = 2; - indices[indexCount++] = 1; - indices[indexCount++] = vertexCount - 1; //RB - // Bottom edge - vertices[vertexCount++] = (SDL_Vertex){ {rect.x + rect.w - clampedRadius, rect.y + rect.h}, color, {1, 1} }; //BR - vertices[vertexCount++] = (SDL_Vertex){ {rect.x + clampedRadius, rect.y + rect.h}, color, {0, 1} }; //BL - - indices[indexCount++] = 2; - indices[indexCount++] = vertexCount - 2; //BR - indices[indexCount++] = vertexCount - 1; //BL - indices[indexCount++] = 3; - indices[indexCount++] = 2; - indices[indexCount++] = vertexCount - 1; //BL - // Left edge - vertices[vertexCount++] = (SDL_Vertex){ {rect.x, rect.y + rect.h - clampedRadius}, color, {0, 1} }; //LB - vertices[vertexCount++] = (SDL_Vertex){ {rect.x, rect.y + clampedRadius}, color, {0, 0} }; //LT - - indices[indexCount++] = 3; - indices[indexCount++] = vertexCount - 2; //LB - indices[indexCount++] = vertexCount - 1; //LT - indices[indexCount++] = 0; - indices[indexCount++] = 3; - indices[indexCount++] = vertexCount - 1; //LT - - // Render everything - SDL_RenderGeometry(rendererData->renderer, NULL, vertices, vertexCount, indices, indexCount); -} - -static void SDL_Clay_RenderArc(Clay_SDL3RendererData *rendererData, const SDL_FPoint center, const float radius, const float startAngle, const float endAngle, const float thickness, const Clay_Color color) { - SDL_SetRenderDrawColor(rendererData->renderer, color.r, color.g, color.b, color.a); - - const float radStart = startAngle * (SDL_PI_F / 180.0f); - const float radEnd = endAngle * (SDL_PI_F / 180.0f); - - const int numCircleSegments = SDL_max(NUM_CIRCLE_SEGMENTS, (int)(radius * 1.5f)); //increase circle segments for larger circles, 1.5 is arbitrary. - - const float angleStep = (radEnd - radStart) / (float)numCircleSegments; - const float thicknessStep = 0.4f; //arbitrary value to avoid overlapping lines. Changing THICKNESS_STEP or numCircleSegments might cause artifacts. - - for (float t = thicknessStep; t < thickness - thicknessStep; t += thicknessStep) { - SDL_FPoint points[numCircleSegments + 1]; - const float clampedRadius = SDL_max(radius - t, 1.0f); - - for (int i = 0; i <= numCircleSegments; i++) { - const float angle = radStart + i * angleStep; - points[i] = (SDL_FPoint){ - SDL_roundf(center.x + SDL_cosf(angle) * clampedRadius), - SDL_roundf(center.y + SDL_sinf(angle) * clampedRadius) }; - } - SDL_RenderLines(rendererData->renderer, points, numCircleSegments + 1); - } -} - -void SDL_Clay_RenderCircle(SDL_Renderer *renderer, float x, float y, float width, float height, float start_angle, float end_angle, const Clay_Color _color) { - const SDL_FColor color = { _color.r/255, _color.g/255, _color.b/255, _color.a/255 }; - float center_x = x + width / 2; - float center_y = y + width / 2; - float min_diameter = width > height ? height : width; - float radius = min_diameter / 2; - - float rad_start = start_angle * (SDL_PI_F / 180.0f); - float rad_end = end_angle * (SDL_PI_F / 180.0f); - - const int segments = SDL_max(16, (int)(radius * 1.5f)); - SDL_Vertex vertices[segments + 1]; - - int vertexCount = 0, indexCount = 0; - - vertices[vertexCount++] = (SDL_Vertex){ - .position = { center_x, center_y }, - .color = color - }; - - float angle_step = (rad_end - rad_start) / ((float)segments - 1); - - for (int i = 0; i < segments; i++) { - float angle = rad_start + (float)i * angle_step; - vertices[vertexCount++] = (SDL_Vertex){ - .position = { - center_x + radius * SDL_cosf(angle), - center_y + radius * SDL_sinf(angle) - }, - .color = color - }; - } - - int indices[segments * 3]; - for (int j = 0; j < segments - 1; j++) { - indices[indexCount++] = 0; - indices[indexCount++] = j + 1; - indices[indexCount++] = (j + 1) % segments + 1; - } - - SDL_RenderGeometry(renderer, NULL, vertices, vertexCount, indices, indexCount); -} - -void SDL_Clay_RenderWaveScreen(SDL_Renderer *renderer, float x, float y, float width, float height, const Clay_Color color, float *point_buffer, size_t buffer_len) { - SDL_SetRenderDrawColor(renderer, color.r, color.g, color.b, color.a); - float samples_per_px = (float)buffer_len / width; - - for (int px = 0; px < (int)width; px++) { - size_t start = (size_t)(px * samples_per_px); - size_t end = (size_t)((px + 1) * samples_per_px); - if (end >= buffer_len) end = buffer_len - 1; - - float minv = 1.0f, maxv = -1.0f; - for (size_t i = start; i <= end; i++) { - if (point_buffer[i] < minv) minv = point_buffer[i]; - if (point_buffer[i] > maxv) maxv = point_buffer[i]; - } - - float y_min = y + height * (0.5f - 0.5f * maxv); - float y_max = y + height * (0.5f - 0.5f * minv); - - SDL_RenderLine(renderer, x + px, y_min, x + px, y_max); - } -} - -SDL_Rect currentClippingRectangle; - -void SDL_Clay_RenderClayCommands(Clay_SDL3RendererData *rendererData, Clay_RenderCommandArray *rcommands) -{ - for (int32_t i = 0; i < rcommands->length; i++) { - Clay_RenderCommand *rcmd = Clay_RenderCommandArray_Get(rcommands, i); - const Clay_BoundingBox bounding_box = rcmd->boundingBox; - const SDL_FRect rect = { (int)bounding_box.x, (int)bounding_box.y, (int)bounding_box.width, (int)bounding_box.height }; - - switch (rcmd->commandType) { - case CLAY_RENDER_COMMAND_TYPE_RECTANGLE: { - Clay_RectangleRenderData *config = &rcmd->renderData.rectangle; - SDL_SetRenderDrawBlendMode(rendererData->renderer, SDL_BLENDMODE_BLEND); - SDL_SetRenderDrawColor(rendererData->renderer, config->backgroundColor.r, config->backgroundColor.g, config->backgroundColor.b, config->backgroundColor.a); - if (config->cornerRadius.topLeft > 0) { - SDL_Clay_RenderFillRoundedRect(rendererData, rect, config->cornerRadius.topLeft, config->backgroundColor); - } else { - SDL_RenderFillRect(rendererData->renderer, &rect); - } - } break; - case CLAY_RENDER_COMMAND_TYPE_TEXT: { - Clay_TextRenderData *config = &rcmd->renderData.text; - TTF_Font *font = rendererData->fonts[config->fontId]; - TTF_SetFontSize(font, config->fontSize); - TTF_Text *text = TTF_CreateText(rendererData->textEngine, font, config->stringContents.chars, config->stringContents.length); - TTF_SetTextColor(text, config->textColor.r, config->textColor.g, config->textColor.b, config->textColor.a); - TTF_DrawRendererText(text, rect.x, rect.y); - TTF_DestroyText(text); - } break; - case CLAY_RENDER_COMMAND_TYPE_BORDER: { - Clay_BorderRenderData *config = &rcmd->renderData.border; - - const float minRadius = SDL_min(rect.w, rect.h) / 2.0f; - const Clay_CornerRadius clampedRadii = { - .topLeft = SDL_min(config->cornerRadius.topLeft, minRadius), - .topRight = SDL_min(config->cornerRadius.topRight, minRadius), - .bottomLeft = SDL_min(config->cornerRadius.bottomLeft, minRadius), - .bottomRight = SDL_min(config->cornerRadius.bottomRight, minRadius) - }; - //edges - SDL_SetRenderDrawColor(rendererData->renderer, config->color.r, config->color.g, config->color.b, config->color.a); - if (config->width.left > 0) { - const float starting_y = rect.y + clampedRadii.topLeft; - const float length = rect.h - clampedRadii.topLeft - clampedRadii.bottomLeft; - SDL_FRect line = { rect.x - 1, starting_y, config->width.left, length }; - SDL_RenderFillRect(rendererData->renderer, &line); - } - if (config->width.right > 0) { - const float starting_x = rect.x + rect.w - (float)config->width.right + 1; - const float starting_y = rect.y + clampedRadii.topRight; - const float length = rect.h - clampedRadii.topRight - clampedRadii.bottomRight; - SDL_FRect line = { starting_x, starting_y, config->width.right, length }; - SDL_RenderFillRect(rendererData->renderer, &line); - } - if (config->width.top > 0) { - const float starting_x = rect.x + clampedRadii.topLeft; - const float length = rect.w - clampedRadii.topLeft - clampedRadii.topRight; - SDL_FRect line = { starting_x, rect.y - 1, length, config->width.top }; - SDL_RenderFillRect(rendererData->renderer, &line); - } - if (config->width.bottom > 0) { - const float starting_x = rect.x + clampedRadii.bottomLeft; - const float starting_y = rect.y + rect.h - (float)config->width.bottom + 1; - const float length = rect.w - clampedRadii.bottomLeft - clampedRadii.bottomRight; - SDL_FRect line = { starting_x, starting_y, length, config->width.bottom }; - SDL_SetRenderDrawColor(rendererData->renderer, config->color.r, config->color.g, config->color.b, config->color.a); - SDL_RenderFillRect(rendererData->renderer, &line); - } - //corners - if (config->cornerRadius.topLeft > 0) { - const float centerX = rect.x + clampedRadii.topLeft -1; - const float centerY = rect.y + clampedRadii.topLeft - 1; - SDL_Clay_RenderArc(rendererData, (SDL_FPoint){centerX, centerY}, clampedRadii.topLeft, - 180.0f, 270.0f, config->width.top, config->color); - } - if (config->cornerRadius.topRight > 0) { - const float centerX = rect.x + rect.w - clampedRadii.topRight; - const float centerY = rect.y + clampedRadii.topRight - 1; - SDL_Clay_RenderArc(rendererData, (SDL_FPoint){centerX, centerY}, clampedRadii.topRight, - 270.0f, 360.0f, config->width.top, config->color); - } - if (config->cornerRadius.bottomLeft > 0) { - const float centerX = rect.x + clampedRadii.bottomLeft -1; - const float centerY = rect.y + rect.h - clampedRadii.bottomLeft; - SDL_Clay_RenderArc(rendererData, (SDL_FPoint){centerX, centerY}, clampedRadii.bottomLeft, - 90.0f, 180.0f, config->width.bottom, config->color); - } - if (config->cornerRadius.bottomRight > 0) { - const float centerX = rect.x + rect.w - clampedRadii.bottomRight; - const float centerY = rect.y + rect.h - clampedRadii.bottomRight; - SDL_Clay_RenderArc(rendererData, (SDL_FPoint){centerX, centerY}, clampedRadii.bottomRight, - 0.0f, 90.0f, config->width.bottom, config->color); - } - - } break; - case CLAY_RENDER_COMMAND_TYPE_SCISSOR_START: { - Clay_BoundingBox boundingBox = rcmd->boundingBox; - currentClippingRectangle = (SDL_Rect) { - .x = boundingBox.x, - .y = boundingBox.y, - .w = boundingBox.width, - .h = boundingBox.height, - }; - SDL_SetRenderClipRect(rendererData->renderer, ¤tClippingRectangle); - break; - } - case CLAY_RENDER_COMMAND_TYPE_SCISSOR_END: { - SDL_SetRenderClipRect(rendererData->renderer, NULL); - break; - } - case CLAY_RENDER_COMMAND_TYPE_IMAGE: { - SDL_Texture *texture = (SDL_Texture *)rcmd->renderData.image.imageData; - const SDL_FRect dest = { rect.x, rect.y, rect.w, rect.h }; - SDL_RenderTexture(rendererData->renderer, texture, NULL, &dest); - break; - } - - case CLAY_RENDER_COMMAND_TYPE_CUSTOM: { - Clay_BoundingBox bounding_box = rcmd->boundingBox; - - CustomElementData *custom_element = rcmd->renderData.custom.customData; - if (!custom_element) continue; - - switch (custom_element->type) { - case CUSTOM_ELEMENT_TYPE_CIRCLE: { - CircleData config = custom_element->circle; - - float start_angle = config.start_angle; - float end_angle = config.value * 360 + start_angle; - end_angle = end_angle > start_angle ? end_angle : end_angle + 360; - - SDL_Clay_RenderCircle(rendererData->renderer, bounding_box.x, bounding_box.y, bounding_box.width, bounding_box.height, start_angle, end_angle, config.color); - break; - } - case CUSTOM_ELEMENT_TYPE_WAVE_SCREEN: { - WaveScreenData config = custom_element->wave_screen; - SDL_Clay_RenderWaveScreen(rendererData->renderer, bounding_box.x, bounding_box.y, - bounding_box.width, bounding_box.height, config.color, - config.point_buffer, config.buffer_len); - } - } - break; - } - default: - SDL_Log("Unknown render command type: %d", rcmd->commandType); - } - } -} diff --git a/src/clay_renderer_SDL3.h b/src/clay_renderer_SDL3.h deleted file mode 100644 index 5ba1104..0000000 --- a/src/clay_renderer_SDL3.h +++ /dev/null @@ -1,43 +0,0 @@ -#ifndef RENDERER_H_ -#define RENDERER_H_ - -#include "clay.h" -#include -#include -#include - -typedef struct { - SDL_Renderer *renderer; - TTF_TextEngine *textEngine; - TTF_Font **fonts; -} Clay_SDL3RendererData; - -typedef enum { - CUSTOM_ELEMENT_TYPE_CIRCLE, - CUSTOM_ELEMENT_TYPE_WAVE_SCREEN, -} CustomElementType; - -typedef struct { - float start_angle; - float value; - Clay_Color color; -} CircleData; - -typedef struct { - float *point_buffer; - size_t buffer_len; - Clay_Color color; -} WaveScreenData; - -typedef struct { - CustomElementType type; - - union { - CircleData circle; - WaveScreenData wave_screen; - }; -} CustomElementData; - -void SDL_Clay_RenderClayCommands(Clay_SDL3RendererData *rendererData, Clay_RenderCommandArray *rcommands); - -#endif // RENDERER_H_ diff --git a/src/custom_elements.c b/src/custom_elements.c new file mode 100644 index 0000000..23a9824 --- /dev/null +++ b/src/custom_elements.c @@ -0,0 +1,255 @@ +#include "custom_elements.h" + +#include "sokol/sokol_gfx.h" +#include "sokol/sokol_gl.h" +#include +#include + +#define STEP_AMOUNT 128 + +typedef struct { + float x; + float y; +} Vec2; + +static float _SIN_FULL[128] = { + 0.000000f, 0.049068f, 0.098017f, 0.146730f, + 0.195090f, 0.242980f, 0.290285f, 0.336890f, + 0.382683f, 0.427555f, 0.471397f, 0.514103f, + 0.555570f, 0.595699f, 0.634393f, 0.671559f, + 0.707107f, 0.740951f, 0.773010f, 0.803208f, + 0.831470f, 0.857729f, 0.881921f, 0.903989f, + 0.923880f, 0.941544f, 0.956940f, 0.970031f, + 0.980785f, 0.989177f, 0.995185f, 0.998795f, + 1.000000f, 0.998795f, 0.995185f, 0.989177f, + 0.980785f, 0.970031f, 0.956940f, 0.941544f, + 0.923880f, 0.903989f, 0.881921f, 0.857729f, + 0.831470f, 0.803208f, 0.773010f, 0.740951f, + 0.707107f, 0.671559f, 0.634393f, 0.595699f, + 0.555570f, 0.514103f, 0.471397f, 0.427555f, + 0.382683f, 0.336890f, 0.290285f, 0.242980f, + 0.195090f, 0.146730f, 0.098017f, 0.049068f, + 0.000000f, -0.049068f, -0.098017f, -0.146730f, + -0.195090f, -0.242980f, -0.290285f, -0.336890f, + -0.382683f, -0.427555f, -0.471397f, -0.514103f, + -0.555570f, -0.595699f, -0.634393f, -0.671559f, + -0.707107f, -0.740951f, -0.773010f, -0.803208f, + -0.831470f, -0.857729f, -0.881921f, -0.903989f, + -0.923880f, -0.941544f, -0.956940f, -0.970031f, + -0.980785f, -0.989177f, -0.995185f, -0.998795f, + -1.000000f, -0.998795f, -0.995185f, -0.989177f, + -0.980785f, -0.970031f, -0.956940f, -0.941544f, + -0.923880f, -0.903989f, -0.881921f, -0.857729f, + -0.831470f, -0.803208f, -0.773010f, -0.740951f, + -0.707107f, -0.671559f, -0.634393f, -0.595699f, + -0.555570f, -0.514103f, -0.471397f, -0.427555f, + -0.382683f, -0.336890f, -0.290285f, -0.242980f, + -0.195090f, -0.146730f, -0.098017f, -0.049068f, +}; + +static float _COS_FULL[128] = { + 1.000000f, 0.998795f, 0.995185f, 0.989177f, + 0.980785f, 0.970031f, 0.956940f, 0.941544f, + 0.923880f, 0.903989f, 0.881921f, 0.857729f, + 0.831470f, 0.803208f, 0.773010f, 0.740951f, + 0.707107f, 0.671559f, 0.634393f, 0.595699f, + 0.555570f, 0.514103f, 0.471397f, 0.427555f, + 0.382683f, 0.336890f, 0.290285f, 0.242980f, + 0.195090f, 0.146730f, 0.098017f, 0.049068f, + 0.000000f, -0.049068f, -0.098017f, -0.146730f, + -0.195090f, -0.242980f, -0.290285f, -0.336890f, + -0.382683f, -0.427555f, -0.471397f, -0.514103f, + -0.555570f, -0.595699f, -0.634393f, -0.671559f, + -0.707107f, -0.740951f, -0.773010f, -0.803208f, + -0.831470f, -0.857729f, -0.881921f, -0.903989f, + -0.923880f, -0.941544f, -0.956940f, -0.970031f, + -0.980785f, -0.989177f, -0.995185f, -0.998795f, + -1.000000f, -0.998795f, -0.995185f, -0.989177f, + -0.980785f, -0.970031f, -0.956940f, -0.941544f, + -0.923880f, -0.903989f, -0.881921f, -0.857729f, + -0.831470f, -0.803208f, -0.773010f, -0.740951f, + -0.707107f, -0.671559f, -0.634393f, -0.595699f, + -0.555570f, -0.514103f, -0.471397f, -0.427555f, + -0.382683f, -0.336890f, -0.290285f, -0.242980f, + -0.195090f, -0.146730f, -0.098017f, -0.049068f, + -0.000000f, 0.049068f, 0.098017f, 0.146730f, + 0.195090f, 0.242980f, 0.290285f, 0.336890f, + 0.382683f, 0.427555f, 0.471397f, 0.514103f, + 0.555570f, 0.595699f, 0.634393f, 0.671559f, + 0.707107f, 0.740951f, 0.773010f, 0.803208f, + 0.831470f, 0.857729f, 0.881921f, 0.903989f, + 0.923880f, 0.941544f, 0.956940f, 0.970031f, + 0.980785f, 0.989177f, 0.995185f, 0.998795f, +}; + +static float STEP = (float)STEP_AMOUNT / 360.0f; + + +static void draw_thick_line(float x1, float y1, float x2, float y2, float thickness) { + float rad = atan2f(y2 - y1, x2 - x1); + + float xoffset = -1 * thickness * sinf(rad); + if (rad < 0) { + xoffset *= -1; + } + float yoffset = thickness * cosf(rad); + if (rad < 0) { + yoffset *= -1; + } + + /* float xoffset = sqrtf(powf(thickness, 2) * (1.0f - powf(sinf(M_PI / 2 - rad), 2))); */ + /* float yoffset = thickness * sinf(M_PI / 2 - rad); */ + /* /\* if (rad < 0) { *\/ */ + /* xoffset *= -1; */ + /* yoffset *= -1; */ + /* } */ + /* printf("rad: %f, x: %f, y: %f\n", rad, xoffset, yoffset); */ + + float x1top = x1 - xoffset; + float y1top = y1 - yoffset; + + float x2top = x2 - xoffset; + float y2top = y2 - yoffset; + + float x1bottom = x1 + xoffset; + float y1bottom = y1 + yoffset; + + float x2bottom = x2 + xoffset; + float y2bottom = y2 + yoffset; + /* printf("1t: (%f %f) 2t: (%f %f)\n" */ + /* "1b: (%f %f) 2b: (%f %f)\n", */ + /* x1top, y1top, */ + /* x2top, y2top, */ + /* x1bottom, y1bottom, */ + /* x2bottom, y2bottom), */ + + sgl_v2f(x1top, y1top); + sgl_v2f(x1top, y1top); + sgl_v2f(x2top, y2top); + sgl_v2f(x1bottom, y1bottom); + sgl_v2f(x2bottom, y2bottom); + sgl_v2f(x2bottom, y2bottom); +} + +static void draw_wave_screen(float x, float y, float h, float w, float *points, size_t len) { + float samples_per_px = (float)len / w; + + float last_point = y + points[0] * h; + for (int px = 1; px < (int)w; px++) { + size_t start = (size_t)(px * samples_per_px); + size_t end = (size_t)((px + 1) * samples_per_px); + if (end >= len) end = len - 1; + + float minv = 1.0f, maxv = -1.0f; + for (size_t i = start; i <= end; i++) { + if (points[i] < minv) minv = points[i]; + if (points[i] > maxv) maxv = points[i]; + } + + float y_min = y + h * (0.5f - 0.5f * maxv); + float y_max = y + h * (0.5f - 0.5f * minv); + + if (y_min == y_max) { + printf("%d %f %d %f\n", x + px - 1, last_point, x + px, y_max); + draw_thick_line(x + px - 1, last_point, x + px, y_max, 5); + last_point = y_max; + } + } +} + +static void draw_circle(float x, float y, float h, float w, float start_angle, float end_angle) { + float c_x = x + w / 2; + float c_y = y + h / 2; + float r = w > h ? h : w; + r /= 2.0f; + + float rad_start = start_angle * (M_PI / 180.0f); + float rad_end = end_angle * (M_PI / 180.0f); + /* draw_thick_line(c_x, c_y, c_x+(r*cosf(rad_end)), c_y+(r*sinf(rad_end)), 5); */ + + int segments = (int)(r * 1.5f); + if (segments < 16) { + segments = 16; + } + float angle_step = (rad_end - rad_start) / ((float)segments - 1); + float angle; + + sgl_v2f(c_x, c_y); + for (int i = 0; i < segments; i++) { + angle = rad_start + angle_step * (float)i; + sgl_v2f(c_x, c_y); + sgl_v2f(c_x+(r*cosf(angle)), c_y+(r*sinf(angle))); + } + sgl_v2f(c_x+(r*cos(angle)), c_y+(r*sin(angle))); +} + +static void _draw_circle(float x, float y, float h, float w, float start_angle, float end_angle) { + float c_x = x + w / 2; + float c_y = y + h / 2; + float r = w > h ? h : w; + r /= 2.0f; + + int start = start_angle * STEP; + int end = end_angle * STEP; + int amount; + if (end_angle == start_angle) { + amount = STEP_AMOUNT; + } else { + amount = (end - start + STEP_AMOUNT) % STEP_AMOUNT; + } + int idx = start; + + + sgl_v2f(c_x, c_y); + for (int i = 0; i < amount; i++) { + sgl_v2f(c_x, c_y); + sgl_v2f(c_x+(r*_COS_FULL[idx]), c_y+(r*_SIN_FULL[idx])); + idx++; + idx %= STEP_AMOUNT; + } + sgl_v2f(c_x, c_y); + sgl_v2f(c_x+(r*_COS_FULL[idx]), c_y+(r*_SIN_FULL[idx])); + sgl_v2f(c_x+(r*_COS_FULL[idx]), c_y+(r*_SIN_FULL[idx])); +} + +void handle_custom(Clay_BoundingBox bbox, Clay_CustomRenderData *config) { + CustomElementData *custom_data = config->customData; + + switch (custom_data->type) { + case CUSTOM_ELEMENT_TYPE_CIRCLE: { + CircleData circle_data = custom_data->circle; + + float start_angle = circle_data.start_angle; + /* float end_angle = fmod(circle_data.value * 360 + start_angle + 360, 360); */ + float end_angle = circle_data.value * 360 + start_angle; + end_angle = end_angle > start_angle ? end_angle : end_angle + 360; + + sgl_c4f(config->backgroundColor.r / 255.0f, + config->backgroundColor.g / 255.0f, + config->backgroundColor.b / 255.0f, + config->backgroundColor.a / 255.0f); + + sgl_begin_triangle_strip(); + draw_circle(bbox.x, bbox.y, bbox.height, bbox.width, + start_angle, end_angle); + sgl_end(); + break; + } + case CUSTOM_ELEMENT_TYPE_WAVE_SCREEN: { + WaveScreenData wave_data = custom_data->wave_screen; + sgl_c4f(config->backgroundColor.r / 255.0f, + config->backgroundColor.g / 255.0f, + config->backgroundColor.b / 255.0f, + config->backgroundColor.a / 255.0f); + sgl_begin_triangle_strip(); + /* draw_thick_line(bbox.x, bbox.y, */ + /* bbox.x+bbox.width, bbox.y+bbox.height, */ + /* 20); */ + draw_wave_screen(bbox.x, bbox.y, + bbox.height, bbox.width, + wave_data.point_buffer, wave_data.buffer_len); + sgl_end(); + + } + } +} diff --git a/src/custom_elements.h b/src/custom_elements.h new file mode 100644 index 0000000..cd36b6a --- /dev/null +++ b/src/custom_elements.h @@ -0,0 +1,32 @@ +#ifndef CUSTOM_ELEMENTS_H_ +#define CUSTOM_ELEMENTS_H_ + +#include "clay.h" + +typedef enum { + CUSTOM_ELEMENT_TYPE_CIRCLE, + CUSTOM_ELEMENT_TYPE_WAVE_SCREEN, +} CustomElementType; + +typedef struct { + float start_angle; + float value; +} CircleData; + +typedef struct { + float *point_buffer; + size_t buffer_len; +} WaveScreenData; + +typedef struct { + CustomElementType type; + + union { + CircleData circle; + WaveScreenData wave_screen; + }; +} CustomElementData; + +void handle_custom(Clay_BoundingBox bbox, Clay_CustomRenderData *config); + +#endif // CUSTOM_ELEMENTS_H_ diff --git a/src/main.c b/src/main.c index 1b398d1..7306c28 100644 --- a/src/main.c +++ b/src/main.c @@ -2,22 +2,32 @@ #include #include -#define SDL_MAIN_USE_CALLBACKS -#include -#include -#include -#include - #include "ui.h" #include "sounds.h" +#include "custom_elements.h" + +#define SOKOL_IMPL +#define SOKOL_GLCORE +#include "sokol/sokol_gfx.h" +#include "sokol/sokol_gl.h" +#include "sokol/sokol_glue.h" +#include "sokol/sokol_app.h" +#include "sokol/sokol_log.h" + +#define FONTSTASH_IMPLEMENTATION +#include "fontstash/fontstash.h" +#include "sokol/sokol_fontstash.h" #define CLAY_IMPLEMENTATION #include "clay.h" -#include "clay_renderer_SDL3.h" + +#define SOKOL_CLAY_IMPL +#include "sokol/sokol_clay.h" #define ARENA_IMPLEMENTATION #include "arena.h" + typedef struct { bool pressed; @@ -29,8 +39,6 @@ typedef struct { typedef struct { - SDL_Window *window; - Clay_SDL3RendererData renderer_data; Arena frame_arena; PointerState pointer; @@ -43,9 +51,9 @@ typedef struct { MessageQueue msg_queue; WaveData wave_data; pthread_t sound_thread; -} app_state; +} AppState; -int init_sounds(app_state *state) { +int init_sounds(AppState *state) { int err; err = @@ -159,123 +167,75 @@ int init_sounds(app_state *state) { return 0; } -static inline Clay_Dimensions SDL_MeasureText(Clay_StringSlice text, Clay_TextElementConfig *config, void *userData) -{ - TTF_Font **fonts = userData; - TTF_Font *font = fonts[config->fontId]; - int width, height; - - TTF_SetFontSize(font, config->fontSize); - if (!TTF_GetStringSize(font, text.chars, text.length, &width, &height)) { - SDL_LogError(SDL_LOG_CATEGORY_ERROR, "Failed to measure text: %s", SDL_GetError()); - } - - return (Clay_Dimensions) { (float) width, (float) height }; -} - void HandleClayErrors(Clay_ErrorData errorData) { printf("%s", errorData.errorText.chars); } -int init_ui(app_state *state) { - if (!TTF_Init()) { - return 1; - } - - if (!SDL_Init(SDL_INIT_VIDEO)) { - return 1; - } - - if (!SDL_CreateWindowAndRenderer("crynth", DEFAULT_DIMENSIONS_WIDTH, DEFAULT_DIMENSIONS_HEIGHT, SDL_WINDOW_RESIZABLE | SDL_WINDOW_BORDERLESS, &state->window, &state->renderer_data.renderer)) { - return 1; - } - - SDL_SetWindowResizable(state->window, true); - - state->renderer_data.textEngine = TTF_CreateRendererTextEngine(state->renderer_data.renderer); - if (!state->renderer_data.textEngine) { - return 1; - } +int init_ui() { + sg_setup(&(sg_desc){ + .environment = sglue_environment(), + .logger.func = slog_func, + }); + sgl_setup(&(sgl_desc_t){ + .logger.func = slog_func, + }); + sclay_setup(); - state->renderer_data.fonts = SDL_calloc(1, sizeof(TTF_Font *)); - if (!state->renderer_data.fonts) { - return 1; - } - - TTF_Font *font = TTF_OpenFont("resources/Roboto-Regular.ttf", 24); - if (!font) { - return 1; - } - - state->renderer_data.fonts[FONT_ID] = font; + sclay_add_font("resources/Roboto-Regular.ttf"); + sclay_set_custom_element_cb(handle_custom); size_t totalMemorySize = Clay_MinMemorySize(); Clay_Arena clayMemory = (Clay_Arena) { - .memory = SDL_malloc(totalMemorySize), + .memory = malloc(totalMemorySize), .capacity = totalMemorySize }; - int width, height; - SDL_GetWindowSize(state->window, &width, &height); - Clay_Initialize(clayMemory, (Clay_Dimensions) { (float) width, (float) height }, (Clay_ErrorHandler) { HandleClayErrors, 0}); - Clay_SetMeasureTextFunction(SDL_MeasureText, state->renderer_data.fonts); + Clay_Initialize(clayMemory, (Clay_Dimensions) { (float) sapp_width(), (float) sapp_height() }, (Clay_ErrorHandler) { HandleClayErrors, 0}); + Clay_SetMeasureTextFunction(sclay_measure_text, NULL); return 0; } static KeyState keys[] = { - {'Z', SDL_SCANCODE_Z, 0, 0}, {'S', SDL_SCANCODE_S, 0, 0}, - {'X', SDL_SCANCODE_X, 0, 0}, {'D', SDL_SCANCODE_D, 0, 0}, - {'C', SDL_SCANCODE_C, 0, 0}, - {'V', SDL_SCANCODE_V, 0, 0}, {'G', SDL_SCANCODE_G, 0, 0}, - {'B', SDL_SCANCODE_B, 0, 0}, {'H', SDL_SCANCODE_H, 0, 0}, - {'N', SDL_SCANCODE_N, 0, 0}, {'J', SDL_SCANCODE_J, 0, 0}, - {'M', SDL_SCANCODE_M, 0, 0}, - - {'Q', SDL_SCANCODE_Q, 0, 0}, {'2', SDL_SCANCODE_2, 0, 0}, - {'W', SDL_SCANCODE_W, 0, 0}, {'3', SDL_SCANCODE_3, 0, 0}, - {'E', SDL_SCANCODE_E, 0, 0}, - {'R', SDL_SCANCODE_R, 0, 0}, {'6', SDL_SCANCODE_5, 0, 0}, - {'T', SDL_SCANCODE_T, 0, 0}, {'7', SDL_SCANCODE_6, 0, 0}, - {'Y', SDL_SCANCODE_Y, 0, 0}, {'8', SDL_SCANCODE_7, 0, 0}, - {'U', SDL_SCANCODE_U, 0, 0}, + {'Z', SAPP_KEYCODE_Z, 0, 0}, {'S', SAPP_KEYCODE_S, 0, 0}, + {'X', SAPP_KEYCODE_X, 0, 0}, {'D', SAPP_KEYCODE_D, 0, 0}, + {'C', SAPP_KEYCODE_C, 0, 0}, + {'V', SAPP_KEYCODE_V, 0, 0}, {'G', SAPP_KEYCODE_G, 0, 0}, + {'B', SAPP_KEYCODE_B, 0, 0}, {'H', SAPP_KEYCODE_H, 0, 0}, + {'N', SAPP_KEYCODE_N, 0, 0}, {'J', SAPP_KEYCODE_J, 0, 0}, + {'M', SAPP_KEYCODE_M, 0, 0}, + + {'Q', SAPP_KEYCODE_Q, 0, 0}, {'2', SAPP_KEYCODE_2, 0, 0}, + {'W', SAPP_KEYCODE_W, 0, 0}, {'3', SAPP_KEYCODE_3, 0, 0}, + {'E', SAPP_KEYCODE_E, 0, 0}, + {'R', SAPP_KEYCODE_R, 0, 0}, {'6', SAPP_KEYCODE_5, 0, 0}, + {'T', SAPP_KEYCODE_T, 0, 0}, {'7', SAPP_KEYCODE_6, 0, 0}, + {'Y', SAPP_KEYCODE_Y, 0, 0}, {'8', SAPP_KEYCODE_7, 0, 0}, + {'U', SAPP_KEYCODE_U, 0, 0}, }; -SDL_AppResult SDL_AppInit(void **appstate, int argc, char **argv) { - (void) argc; - (void) argv; - - app_state *state = malloc(sizeof(app_state)); - memset(state, 0, sizeof(app_state)); - *appstate = state; +static void init(void *app_state) { + AppState *state = app_state; state->keys = keys; state->keys_amount = sizeof(keys)/sizeof(keys[0]); - if (init_ui(state) != 0) { - printf("Couldn't initialize UI: %s", SDL_GetError()); - return SDL_APP_FAILURE; + if (init_ui() != 0) { + printf("Couldn't initialize UI"); + sapp_quit(); } if (init_sounds(state) != 0) { - return SDL_APP_FAILURE; + printf("Couldn't initialize sounds: %s"); + sapp_quit(); } - - return SDL_APP_CONTINUE; } float old_wave_buffer[DISPLAY_SAMPLES]; -SDL_AppResult SDL_AppIterate(void *appstate) { - app_state *state = appstate; - Arena *arena = &state->frame_arena; - arena_reset(arena); - - int start_tick = SDL_GetTicks(); - +void fill_ui_data(UIData *ui_data, AppState *state) { Clay_Dimensions dimensions = Clay_GetCurrentContext()->layoutDimensions; - UIData *ui_data = arena_alloc(arena, sizeof(UIData)); - ui_data->arena = arena; + ui_data->arena = &state->frame_arena; ui_data->msg_queue = &state->msg_queue; int read_index = 1 - atomic_load(&state->wave_data.write_index); @@ -283,7 +243,7 @@ SDL_AppResult SDL_AppIterate(void *appstate) { size_t buffer_start = 0; for (size_t i = 1; i < DISPLAY_SAMPLES; i++) { - if (wave_buffer[i-1] < 0 && wave_buffer[i] >= 0) { + if (wave_buffer[i-1] < 0 && wave_buffer[i] >= 0) { buffer_start = i; break; } @@ -303,46 +263,51 @@ SDL_AppResult SDL_AppIterate(void *appstate) { ui_data->knob_settings = &state->knob_settings; ui_data->scale = dimensions.width / DEFAULT_DIMENSIONS_WIDTH; +} - - Clay_SetPointerState((Clay_Vector2){ state->pointer.position_x, state->pointer.position_y }, state->pointer.pressed); - Clay_UpdateScrollContainers(true, (Clay_Vector2){ state->pointer.pending_scroll_delta_x, state->pointer.pending_scroll_delta_y }, 0.016f); - state->pointer.pending_scroll_delta_x = 0; - state->pointer.pending_scroll_delta_y = 0; +static void frame(void *app_state) { + AppState *state = app_state; + Arena *arena = &state->frame_arena; + arena_reset(arena); + UIData *ui_data = arena_alloc(arena, sizeof(UIData)); + fill_ui_data(ui_data, state); + + + sclay_new_frame(); Clay_BeginLayout(); draw_ui(ui_data); Clay_RenderCommandArray render_commands = Clay_EndLayout(); - SDL_SetRenderDrawColor(state->renderer_data.renderer, 0, 0, 0, 255); - SDL_RenderClear(state->renderer_data.renderer); - SDL_Clay_RenderClayCommands(&state->renderer_data, &render_commands); + sg_begin_pass(&(sg_pass){ .swapchain = sglue_swapchain() }); + + sgl_matrix_mode_modelview(); + sgl_load_identity(); - SDL_RenderPresent(state->renderer_data.renderer); + sclay_render(render_commands, NULL); - int end_tick = SDL_GetTicks(); - int frame_ticks = end_tick - start_tick; + sgl_draw(); + sg_end_pass(); + sg_commit(); - if (frame_ticks < SCREEN_TICKS_PER_FRAME) { - SDL_Delay(SCREEN_TICKS_PER_FRAME - frame_ticks); - } + /* int end_tick = SDL_GetTicks(); */ + /* int frame_ticks = end_tick - start_tick; */ - return SDL_APP_CONTINUE; + /* if (frame_ticks < SCREEN_TICKS_PER_FRAME) { */ + /* SDL_Delay(SCREEN_TICKS_PER_FRAME - frame_ticks); */ + /* } */ } -SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event) { - app_state *state = appstate; +static void event(const sapp_event* event, void *app_state) { + AppState *state = app_state; switch (event->type) { - case SDL_EVENT_QUIT: { - return SDL_APP_SUCCESS; - } - case SDL_EVENT_KEY_DOWN: { + case SAPP_EVENTTYPE_KEY_DOWN: { for (size_t i = 0; i < state->keys_amount; i++) { - if (event->key.scancode == state->keys[i].keycode) { + if (event->key_code == state->keys[i].keycode) { if (state->keys[i].keyboard_pressed) { break; } @@ -357,9 +322,9 @@ SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event) { break; } - case SDL_EVENT_KEY_UP: { + case SAPP_EVENTTYPE_KEY_UP: { for (size_t i = 0; i < state->keys_amount; i++) { - if (event->key.scancode == state->keys[i].keycode) { + if (event->key_code == state->keys[i].keycode) { if (!state->keys[i].keyboard_pressed) { break; } @@ -373,50 +338,33 @@ SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event) { break; } - case SDL_EVENT_WINDOW_RESIZED: - Clay_SetLayoutDimensions((Clay_Dimensions) { (float) event->window.data1, (float) event->window.data2 }); - break; - - case SDL_EVENT_MOUSE_MOTION: - state->pointer.pressed = event->motion.state & SDL_BUTTON_LMASK; - state->pointer.position_x = event->motion.x; - state->pointer.position_y = event->motion.y; - break; - - case SDL_EVENT_MOUSE_BUTTON_DOWN: - state->pointer.pressed = event->button.button == SDL_BUTTON_LEFT; - state->pointer.position_x = event->button.x; - state->pointer.position_y = event->button.y; - break; - case SDL_EVENT_MOUSE_BUTTON_UP: - if (event->button.button == SDL_BUTTON_LEFT) { - state->pointer.pressed = false; - state->pointer.position_x = event->button.x; - state->pointer.position_y = event->button.y; - } - break; - - case SDL_EVENT_MOUSE_WHEEL: - state->pointer.pending_scroll_delta_x += event->wheel.x; - state->pointer.pending_scroll_delta_y += event->wheel.y; - break; - default: + sclay_handle_event(event); break; } - return SDL_APP_CONTINUE; } -void SDL_AppQuit(void *appstate, SDL_AppResult result) { - (void) result; - - app_state *state = appstate; - - MessageQueue *queue = &state->msg_queue; +static void cleanup() { + sclay_shutdown(); + sgl_shutdown(); + sg_shutdown(); +} - SynthMessage stop_msg = {.type = MSG_STOP}; - mqueue_push(queue, stop_msg); +sapp_desc sokol_main(int argc, char **argv) { + (void)argc; + (void)argv; - pthread_join(state->sound_thread, NULL); - check(snd_pcm_close(state->sound_device)); + AppState *state = malloc(sizeof(AppState)); + + return (sapp_desc){ + .user_data = state, + .init_userdata_cb = init, + .frame_userdata_cb = frame, + .event_userdata_cb = event, + .cleanup_cb = cleanup, + .window_title = "crynth", + .width = DEFAULT_DIMENSIONS_WIDTH, + .height = DEFAULT_DIMENSIONS_HEIGHT, + .logger.func = slog_func, + }; } diff --git a/src/ui.c b/src/ui.c index 1ab15ab..10f4c64 100644 --- a/src/ui.c +++ b/src/ui.c @@ -13,14 +13,14 @@ float point_angle_on_circle(Clay_Vector2 point, Clay_BoundingBox circle) { float center_x = circle.x + circle.width / 2; float center_y = circle.y + circle.width / 2; - float point_rad = SDL_atan2f(point.y - center_y, point.x - center_x); + float point_rad = atan2f(point.y - center_y, point.x - center_x); - return point_rad * 180.0f / SDL_PI_F; + return point_rad * 180.0f / M_PI; } float point_value_on_circle(Clay_Vector2 point, Clay_BoundingBox circle, float start_angle) { float point_on_circle = point_angle_on_circle(point, circle); - float value = SDL_fmodf((point_on_circle - start_angle + 360), 360) / 360; + float value = fmodf((point_on_circle - start_angle + 360), 360) / 360; return value; } @@ -170,9 +170,9 @@ void draw_black_key(size_t idx, UIData *ui_data) { .element = CLAY_ATTACH_POINT_CENTER_TOP, .parent = CLAY_ATTACH_POINT_RIGHT_TOP, }, - .offset = { - .y = -1, - }, + /* .offset = { */ + /* .y = -1, */ + /* }, */ }, }) { bool mouse_pressed = ui_data->keys[idx].mouse_pressed; @@ -261,13 +261,13 @@ void draw_knob(Clay_ElementId id, UIData *ui_data, KnobInfo* knob_info, Clay_Siz knob_element_data->circle = (CircleData){ .start_angle = -90, .value = value, - .color = hovered ? COLOR_FG_INTER : COLOR_FG, }; CLAY_AUTO_ID({ .layout = { .sizing = {outer_size, outer_size}, .childAlignment = {CLAY_ALIGN_X_CENTER, CLAY_ALIGN_Y_CENTER}, }, + .backgroundColor = hovered ? COLOR_FG_INTER : COLOR_FG, .custom = { .customData = knob_element_data, }, @@ -277,12 +277,12 @@ void draw_knob(Clay_ElementId id, UIData *ui_data, KnobInfo* knob_info, Clay_Siz inner_data->circle = (CircleData){ .start_angle = 0, .value = 1.0f, - .color = COLOR_ACCENT, }; CLAY_AUTO_ID({ .layout = { .sizing = {inner_size, inner_size}, }, + .backgroundColor = COLOR_ACCENT, .custom = { .customData = inner_data, }, @@ -298,13 +298,13 @@ void draw_screen(UIData *ui_data) { wave->wave_screen = (WaveScreenData){ .point_buffer = ui_data->wave_buffer, .buffer_len = ui_data->wave_buffer_size, - .color = COLOR_FG, }; CLAY(CLAY_ID("wave_screen"), { .layout = { .sizing = {CLAY_SIZING_FIXED(200 * ui_data->scale), CLAY_SIZING_FIXED(100 * ui_data->scale)}, }, + .backgroundColor = COLOR_FG, .border = { .width = {1, 1, 1, 1, 0}, .color = COLOR_FG }, .custom = { .customData = wave, diff --git a/src/ui.h b/src/ui.h index d9ecbf4..bfae0c0 100644 --- a/src/ui.h +++ b/src/ui.h @@ -4,13 +4,12 @@ #include #include #include - -#include -#include +#include #include "arena.h" +#include "sokol/sokol_app.h" -#include "clay_renderer_SDL3.h" +#include "custom_elements.h" #include "clay.h" #include "messages.h" @@ -24,7 +23,7 @@ static const Clay_Color COLOR_ACCENT = (Clay_Color){133, 146, 137, 255}; typedef struct { char letter; - SDL_Keycode keycode; + sapp_keycode keycode; bool mouse_pressed; bool keyboard_pressed; } KeyState; diff --git a/third-party/arena.h b/third-party/arena.h index 7ec4c8f..8043098 100644 --- a/third-party/arena.h +++ b/third-party/arena.h @@ -44,21 +44,21 @@ #define ARENA_BACKEND ARENA_BACKEND_LIBC_MALLOC #endif // ARENA_BACKEND -typedef struct Region Region; +typedef struct ArenaRegion ArenaRegion; -struct Region { - Region *next; +struct ArenaRegion { + ArenaRegion *next; size_t count; size_t capacity; uintptr_t data[]; }; typedef struct { - Region *begin, *end; + ArenaRegion *begin, *end; } Arena; typedef struct { - Region *region; + ArenaRegion *region; size_t count; } Arena_Mark; @@ -66,8 +66,8 @@ typedef struct { #define ARENA_REGION_DEFAULT_CAPACITY (8*1024) #endif // ARENA_REGION_DEFAULT_CAPACITY -Region *new_region(size_t capacity); -void free_region(Region *r); +ArenaRegion *new_region(size_t capacity); +void free_region(ArenaRegion *r); void *arena_alloc(Arena *a, size_t size_bytes); void *arena_realloc(Arena *a, void *oldptr, size_t oldsz, size_t newsz); @@ -150,11 +150,11 @@ void arena_trim(Arena *a); // TODO: instead of accepting specific capacity new_region() should accept the size of the object we want to fit into the region // It should be up to new_region() to decide the actual capacity to allocate -Region *new_region(size_t capacity) +ArenaRegion *new_region(size_t capacity) { - size_t size_bytes = sizeof(Region) + sizeof(uintptr_t)*capacity; + size_t size_bytes = sizeof(ArenaRegion) + sizeof(uintptr_t)*capacity; // TODO: it would be nice if we could guarantee that the regions are allocated by ARENA_BACKEND_LIBC_MALLOC are page aligned - Region *r = (Region*)malloc(size_bytes); + ArenaRegion *r = (ArenaRegion*)malloc(size_bytes); ARENA_ASSERT(r); // TODO: since ARENA_ASSERT is disableable go through all the places where we use it to check for failed memory allocation and return with NULL there. r->next = NULL; r->count = 0; @@ -162,7 +162,7 @@ Region *new_region(size_t capacity) return r; } -void free_region(Region *r) +void free_region(ArenaRegion *r) { free(r); } @@ -170,10 +170,10 @@ void free_region(Region *r) #include #include -Region *new_region(size_t capacity) +ArenaRegion *new_region(size_t capacity) { - size_t size_bytes = sizeof(Region) + sizeof(uintptr_t) * capacity; - Region *r = mmap(NULL, size_bytes, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); + size_t size_bytes = sizeof(ArenaRegion) + sizeof(uintptr_t) * capacity; + ArenaRegion *r = mmap(NULL, size_bytes, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); ARENA_ASSERT(r != MAP_FAILED); r->next = NULL; r->count = 0; @@ -181,9 +181,9 @@ Region *new_region(size_t capacity) return r; } -void free_region(Region *r) +void free_region(ArenaRegion *r) { - size_t size_bytes = sizeof(Region) + sizeof(uintptr_t) * r->capacity; + size_t size_bytes = sizeof(ArenaRegion) + sizeof(uintptr_t) * r->capacity; int ret = munmap(r, size_bytes); ARENA_ASSERT(ret == 0); } @@ -199,10 +199,10 @@ void free_region(Region *r) #define INV_HANDLE(x) (((x) == NULL) || ((x) == INVALID_HANDLE_VALUE)) -Region *new_region(size_t capacity) +ArenaRegion *new_region(size_t capacity) { - SIZE_T size_bytes = sizeof(Region) + sizeof(uintptr_t) * capacity; - Region *r = VirtualAllocEx( + SIZE_T size_bytes = sizeof(ArenaRegion) + sizeof(uintptr_t) * capacity; + ArenaRegion *r = VirtualAllocEx( GetCurrentProcess(), /* Allocate in current process address space */ NULL, /* Unknown position */ size_bytes, /* Bytes to allocate */ @@ -218,7 +218,7 @@ Region *new_region(size_t capacity) return r; } -void free_region(Region *r) +void free_region(ArenaRegion *r) { if (INV_HANDLE(r)) return; @@ -247,10 +247,10 @@ unsigned char* bump_pointer = &__heap_base; // __builtin_wasm_memory_size and __builtin_wasm_memory_grow are defined in units of page sizes #define ARENA_WASM_PAGE_SIZE (64*1024) -Region *new_region(size_t capacity) +ArenaRegion *new_region(size_t capacity) { - size_t size_bytes = sizeof(Region) + sizeof(uintptr_t)*capacity; - Region *r = (void*)bump_pointer; + size_t size_bytes = sizeof(ArenaRegion) + sizeof(uintptr_t)*capacity; + ArenaRegion *r = (void*)bump_pointer; // grow memory brk() style size_t current_memory_size = ARENA_WASM_PAGE_SIZE * __builtin_wasm_memory_size(0); @@ -272,7 +272,7 @@ Region *new_region(size_t capacity) return r; } -void free_region(Region *r) +void free_region(ArenaRegion *r) { // Since ARENA_BACKEND_WASM_HEAPBASE uses a primitive bump allocator to // allocate the regions, free_region() does nothing. It is generally @@ -404,7 +404,7 @@ Arena_Mark arena_snapshot(Arena *a) void arena_reset(Arena *a) { - for (Region *r = a->begin; r != NULL; r = r->next) { + for (ArenaRegion *r = a->begin; r != NULL; r = r->next) { r->count = 0; } @@ -419,7 +419,7 @@ void arena_rewind(Arena *a, Arena_Mark m) } m.region->count = m.count; - for (Region *r = m.region->next; r != NULL; r = r->next) { + for (ArenaRegion *r = m.region->next; r != NULL; r = r->next) { r->count = 0; } @@ -428,9 +428,9 @@ void arena_rewind(Arena *a, Arena_Mark m) void arena_free(Arena *a) { - Region *r = a->begin; + ArenaRegion *r = a->begin; while (r) { - Region *r0 = r; + ArenaRegion *r0 = r; r = r->next; free_region(r0); } @@ -439,9 +439,9 @@ void arena_free(Arena *a) } void arena_trim(Arena *a){ - Region *r = a->end->next; + ArenaRegion *r = a->end->next; while (r) { - Region *r0 = r; + ArenaRegion *r0 = r; r = r->next; free_region(r0); } diff --git a/third-party/fontstash/fontstash.h b/third-party/fontstash/fontstash.h new file mode 100644 index 0000000..f276af1 --- /dev/null +++ b/third-party/fontstash/fontstash.h @@ -0,0 +1,1770 @@ +// +// Copyright (c) 2009-2013 Mikko Mononen memon@inside.org +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. +// + +#ifndef FONS_H +#define FONS_H + +#ifdef __cplusplus +extern "C" { +#endif + +// To make the implementation private to the file that generates the implementation +#ifdef FONS_STATIC +#define FONS_DEF static +#else +#define FONS_DEF extern +#endif + +#define FONS_INVALID -1 + +enum FONSflags { + FONS_ZERO_TOPLEFT = 1, + FONS_ZERO_BOTTOMLEFT = 2, +}; + +enum FONSalign { + // Horizontal align + FONS_ALIGN_LEFT = 1<<0, // Default + FONS_ALIGN_CENTER = 1<<1, + FONS_ALIGN_RIGHT = 1<<2, + // Vertical align + FONS_ALIGN_TOP = 1<<3, + FONS_ALIGN_MIDDLE = 1<<4, + FONS_ALIGN_BOTTOM = 1<<5, + FONS_ALIGN_BASELINE = 1<<6, // Default +}; + +enum FONSerrorCode { + // Font atlas is full. + FONS_ATLAS_FULL = 1, + // Scratch memory used to render glyphs is full, requested size reported in 'val', you may need to bump up FONS_SCRATCH_BUF_SIZE. + FONS_SCRATCH_FULL = 2, + // Calls to fonsPushState has created too large stack, if you need deep state stack bump up FONS_MAX_STATES. + FONS_STATES_OVERFLOW = 3, + // Trying to pop too many states fonsPopState(). + FONS_STATES_UNDERFLOW = 4, +}; + +struct FONSparams { + int width, height; + unsigned char flags; + void* userPtr; + int (*renderCreate)(void* uptr, int width, int height); + int (*renderResize)(void* uptr, int width, int height); + void (*renderUpdate)(void* uptr, int* rect, const unsigned char* data); + void (*renderDraw)(void* uptr, const float* verts, const float* tcoords, const unsigned int* colors, int nverts); + void (*renderDelete)(void* uptr); +}; +typedef struct FONSparams FONSparams; + +struct FONSquad +{ + float x0,y0,s0,t0; + float x1,y1,s1,t1; +}; +typedef struct FONSquad FONSquad; + +struct FONStextIter { + float x, y, nextx, nexty, scale, spacing; + unsigned int codepoint; + short isize, iblur; + struct FONSfont* font; + int prevGlyphIndex; + const char* str; + const char* next; + const char* end; + unsigned int utf8state; +}; +typedef struct FONStextIter FONStextIter; + +typedef struct FONScontext FONScontext; + +// Contructor and destructor. +FONS_DEF FONScontext* fonsCreateInternal(FONSparams* params); +FONS_DEF void fonsDeleteInternal(FONScontext* s); + +FONS_DEF void fonsSetErrorCallback(FONScontext* s, void (*callback)(void* uptr, int error, int val), void* uptr); +// Returns current atlas size. +FONS_DEF void fonsGetAtlasSize(FONScontext* s, int* width, int* height); +// Expands the atlas size. +FONS_DEF int fonsExpandAtlas(FONScontext* s, int width, int height); +// Resets the whole stash. +FONS_DEF int fonsResetAtlas(FONScontext* stash, int width, int height); + +// Add fonts +FONS_DEF int fonsAddFont(FONScontext* s, const char* name, const char* path); +FONS_DEF int fonsAddFontMem(FONScontext* s, const char* name, unsigned char* data, int ndata, int freeData); +FONS_DEF int fonsGetFontByName(FONScontext* s, const char* name); +FONS_DEF int fonsAddFallbackFont(FONScontext* stash, int base, int fallback); + +// State handling +FONS_DEF void fonsPushState(FONScontext* s); +FONS_DEF void fonsPopState(FONScontext* s); +FONS_DEF void fonsClearState(FONScontext* s); + +// State setting +FONS_DEF void fonsSetSize(FONScontext* s, float size); +FONS_DEF void fonsSetColor(FONScontext* s, unsigned int color); +FONS_DEF void fonsSetSpacing(FONScontext* s, float spacing); +FONS_DEF void fonsSetBlur(FONScontext* s, float blur); +FONS_DEF void fonsSetAlign(FONScontext* s, int align); +FONS_DEF void fonsSetFont(FONScontext* s, int font); + +// Draw text +FONS_DEF float fonsDrawText(FONScontext* s, float x, float y, const char* string, const char* end); + +// Measure text +FONS_DEF float fonsTextBounds(FONScontext* s, float x, float y, const char* string, const char* end, float* bounds); +FONS_DEF void fonsLineBounds(FONScontext* s, float y, float* miny, float* maxy); +FONS_DEF void fonsVertMetrics(FONScontext* s, float* ascender, float* descender, float* lineh); + +// Text iterator +FONS_DEF int fonsTextIterInit(FONScontext* stash, FONStextIter* iter, float x, float y, const char* str, const char* end); +FONS_DEF int fonsTextIterNext(FONScontext* stash, FONStextIter* iter, struct FONSquad* quad); + +// Pull texture changes +FONS_DEF const unsigned char* fonsGetTextureData(FONScontext* stash, int* width, int* height); +FONS_DEF int fonsValidateTexture(FONScontext* s, int* dirty); + +// Draws the stash texture for debugging +FONS_DEF void fonsDrawDebug(FONScontext* s, float x, float y); + +#ifdef __cplusplus +} +#endif + +#endif // FONS_H + + +#ifdef FONTSTASH_IMPLEMENTATION + +#define FONS_NOTUSED(v) (void)sizeof(v) + +#ifdef FONS_USE_FREETYPE + +#include +#include FT_FREETYPE_H +#include FT_ADVANCES_H +#include + +struct FONSttFontImpl { + FT_Face font; +}; +typedef struct FONSttFontImpl FONSttFontImpl; + +static FT_Library ftLibrary; + +static int fons__tt_init() +{ + FT_Error ftError; + FONS_NOTUSED(context); + ftError = FT_Init_FreeType(&ftLibrary); + return ftError == 0; +} + +static int fons__tt_loadFont(FONScontext *context, FONSttFontImpl *font, unsigned char *data, int dataSize) +{ + FT_Error ftError; + FONS_NOTUSED(context); + + //font->font.userdata = stash; + ftError = FT_New_Memory_Face(ftLibrary, (const FT_Byte*)data, dataSize, 0, &font->font); + return ftError == 0; +} + +static void fons__tt_getFontVMetrics(FONSttFontImpl *font, int *ascent, int *descent, int *lineGap) +{ + *ascent = font->font->ascender; + *descent = font->font->descender; + *lineGap = font->font->height - (*ascent - *descent); +} + +static float fons__tt_getPixelHeightScale(FONSttFontImpl *font, float size) +{ + return size / (font->font->ascender - font->font->descender); +} + +static int fons__tt_getGlyphIndex(FONSttFontImpl *font, int codepoint) +{ + return FT_Get_Char_Index(font->font, codepoint); +} + +static int fons__tt_buildGlyphBitmap(FONSttFontImpl *font, int glyph, float size, float scale, + int *advance, int *lsb, int *x0, int *y0, int *x1, int *y1) +{ + FT_Error ftError; + FT_GlyphSlot ftGlyph; + FT_Fixed advFixed; + FONS_NOTUSED(scale); + + ftError = FT_Set_Pixel_Sizes(font->font, 0, (FT_UInt)(size * (float)font->font->units_per_EM / (float)(font->font->ascender - font->font->descender))); + if (ftError) return 0; + ftError = FT_Load_Glyph(font->font, glyph, FT_LOAD_RENDER); + if (ftError) return 0; + ftError = FT_Get_Advance(font->font, glyph, FT_LOAD_NO_SCALE, &advFixed); + if (ftError) return 0; + ftGlyph = font->font->glyph; + *advance = (int)advFixed; + *lsb = (int)ftGlyph->metrics.horiBearingX; + *x0 = ftGlyph->bitmap_left; + *x1 = *x0 + ftGlyph->bitmap.width; + *y0 = -ftGlyph->bitmap_top; + *y1 = *y0 + ftGlyph->bitmap.rows; + return 1; +} + +static void fons__tt_renderGlyphBitmap(FONSttFontImpl *font, unsigned char *output, int outWidth, int outHeight, int outStride, + float scaleX, float scaleY, int glyph) +{ + FT_GlyphSlot ftGlyph = font->font->glyph; + int ftGlyphOffset = 0; + int x, y; + FONS_NOTUSED(outWidth); + FONS_NOTUSED(outHeight); + FONS_NOTUSED(scaleX); + FONS_NOTUSED(scaleY); + FONS_NOTUSED(glyph); // glyph has already been loaded by fons__tt_buildGlyphBitmap + + for ( y = 0; y < ftGlyph->bitmap.rows; y++ ) { + for ( x = 0; x < ftGlyph->bitmap.width; x++ ) { + output[(y * outStride) + x] = ftGlyph->bitmap.buffer[ftGlyphOffset++]; + } + } +} + +static int fons__tt_getGlyphKernAdvance(FONSttFontImpl *font, int glyph1, int glyph2) +{ + FT_Vector ftKerning; + FT_Get_Kerning(font->font, glyph1, glyph2, FT_KERNING_DEFAULT, &ftKerning); + return (int)((ftKerning.x + 32) >> 6); // Round up and convert to integer +} + +#else + +#define STB_TRUETYPE_IMPLEMENTATION +#define STBTT_STATIC +static void* fons__tmpalloc(size_t size, void* up); +static void fons__tmpfree(void* ptr, void* up); +#define STBTT_malloc(x,u) fons__tmpalloc(x,u) +#define STBTT_free(x,u) fons__tmpfree(x,u) +#include "stb_truetype.h" + +struct FONSttFontImpl { + stbtt_fontinfo font; +}; +typedef struct FONSttFontImpl FONSttFontImpl; + +static int fons__tt_init(FONScontext *context) +{ + FONS_NOTUSED(context); + return 1; +} + +static int fons__tt_loadFont(FONScontext *context, FONSttFontImpl *font, unsigned char *data, int dataSize) +{ + int stbError; + FONS_NOTUSED(dataSize); + + font->font.userdata = context; + stbError = stbtt_InitFont(&font->font, data, 0); + return stbError; +} + +static void fons__tt_getFontVMetrics(FONSttFontImpl *font, int *ascent, int *descent, int *lineGap) +{ + stbtt_GetFontVMetrics(&font->font, ascent, descent, lineGap); +} + +static float fons__tt_getPixelHeightScale(FONSttFontImpl *font, float size) +{ + return stbtt_ScaleForPixelHeight(&font->font, size); +} + +static int fons__tt_getGlyphIndex(FONSttFontImpl *font, int codepoint) +{ + return stbtt_FindGlyphIndex(&font->font, codepoint); +} + +static int fons__tt_buildGlyphBitmap(FONSttFontImpl *font, int glyph, float size, float scale, + int *advance, int *lsb, int *x0, int *y0, int *x1, int *y1) +{ + FONS_NOTUSED(size); + stbtt_GetGlyphHMetrics(&font->font, glyph, advance, lsb); + stbtt_GetGlyphBitmapBox(&font->font, glyph, scale, scale, x0, y0, x1, y1); + return 1; +} + +static void fons__tt_renderGlyphBitmap(FONSttFontImpl *font, unsigned char *output, int outWidth, int outHeight, int outStride, + float scaleX, float scaleY, int glyph) +{ + stbtt_MakeGlyphBitmap(&font->font, output, outWidth, outHeight, outStride, scaleX, scaleY, glyph); +} + +static int fons__tt_getGlyphKernAdvance(FONSttFontImpl *font, int glyph1, int glyph2) +{ + return stbtt_GetGlyphKernAdvance(&font->font, glyph1, glyph2); +} + +#endif + +#ifndef FONS_SCRATCH_BUF_SIZE +# define FONS_SCRATCH_BUF_SIZE 64000 +#endif +#ifndef FONS_HASH_LUT_SIZE +# define FONS_HASH_LUT_SIZE 256 +#endif +#ifndef FONS_INIT_FONTS +# define FONS_INIT_FONTS 4 +#endif +#ifndef FONS_INIT_GLYPHS +# define FONS_INIT_GLYPHS 256 +#endif +#ifndef FONS_INIT_ATLAS_NODES +# define FONS_INIT_ATLAS_NODES 256 +#endif +#ifndef FONS_VERTEX_COUNT +# define FONS_VERTEX_COUNT 1024 +#endif +#ifndef FONS_MAX_STATES +# define FONS_MAX_STATES 20 +#endif +#ifndef FONS_MAX_FALLBACKS +# define FONS_MAX_FALLBACKS 20 +#endif + +static unsigned int fons__hashint(unsigned int a) +{ + a += ~(a<<15); + a ^= (a>>10); + a += (a<<3); + a ^= (a>>6); + a += ~(a<<11); + a ^= (a>>16); + return a; +} + +static int fons__mini(int a, int b) +{ + return a < b ? a : b; +} + +static int fons__maxi(int a, int b) +{ + return a > b ? a : b; +} + +struct FONSglyph +{ + unsigned int codepoint; + int index; + int next; + short size, blur; + short x0,y0,x1,y1; + short xadv,xoff,yoff; +}; +typedef struct FONSglyph FONSglyph; + +struct FONSfont +{ + FONSttFontImpl font; + char name[64]; + unsigned char* data; + int dataSize; + unsigned char freeData; + float ascender; + float descender; + float lineh; + FONSglyph* glyphs; + int cglyphs; + int nglyphs; + int lut[FONS_HASH_LUT_SIZE]; + int fallbacks[FONS_MAX_FALLBACKS]; + int nfallbacks; +}; +typedef struct FONSfont FONSfont; + +struct FONSstate +{ + int font; + int align; + float size; + unsigned int color; + float blur; + float spacing; +}; +typedef struct FONSstate FONSstate; + +struct FONSatlasNode { + short x, y, width; +}; +typedef struct FONSatlasNode FONSatlasNode; + +struct FONSatlas +{ + int width, height; + FONSatlasNode* nodes; + int nnodes; + int cnodes; +}; +typedef struct FONSatlas FONSatlas; + +struct FONScontext +{ + FONSparams params; + float itw,ith; + unsigned char* texData; + int dirtyRect[4]; + FONSfont** fonts; + FONSatlas* atlas; + int cfonts; + int nfonts; + float verts[FONS_VERTEX_COUNT*2]; + float tcoords[FONS_VERTEX_COUNT*2]; + unsigned int colors[FONS_VERTEX_COUNT]; + int nverts; + unsigned char* scratch; + int nscratch; + FONSstate states[FONS_MAX_STATES]; + int nstates; + void (*handleError)(void* uptr, int error, int val); + void* errorUptr; +}; + +#ifdef STB_TRUETYPE_IMPLEMENTATION + +static void* fons__tmpalloc(size_t size, void* up) +{ + unsigned char* ptr; + FONScontext* stash = (FONScontext*)up; + + // 16-byte align the returned pointer + size = (size + 0xf) & ~0xf; + + if (stash->nscratch+(int)size > FONS_SCRATCH_BUF_SIZE) { + if (stash->handleError) + stash->handleError(stash->errorUptr, FONS_SCRATCH_FULL, stash->nscratch+(int)size); + return NULL; + } + ptr = stash->scratch + stash->nscratch; + stash->nscratch += (int)size; + return ptr; +} + +static void fons__tmpfree(void* ptr, void* up) +{ + (void)ptr; + (void)up; + // empty +} + +#endif // STB_TRUETYPE_IMPLEMENTATION + +// Copyright (c) 2008-2010 Bjoern Hoehrmann +// See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details. + +#define FONS_UTF8_ACCEPT 0 +#define FONS_UTF8_REJECT 12 + +static unsigned int fons__decutf8(unsigned int* state, unsigned int* codep, unsigned int byte) +{ + static const unsigned char utf8d[] = { + // The first part of the table maps bytes to character classes that + // to reduce the size of the transition table and create bitmasks. + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, + 10,3,3,3,3,3,3,3,3,3,3,3,3,4,3,3, 11,6,6,6,5,8,8,8,8,8,8,8,8,8,8,8, + + // The second part is a transition table that maps a combination + // of a state of the automaton and a character class to a state. + 0,12,24,36,60,96,84,12,12,12,48,72, 12,12,12,12,12,12,12,12,12,12,12,12, + 12, 0,12,12,12,12,12, 0,12, 0,12,12, 12,24,12,12,12,12,12,24,12,24,12,12, + 12,12,12,12,12,12,12,24,12,12,12,12, 12,24,12,12,12,12,12,12,12,24,12,12, + 12,12,12,12,12,12,12,36,12,36,12,12, 12,36,12,12,12,12,12,36,12,36,12,12, + 12,36,12,12,12,12,12,12,12,12,12,12, + }; + + unsigned int type = utf8d[byte]; + + *codep = (*state != FONS_UTF8_ACCEPT) ? + (byte & 0x3fu) | (*codep << 6) : + (0xff >> type) & (byte); + + *state = utf8d[256 + *state + type]; + return *state; +} + +// Atlas based on Skyline Bin Packer by Jukka Jylänki + +static void fons__deleteAtlas(FONSatlas* atlas) +{ + if (atlas == NULL) return; + if (atlas->nodes != NULL) free(atlas->nodes); + free(atlas); +} + +static FONSatlas* fons__allocAtlas(int w, int h, int nnodes) +{ + FONSatlas* atlas = NULL; + + // Allocate memory for the font stash. + atlas = (FONSatlas*)malloc(sizeof(FONSatlas)); + if (atlas == NULL) goto error; + memset(atlas, 0, sizeof(FONSatlas)); + + atlas->width = w; + atlas->height = h; + + // Allocate space for skyline nodes + atlas->nodes = (FONSatlasNode*)malloc(sizeof(FONSatlasNode) * nnodes); + if (atlas->nodes == NULL) goto error; + memset(atlas->nodes, 0, sizeof(FONSatlasNode) * nnodes); + atlas->nnodes = 0; + atlas->cnodes = nnodes; + + // Init root node. + atlas->nodes[0].x = 0; + atlas->nodes[0].y = 0; + atlas->nodes[0].width = (short)w; + atlas->nnodes++; + + return atlas; + +error: + if (atlas) fons__deleteAtlas(atlas); + return NULL; +} + +static int fons__atlasInsertNode(FONSatlas* atlas, int idx, int x, int y, int w) +{ + int i; + // Insert node + if (atlas->nnodes+1 > atlas->cnodes) { + atlas->cnodes = atlas->cnodes == 0 ? 8 : atlas->cnodes * 2; + atlas->nodes = (FONSatlasNode*)realloc(atlas->nodes, sizeof(FONSatlasNode) * atlas->cnodes); + if (atlas->nodes == NULL) + return 0; + } + for (i = atlas->nnodes; i > idx; i--) + atlas->nodes[i] = atlas->nodes[i-1]; + atlas->nodes[idx].x = (short)x; + atlas->nodes[idx].y = (short)y; + atlas->nodes[idx].width = (short)w; + atlas->nnodes++; + + return 1; +} + +static void fons__atlasRemoveNode(FONSatlas* atlas, int idx) +{ + int i; + if (atlas->nnodes == 0) return; + for (i = idx; i < atlas->nnodes-1; i++) + atlas->nodes[i] = atlas->nodes[i+1]; + atlas->nnodes--; +} + +static void fons__atlasExpand(FONSatlas* atlas, int w, int h) +{ + // Insert node for empty space + if (w > atlas->width) + fons__atlasInsertNode(atlas, atlas->nnodes, atlas->width, 0, w - atlas->width); + atlas->width = w; + atlas->height = h; +} + +static void fons__atlasReset(FONSatlas* atlas, int w, int h) +{ + atlas->width = w; + atlas->height = h; + atlas->nnodes = 0; + + // Init root node. + atlas->nodes[0].x = 0; + atlas->nodes[0].y = 0; + atlas->nodes[0].width = (short)w; + atlas->nnodes++; +} + +static int fons__atlasAddSkylineLevel(FONSatlas* atlas, int idx, int x, int y, int w, int h) +{ + int i; + + // Insert new node + if (fons__atlasInsertNode(atlas, idx, x, y+h, w) == 0) + return 0; + + // Delete skyline segments that fall under the shadow of the new segment. + for (i = idx+1; i < atlas->nnodes; i++) { + if (atlas->nodes[i].x < atlas->nodes[i-1].x + atlas->nodes[i-1].width) { + int shrink = atlas->nodes[i-1].x + atlas->nodes[i-1].width - atlas->nodes[i].x; + atlas->nodes[i].x += (short)shrink; + atlas->nodes[i].width -= (short)shrink; + if (atlas->nodes[i].width <= 0) { + fons__atlasRemoveNode(atlas, i); + i--; + } else { + break; + } + } else { + break; + } + } + + // Merge same height skyline segments that are next to each other. + for (i = 0; i < atlas->nnodes-1; i++) { + if (atlas->nodes[i].y == atlas->nodes[i+1].y) { + atlas->nodes[i].width += atlas->nodes[i+1].width; + fons__atlasRemoveNode(atlas, i+1); + i--; + } + } + + return 1; +} + +static int fons__atlasRectFits(FONSatlas* atlas, int i, int w, int h) +{ + // Checks if there is enough space at the location of skyline span 'i', + // and return the max height of all skyline spans under that at that location, + // (think tetris block being dropped at that position). Or -1 if no space found. + int x = atlas->nodes[i].x; + int y = atlas->nodes[i].y; + int spaceLeft; + if (x + w > atlas->width) + return -1; + spaceLeft = w; + while (spaceLeft > 0) { + if (i == atlas->nnodes) return -1; + y = fons__maxi(y, atlas->nodes[i].y); + if (y + h > atlas->height) return -1; + spaceLeft -= atlas->nodes[i].width; + ++i; + } + return y; +} + +static int fons__atlasAddRect(FONSatlas* atlas, int rw, int rh, int* rx, int* ry) +{ + int besth = atlas->height, bestw = atlas->width, besti = -1; + int bestx = -1, besty = -1, i; + + // Bottom left fit heuristic. + for (i = 0; i < atlas->nnodes; i++) { + int y = fons__atlasRectFits(atlas, i, rw, rh); + if (y != -1) { + if (y + rh < besth || (y + rh == besth && atlas->nodes[i].width < bestw)) { + besti = i; + bestw = atlas->nodes[i].width; + besth = y + rh; + bestx = atlas->nodes[i].x; + besty = y; + } + } + } + + if (besti == -1) + return 0; + + // Perform the actual packing. + if (fons__atlasAddSkylineLevel(atlas, besti, bestx, besty, rw, rh) == 0) + return 0; + + *rx = bestx; + *ry = besty; + + return 1; +} + +static void fons__addWhiteRect(FONScontext* stash, int w, int h) +{ + int x, y, gx, gy; + unsigned char* dst; + if (fons__atlasAddRect(stash->atlas, w, h, &gx, &gy) == 0) + return; + + // Rasterize + dst = &stash->texData[gx + gy * stash->params.width]; + for (y = 0; y < h; y++) { + for (x = 0; x < w; x++) + dst[x] = 0xff; + dst += stash->params.width; + } + + stash->dirtyRect[0] = fons__mini(stash->dirtyRect[0], gx); + stash->dirtyRect[1] = fons__mini(stash->dirtyRect[1], gy); + stash->dirtyRect[2] = fons__maxi(stash->dirtyRect[2], gx+w); + stash->dirtyRect[3] = fons__maxi(stash->dirtyRect[3], gy+h); +} + +FONScontext* fonsCreateInternal(FONSparams* params) +{ + FONScontext* stash = NULL; + + // Allocate memory for the font stash. + stash = (FONScontext*)malloc(sizeof(FONScontext)); + if (stash == NULL) goto error; + memset(stash, 0, sizeof(FONScontext)); + + stash->params = *params; + + // Allocate scratch buffer. + stash->scratch = (unsigned char*)malloc(FONS_SCRATCH_BUF_SIZE); + if (stash->scratch == NULL) goto error; + + // Initialize implementation library + if (!fons__tt_init(stash)) goto error; + + if (stash->params.renderCreate != NULL) { + if (stash->params.renderCreate(stash->params.userPtr, stash->params.width, stash->params.height) == 0) + goto error; + } + + stash->atlas = fons__allocAtlas(stash->params.width, stash->params.height, FONS_INIT_ATLAS_NODES); + if (stash->atlas == NULL) goto error; + + // Allocate space for fonts. + stash->fonts = (FONSfont**)malloc(sizeof(FONSfont*) * FONS_INIT_FONTS); + if (stash->fonts == NULL) goto error; + memset(stash->fonts, 0, sizeof(FONSfont*) * FONS_INIT_FONTS); + stash->cfonts = FONS_INIT_FONTS; + stash->nfonts = 0; + + // Create texture for the cache. + stash->itw = 1.0f/stash->params.width; + stash->ith = 1.0f/stash->params.height; + stash->texData = (unsigned char*)malloc(stash->params.width * stash->params.height); + if (stash->texData == NULL) goto error; + memset(stash->texData, 0, stash->params.width * stash->params.height); + + stash->dirtyRect[0] = stash->params.width; + stash->dirtyRect[1] = stash->params.height; + stash->dirtyRect[2] = 0; + stash->dirtyRect[3] = 0; + + // Add white rect at 0,0 for debug drawing. + fons__addWhiteRect(stash, 2,2); + + fonsPushState(stash); + fonsClearState(stash); + + return stash; + +error: + fonsDeleteInternal(stash); + return NULL; +} + +static FONSstate* fons__getState(FONScontext* stash) +{ + return &stash->states[stash->nstates-1]; +} + +int fonsAddFallbackFont(FONScontext* stash, int base, int fallback) +{ + FONSfont* baseFont = stash->fonts[base]; + if (baseFont->nfallbacks < FONS_MAX_FALLBACKS) { + baseFont->fallbacks[baseFont->nfallbacks++] = fallback; + return 1; + } + return 0; +} + +void fonsSetSize(FONScontext* stash, float size) +{ + fons__getState(stash)->size = size; +} + +void fonsSetColor(FONScontext* stash, unsigned int color) +{ + fons__getState(stash)->color = color; +} + +void fonsSetSpacing(FONScontext* stash, float spacing) +{ + fons__getState(stash)->spacing = spacing; +} + +void fonsSetBlur(FONScontext* stash, float blur) +{ + fons__getState(stash)->blur = blur; +} + +void fonsSetAlign(FONScontext* stash, int align) +{ + fons__getState(stash)->align = align; +} + +void fonsSetFont(FONScontext* stash, int font) +{ + fons__getState(stash)->font = font; +} + +void fonsPushState(FONScontext* stash) +{ + if (stash->nstates >= FONS_MAX_STATES) { + if (stash->handleError) + stash->handleError(stash->errorUptr, FONS_STATES_OVERFLOW, 0); + return; + } + if (stash->nstates > 0) + memcpy(&stash->states[stash->nstates], &stash->states[stash->nstates-1], sizeof(FONSstate)); + stash->nstates++; +} + +void fonsPopState(FONScontext* stash) +{ + if (stash->nstates <= 1) { + if (stash->handleError) + stash->handleError(stash->errorUptr, FONS_STATES_UNDERFLOW, 0); + return; + } + stash->nstates--; +} + +void fonsClearState(FONScontext* stash) +{ + FONSstate* state = fons__getState(stash); + state->size = 12.0f; + state->color = 0xffffffff; + state->font = 0; + state->blur = 0; + state->spacing = 0; + state->align = FONS_ALIGN_LEFT | FONS_ALIGN_BASELINE; +} + +static void fons__freeFont(FONSfont* font) +{ + if (font == NULL) return; + if (font->glyphs) free(font->glyphs); + if (font->freeData && font->data) free(font->data); + free(font); +} + +static int fons__allocFont(FONScontext* stash) +{ + FONSfont* font = NULL; + if (stash->nfonts+1 > stash->cfonts) { + stash->cfonts = stash->cfonts == 0 ? 8 : stash->cfonts * 2; + stash->fonts = (FONSfont**)realloc(stash->fonts, sizeof(FONSfont*) * stash->cfonts); + if (stash->fonts == NULL) + return -1; + } + font = (FONSfont*)malloc(sizeof(FONSfont)); + if (font == NULL) goto error; + memset(font, 0, sizeof(FONSfont)); + + font->glyphs = (FONSglyph*)malloc(sizeof(FONSglyph) * FONS_INIT_GLYPHS); + if (font->glyphs == NULL) goto error; + font->cglyphs = FONS_INIT_GLYPHS; + font->nglyphs = 0; + + stash->fonts[stash->nfonts++] = font; + return stash->nfonts-1; + +error: + fons__freeFont(font); + + return FONS_INVALID; +} + +static FILE* fons__fopen(const char* filename, const char* mode) +{ +#ifdef _WIN32 + int len = 0; + int fileLen = strlen(filename); + int modeLen = strlen(mode); + wchar_t wpath[MAX_PATH]; + wchar_t wmode[MAX_PATH]; + FILE* f; + + if (fileLen == 0) + return NULL; + if (modeLen == 0) + return NULL; + len = MultiByteToWideChar(CP_UTF8, 0, filename, fileLen, wpath, fileLen); + if (len >= MAX_PATH) + return NULL; + wpath[len] = L'\0'; + len = MultiByteToWideChar(CP_UTF8, 0, mode, modeLen, wmode, modeLen); + if (len >= MAX_PATH) + return NULL; + wmode[len] = L'\0'; + f = _wfopen(wpath, wmode); + return f; +#else + return fopen(filename, mode); +#endif +} + +int fonsAddFont(FONScontext* stash, const char* name, const char* path) +{ + FILE* fp = 0; + int dataSize = 0, readed; + unsigned char* data = NULL; + + // Read in the font data. + fp = fons__fopen(path, "rb"); + if (fp == NULL) goto error; + fseek(fp,0,SEEK_END); + dataSize = (int)ftell(fp); + fseek(fp,0,SEEK_SET); + data = (unsigned char*)malloc(dataSize); + if (data == NULL) goto error; + readed = fread(data, 1, dataSize, fp); + fclose(fp); + fp = 0; + if (readed != dataSize) goto error; + + return fonsAddFontMem(stash, name, data, dataSize, 1); + +error: + if (data) free(data); + if (fp) fclose(fp); + return FONS_INVALID; +} + +int fonsAddFontMem(FONScontext* stash, const char* name, unsigned char* data, int dataSize, int freeData) +{ + int i, ascent, descent, fh, lineGap; + FONSfont* font; + + int idx = fons__allocFont(stash); + if (idx == FONS_INVALID) + return FONS_INVALID; + + font = stash->fonts[idx]; + + strncpy(font->name, name, sizeof(font->name)); + font->name[sizeof(font->name)-1] = '\0'; + + // Init hash lookup. + for (i = 0; i < FONS_HASH_LUT_SIZE; ++i) + font->lut[i] = -1; + + // Read in the font data. + font->dataSize = dataSize; + font->data = data; + font->freeData = (unsigned char)freeData; + + // Init font + stash->nscratch = 0; + if (!fons__tt_loadFont(stash, &font->font, data, dataSize)) goto error; + + // Store normalized line height. The real line height is got + // by multiplying the lineh by font size. + fons__tt_getFontVMetrics( &font->font, &ascent, &descent, &lineGap); + fh = ascent - descent; + font->ascender = (float)ascent / (float)fh; + font->descender = (float)descent / (float)fh; + font->lineh = (float)(fh + lineGap) / (float)fh; + + return idx; + +error: + fons__freeFont(font); + stash->nfonts--; + return FONS_INVALID; +} + +int fonsGetFontByName(FONScontext* s, const char* name) +{ + int i; + for (i = 0; i < s->nfonts; i++) { + if (strcmp(s->fonts[i]->name, name) == 0) + return i; + } + return FONS_INVALID; +} + + +static FONSglyph* fons__allocGlyph(FONSfont* font) +{ + if (font->nglyphs+1 > font->cglyphs) { + font->cglyphs = font->cglyphs == 0 ? 8 : font->cglyphs * 2; + font->glyphs = (FONSglyph*)realloc(font->glyphs, sizeof(FONSglyph) * font->cglyphs); + if (font->glyphs == NULL) return NULL; + } + font->nglyphs++; + return &font->glyphs[font->nglyphs-1]; +} + + +// Based on Exponential blur, Jani Huhtanen, 2006 + +#define APREC 16 +#define ZPREC 7 + +static void fons__blurCols(unsigned char* dst, int w, int h, int dstStride, int alpha) +{ + int x, y; + for (y = 0; y < h; y++) { + int z = 0; // force zero border + for (x = 1; x < w; x++) { + z += (alpha * (((int)(dst[x]) << ZPREC) - z)) >> APREC; + dst[x] = (unsigned char)(z >> ZPREC); + } + dst[w-1] = 0; // force zero border + z = 0; + for (x = w-2; x >= 0; x--) { + z += (alpha * (((int)(dst[x]) << ZPREC) - z)) >> APREC; + dst[x] = (unsigned char)(z >> ZPREC); + } + dst[0] = 0; // force zero border + dst += dstStride; + } +} + +static void fons__blurRows(unsigned char* dst, int w, int h, int dstStride, int alpha) +{ + int x, y; + for (x = 0; x < w; x++) { + int z = 0; // force zero border + for (y = dstStride; y < h*dstStride; y += dstStride) { + z += (alpha * (((int)(dst[y]) << ZPREC) - z)) >> APREC; + dst[y] = (unsigned char)(z >> ZPREC); + } + dst[(h-1)*dstStride] = 0; // force zero border + z = 0; + for (y = (h-2)*dstStride; y >= 0; y -= dstStride) { + z += (alpha * (((int)(dst[y]) << ZPREC) - z)) >> APREC; + dst[y] = (unsigned char)(z >> ZPREC); + } + dst[0] = 0; // force zero border + dst++; + } +} + + +static void fons__blur(FONScontext* stash, unsigned char* dst, int w, int h, int dstStride, int blur) +{ + int alpha; + float sigma; + (void)stash; + + if (blur < 1) + return; + // Calculate the alpha such that 90% of the kernel is within the radius. (Kernel extends to infinity) + sigma = (float)blur * 0.57735f; // 1 / sqrt(3) + alpha = (int)((1< 20) iblur = 20; + pad = iblur+2; + + // Reset allocator. + stash->nscratch = 0; + + // Find code point and size. + h = fons__hashint(codepoint) & (FONS_HASH_LUT_SIZE-1); + i = font->lut[h]; + while (i != -1) { + if (font->glyphs[i].codepoint == codepoint && font->glyphs[i].size == isize && font->glyphs[i].blur == iblur) + return &font->glyphs[i]; + i = font->glyphs[i].next; + } + + // Could not find glyph, create it. + g = fons__tt_getGlyphIndex(&font->font, codepoint); + // Try to find the glyph in fallback fonts. + if (g == 0) { + for (i = 0; i < font->nfallbacks; ++i) { + FONSfont* fallbackFont = stash->fonts[font->fallbacks[i]]; + int fallbackIndex = fons__tt_getGlyphIndex(&fallbackFont->font, codepoint); + if (fallbackIndex != 0) { + g = fallbackIndex; + renderFont = fallbackFont; + break; + } + } + // It is possible that we did not find a fallback glyph. + // In that case the glyph index 'g' is 0, and we'll proceed below and cache empty glyph. + } + scale = fons__tt_getPixelHeightScale(&renderFont->font, size); + fons__tt_buildGlyphBitmap(&renderFont->font, g, size, scale, &advance, &lsb, &x0, &y0, &x1, &y1); + gw = x1-x0 + pad*2; + gh = y1-y0 + pad*2; + + // Find free spot for the rect in the atlas + added = fons__atlasAddRect(stash->atlas, gw, gh, &gx, &gy); + if (added == 0 && stash->handleError != NULL) { + // Atlas is full, let the user to resize the atlas (or not), and try again. + stash->handleError(stash->errorUptr, FONS_ATLAS_FULL, 0); + added = fons__atlasAddRect(stash->atlas, gw, gh, &gx, &gy); + } + if (added == 0) return NULL; + + // Init glyph. + glyph = fons__allocGlyph(font); + glyph->codepoint = codepoint; + glyph->size = isize; + glyph->blur = iblur; + glyph->index = g; + glyph->x0 = (short)gx; + glyph->y0 = (short)gy; + glyph->x1 = (short)(glyph->x0+gw); + glyph->y1 = (short)(glyph->y0+gh); + glyph->xadv = (short)(scale * advance * 10.0f); + glyph->xoff = (short)(x0 - pad); + glyph->yoff = (short)(y0 - pad); + glyph->next = 0; + + // Insert char to hash lookup. + glyph->next = font->lut[h]; + font->lut[h] = font->nglyphs-1; + + // Rasterize + dst = &stash->texData[(glyph->x0+pad) + (glyph->y0+pad) * stash->params.width]; + fons__tt_renderGlyphBitmap(&renderFont->font, dst, gw-pad*2,gh-pad*2, stash->params.width, scale,scale, g); + + // Make sure there is one pixel empty border. + dst = &stash->texData[glyph->x0 + glyph->y0 * stash->params.width]; + for (y = 0; y < gh; y++) { + dst[y*stash->params.width] = 0; + dst[gw-1 + y*stash->params.width] = 0; + } + for (x = 0; x < gw; x++) { + dst[x] = 0; + dst[x + (gh-1)*stash->params.width] = 0; + } + + // Debug code to color the glyph background +/* unsigned char* fdst = &stash->texData[glyph->x0 + glyph->y0 * stash->params.width]; + for (y = 0; y < gh; y++) { + for (x = 0; x < gw; x++) { + int a = (int)fdst[x+y*stash->params.width] + 20; + if (a > 255) a = 255; + fdst[x+y*stash->params.width] = a; + } + }*/ + + // Blur + if (iblur > 0) { + stash->nscratch = 0; + bdst = &stash->texData[glyph->x0 + glyph->y0 * stash->params.width]; + fons__blur(stash, bdst, gw,gh, stash->params.width, iblur); + } + + stash->dirtyRect[0] = fons__mini(stash->dirtyRect[0], glyph->x0); + stash->dirtyRect[1] = fons__mini(stash->dirtyRect[1], glyph->y0); + stash->dirtyRect[2] = fons__maxi(stash->dirtyRect[2], glyph->x1); + stash->dirtyRect[3] = fons__maxi(stash->dirtyRect[3], glyph->y1); + + return glyph; +} + +static void fons__getQuad(FONScontext* stash, FONSfont* font, + int prevGlyphIndex, FONSglyph* glyph, + float scale, float spacing, float* x, float* y, FONSquad* q) +{ + float rx,ry,xoff,yoff,x0,y0,x1,y1; + + if (prevGlyphIndex != -1) { + float adv = fons__tt_getGlyphKernAdvance(&font->font, prevGlyphIndex, glyph->index) * scale; + *x += (int)(adv + spacing + 0.5f); + } + + // Each glyph has 2px border to allow good interpolation, + // one pixel to prevent leaking, and one to allow good interpolation for rendering. + // Inset the texture region by one pixel for correct interpolation. + xoff = (short)(glyph->xoff+1); + yoff = (short)(glyph->yoff+1); + x0 = (float)(glyph->x0+1); + y0 = (float)(glyph->y0+1); + x1 = (float)(glyph->x1-1); + y1 = (float)(glyph->y1-1); + + if (stash->params.flags & FONS_ZERO_TOPLEFT) { + rx = (float)(int)(*x + xoff); + ry = (float)(int)(*y + yoff); + + q->x0 = rx; + q->y0 = ry; + q->x1 = rx + x1 - x0; + q->y1 = ry + y1 - y0; + + q->s0 = x0 * stash->itw; + q->t0 = y0 * stash->ith; + q->s1 = x1 * stash->itw; + q->t1 = y1 * stash->ith; + } else { + rx = (float)(int)(*x + xoff); + ry = (float)(int)(*y - yoff); + + q->x0 = rx; + q->y0 = ry; + q->x1 = rx + x1 - x0; + q->y1 = ry - y1 + y0; + + q->s0 = x0 * stash->itw; + q->t0 = y0 * stash->ith; + q->s1 = x1 * stash->itw; + q->t1 = y1 * stash->ith; + } + + *x += (int)(glyph->xadv / 10.0f + 0.5f); +} + +static void fons__flush(FONScontext* stash) +{ + // Flush texture + if (stash->dirtyRect[0] < stash->dirtyRect[2] && stash->dirtyRect[1] < stash->dirtyRect[3]) { + if (stash->params.renderUpdate != NULL) + stash->params.renderUpdate(stash->params.userPtr, stash->dirtyRect, stash->texData); + // Reset dirty rect + stash->dirtyRect[0] = stash->params.width; + stash->dirtyRect[1] = stash->params.height; + stash->dirtyRect[2] = 0; + stash->dirtyRect[3] = 0; + } + + // Flush triangles + if (stash->nverts > 0) { + if (stash->params.renderDraw != NULL) + stash->params.renderDraw(stash->params.userPtr, stash->verts, stash->tcoords, stash->colors, stash->nverts); + stash->nverts = 0; + } +} + +static __inline void fons__vertex(FONScontext* stash, float x, float y, float s, float t, unsigned int c) +{ + stash->verts[stash->nverts*2+0] = x; + stash->verts[stash->nverts*2+1] = y; + stash->tcoords[stash->nverts*2+0] = s; + stash->tcoords[stash->nverts*2+1] = t; + stash->colors[stash->nverts] = c; + stash->nverts++; +} + +static float fons__getVertAlign(FONScontext* stash, FONSfont* font, int align, short isize) +{ + if (stash->params.flags & FONS_ZERO_TOPLEFT) { + if (align & FONS_ALIGN_TOP) { + return font->ascender * (float)isize/10.0f; + } else if (align & FONS_ALIGN_MIDDLE) { + return (font->ascender + font->descender) / 2.0f * (float)isize/10.0f; + } else if (align & FONS_ALIGN_BASELINE) { + return 0.0f; + } else if (align & FONS_ALIGN_BOTTOM) { + return font->descender * (float)isize/10.0f; + } + } else { + if (align & FONS_ALIGN_TOP) { + return -font->ascender * (float)isize/10.0f; + } else if (align & FONS_ALIGN_MIDDLE) { + return -(font->ascender + font->descender) / 2.0f * (float)isize/10.0f; + } else if (align & FONS_ALIGN_BASELINE) { + return 0.0f; + } else if (align & FONS_ALIGN_BOTTOM) { + return -font->descender * (float)isize/10.0f; + } + } + return 0.0; +} + +FONS_DEF float fonsDrawText(FONScontext* stash, + float x, float y, + const char* str, const char* end) +{ + FONSstate* state = fons__getState(stash); + unsigned int codepoint; + unsigned int utf8state = 0; + FONSglyph* glyph = NULL; + FONSquad q; + int prevGlyphIndex = -1; + short isize = (short)(state->size*10.0f); + short iblur = (short)state->blur; + float scale; + FONSfont* font; + float width; + + if (stash == NULL) return x; + if (state->font < 0 || state->font >= stash->nfonts) return x; + font = stash->fonts[state->font]; + if (font->data == NULL) return x; + + scale = fons__tt_getPixelHeightScale(&font->font, (float)isize/10.0f); + + if (end == NULL) + end = str + strlen(str); + + // Align horizontally + if (state->align & FONS_ALIGN_LEFT) { + // empty + } else if (state->align & FONS_ALIGN_RIGHT) { + width = fonsTextBounds(stash, x,y, str, end, NULL); + x -= width; + } else if (state->align & FONS_ALIGN_CENTER) { + width = fonsTextBounds(stash, x,y, str, end, NULL); + x -= width * 0.5f; + } + // Align vertically. + y += fons__getVertAlign(stash, font, state->align, isize); + + for (; str != end; ++str) { + if (fons__decutf8(&utf8state, &codepoint, *(const unsigned char*)str)) + continue; + glyph = fons__getGlyph(stash, font, codepoint, isize, iblur); + if (glyph != NULL) { + fons__getQuad(stash, font, prevGlyphIndex, glyph, scale, state->spacing, &x, &y, &q); + + if (stash->nverts+6 > FONS_VERTEX_COUNT) + fons__flush(stash); + + fons__vertex(stash, q.x0, q.y0, q.s0, q.t0, state->color); + fons__vertex(stash, q.x1, q.y1, q.s1, q.t1, state->color); + fons__vertex(stash, q.x1, q.y0, q.s1, q.t0, state->color); + + fons__vertex(stash, q.x0, q.y0, q.s0, q.t0, state->color); + fons__vertex(stash, q.x0, q.y1, q.s0, q.t1, state->color); + fons__vertex(stash, q.x1, q.y1, q.s1, q.t1, state->color); + } + prevGlyphIndex = glyph != NULL ? glyph->index : -1; + } + fons__flush(stash); + + return x; +} + +FONS_DEF int fonsTextIterInit(FONScontext* stash, FONStextIter* iter, + float x, float y, const char* str, const char* end) +{ + FONSstate* state = fons__getState(stash); + float width; + + memset(iter, 0, sizeof(*iter)); + + if (stash == NULL) return 0; + if (state->font < 0 || state->font >= stash->nfonts) return 0; + iter->font = stash->fonts[state->font]; + if (iter->font->data == NULL) return 0; + + iter->isize = (short)(state->size*10.0f); + iter->iblur = (short)state->blur; + iter->scale = fons__tt_getPixelHeightScale(&iter->font->font, (float)iter->isize/10.0f); + + // Align horizontally + if (state->align & FONS_ALIGN_LEFT) { + // empty + } else if (state->align & FONS_ALIGN_RIGHT) { + width = fonsTextBounds(stash, x,y, str, end, NULL); + x -= width; + } else if (state->align & FONS_ALIGN_CENTER) { + width = fonsTextBounds(stash, x,y, str, end, NULL); + x -= width * 0.5f; + } + // Align vertically. + y += fons__getVertAlign(stash, iter->font, state->align, iter->isize); + + if (end == NULL) + end = str + strlen(str); + + iter->x = iter->nextx = x; + iter->y = iter->nexty = y; + iter->spacing = state->spacing; + iter->str = str; + iter->next = str; + iter->end = end; + iter->codepoint = 0; + iter->prevGlyphIndex = -1; + + return 1; +} + +FONS_DEF int fonsTextIterNext(FONScontext* stash, FONStextIter* iter, FONSquad* quad) +{ + FONSglyph* glyph = NULL; + const char* str = iter->next; + iter->str = iter->next; + + if (str == iter->end) + return 0; + + for (; str != iter->end; str++) { + if (fons__decutf8(&iter->utf8state, &iter->codepoint, *(const unsigned char*)str)) + continue; + str++; + // Get glyph and quad + iter->x = iter->nextx; + iter->y = iter->nexty; + glyph = fons__getGlyph(stash, iter->font, iter->codepoint, iter->isize, iter->iblur); + if (glyph != NULL) + fons__getQuad(stash, iter->font, iter->prevGlyphIndex, glyph, iter->scale, iter->spacing, &iter->nextx, &iter->nexty, quad); + iter->prevGlyphIndex = glyph != NULL ? glyph->index : -1; + break; + } + iter->next = str; + + return 1; +} + +FONS_DEF void fonsDrawDebug(FONScontext* stash, float x, float y) +{ + int i; + int w = stash->params.width; + int h = stash->params.height; + float u = w == 0 ? 0 : (1.0f / w); + float v = h == 0 ? 0 : (1.0f / h); + + if (stash->nverts+6+6 > FONS_VERTEX_COUNT) + fons__flush(stash); + + // Draw background + fons__vertex(stash, x+0, y+0, u, v, 0x0fffffff); + fons__vertex(stash, x+w, y+h, u, v, 0x0fffffff); + fons__vertex(stash, x+w, y+0, u, v, 0x0fffffff); + + fons__vertex(stash, x+0, y+0, u, v, 0x0fffffff); + fons__vertex(stash, x+0, y+h, u, v, 0x0fffffff); + fons__vertex(stash, x+w, y+h, u, v, 0x0fffffff); + + // Draw texture + fons__vertex(stash, x+0, y+0, 0, 0, 0xffffffff); + fons__vertex(stash, x+w, y+h, 1, 1, 0xffffffff); + fons__vertex(stash, x+w, y+0, 1, 0, 0xffffffff); + + fons__vertex(stash, x+0, y+0, 0, 0, 0xffffffff); + fons__vertex(stash, x+0, y+h, 0, 1, 0xffffffff); + fons__vertex(stash, x+w, y+h, 1, 1, 0xffffffff); + + // Drawbug draw atlas + for (i = 0; i < stash->atlas->nnodes; i++) { + FONSatlasNode* n = &stash->atlas->nodes[i]; + + if (stash->nverts+6 > FONS_VERTEX_COUNT) + fons__flush(stash); + + fons__vertex(stash, x+n->x+0, y+n->y+0, u, v, 0xc00000ff); + fons__vertex(stash, x+n->x+n->width, y+n->y+1, u, v, 0xc00000ff); + fons__vertex(stash, x+n->x+n->width, y+n->y+0, u, v, 0xc00000ff); + + fons__vertex(stash, x+n->x+0, y+n->y+0, u, v, 0xc00000ff); + fons__vertex(stash, x+n->x+0, y+n->y+1, u, v, 0xc00000ff); + fons__vertex(stash, x+n->x+n->width, y+n->y+1, u, v, 0xc00000ff); + } + + fons__flush(stash); +} + +FONS_DEF float fonsTextBounds(FONScontext* stash, + float x, float y, + const char* str, const char* end, + float* bounds) +{ + FONSstate* state = fons__getState(stash); + unsigned int codepoint; + unsigned int utf8state = 0; + FONSquad q; + FONSglyph* glyph = NULL; + int prevGlyphIndex = -1; + short isize = (short)(state->size*10.0f); + short iblur = (short)state->blur; + float scale; + FONSfont* font; + float startx, advance; + float minx, miny, maxx, maxy; + + if (stash == NULL) return 0; + if (state->font < 0 || state->font >= stash->nfonts) return 0; + font = stash->fonts[state->font]; + if (font->data == NULL) return 0; + + scale = fons__tt_getPixelHeightScale(&font->font, (float)isize/10.0f); + + // Align vertically. + y += fons__getVertAlign(stash, font, state->align, isize); + + minx = maxx = x; + miny = maxy = y; + startx = x; + + if (end == NULL) + end = str + strlen(str); + + for (; str != end; ++str) { + if (fons__decutf8(&utf8state, &codepoint, *(const unsigned char*)str)) + continue; + glyph = fons__getGlyph(stash, font, codepoint, isize, iblur); + if (glyph != NULL) { + fons__getQuad(stash, font, prevGlyphIndex, glyph, scale, state->spacing, &x, &y, &q); + if (q.x0 < minx) minx = q.x0; + if (q.x1 > maxx) maxx = q.x1; + if (stash->params.flags & FONS_ZERO_TOPLEFT) { + if (q.y0 < miny) miny = q.y0; + if (q.y1 > maxy) maxy = q.y1; + } else { + if (q.y1 < miny) miny = q.y1; + if (q.y0 > maxy) maxy = q.y0; + } + } + prevGlyphIndex = glyph != NULL ? glyph->index : -1; + } + + advance = x - startx; + + // Align horizontally + if (state->align & FONS_ALIGN_LEFT) { + // empty + } else if (state->align & FONS_ALIGN_RIGHT) { + minx -= advance; + maxx -= advance; + } else if (state->align & FONS_ALIGN_CENTER) { + minx -= advance * 0.5f; + maxx -= advance * 0.5f; + } + + if (bounds) { + bounds[0] = minx; + bounds[1] = miny; + bounds[2] = maxx; + bounds[3] = maxy; + } + + return advance; +} + +FONS_DEF void fonsVertMetrics(FONScontext* stash, + float* ascender, float* descender, float* lineh) +{ + FONSfont* font; + FONSstate* state = fons__getState(stash); + short isize; + + if (stash == NULL) return; + if (state->font < 0 || state->font >= stash->nfonts) return; + font = stash->fonts[state->font]; + isize = (short)(state->size*10.0f); + if (font->data == NULL) return; + + if (ascender) + *ascender = font->ascender*isize/10.0f; + if (descender) + *descender = font->descender*isize/10.0f; + if (lineh) + *lineh = font->lineh*isize/10.0f; +} + +FONS_DEF void fonsLineBounds(FONScontext* stash, float y, float* miny, float* maxy) +{ + FONSfont* font; + FONSstate* state = fons__getState(stash); + short isize; + + if (stash == NULL) return; + if (state->font < 0 || state->font >= stash->nfonts) return; + font = stash->fonts[state->font]; + isize = (short)(state->size*10.0f); + if (font->data == NULL) return; + + y += fons__getVertAlign(stash, font, state->align, isize); + + if (stash->params.flags & FONS_ZERO_TOPLEFT) { + *miny = y - font->ascender * (float)isize/10.0f; + *maxy = *miny + font->lineh*isize/10.0f; + } else { + *maxy = y + font->descender * (float)isize/10.0f; + *miny = *maxy - font->lineh*isize/10.0f; + } +} + +FONS_DEF const unsigned char* fonsGetTextureData(FONScontext* stash, int* width, int* height) +{ + if (width != NULL) + *width = stash->params.width; + if (height != NULL) + *height = stash->params.height; + return stash->texData; +} + +FONS_DEF int fonsValidateTexture(FONScontext* stash, int* dirty) +{ + if (stash->dirtyRect[0] < stash->dirtyRect[2] && stash->dirtyRect[1] < stash->dirtyRect[3]) { + dirty[0] = stash->dirtyRect[0]; + dirty[1] = stash->dirtyRect[1]; + dirty[2] = stash->dirtyRect[2]; + dirty[3] = stash->dirtyRect[3]; + // Reset dirty rect + stash->dirtyRect[0] = stash->params.width; + stash->dirtyRect[1] = stash->params.height; + stash->dirtyRect[2] = 0; + stash->dirtyRect[3] = 0; + return 1; + } + return 0; +} + +FONS_DEF void fonsDeleteInternal(FONScontext* stash) +{ + int i; + if (stash == NULL) return; + + if (stash->params.renderDelete) + stash->params.renderDelete(stash->params.userPtr); + + for (i = 0; i < stash->nfonts; ++i) + fons__freeFont(stash->fonts[i]); + + if (stash->atlas) fons__deleteAtlas(stash->atlas); + if (stash->fonts) free(stash->fonts); + if (stash->texData) free(stash->texData); + if (stash->scratch) free(stash->scratch); + free(stash); +} + +FONS_DEF void fonsSetErrorCallback(FONScontext* stash, void (*callback)(void* uptr, int error, int val), void* uptr) +{ + if (stash == NULL) return; + stash->handleError = callback; + stash->errorUptr = uptr; +} + +FONS_DEF void fonsGetAtlasSize(FONScontext* stash, int* width, int* height) +{ + if (stash == NULL) return; + *width = stash->params.width; + *height = stash->params.height; +} + +FONS_DEF int fonsExpandAtlas(FONScontext* stash, int width, int height) +{ + int i, maxy = 0; + unsigned char* data = NULL; + if (stash == NULL) return 0; + + width = fons__maxi(width, stash->params.width); + height = fons__maxi(height, stash->params.height); + + if (width == stash->params.width && height == stash->params.height) + return 1; + + // Flush pending glyphs. + fons__flush(stash); + + // Create new texture + if (stash->params.renderResize != NULL) { + if (stash->params.renderResize(stash->params.userPtr, width, height) == 0) + return 0; + } + // Copy old texture data over. + data = (unsigned char*)malloc(width * height); + if (data == NULL) + return 0; + for (i = 0; i < stash->params.height; i++) { + unsigned char* dst = &data[i*width]; + unsigned char* src = &stash->texData[i*stash->params.width]; + memcpy(dst, src, stash->params.width); + if (width > stash->params.width) + memset(dst+stash->params.width, 0, width - stash->params.width); + } + if (height > stash->params.height) + memset(&data[stash->params.height * width], 0, (height - stash->params.height) * width); + + free(stash->texData); + stash->texData = data; + + // Increase atlas size + fons__atlasExpand(stash->atlas, width, height); + + // Add existing data as dirty. + for (i = 0; i < stash->atlas->nnodes; i++) + maxy = fons__maxi(maxy, stash->atlas->nodes[i].y); + stash->dirtyRect[0] = 0; + stash->dirtyRect[1] = 0; + stash->dirtyRect[2] = stash->params.width; + stash->dirtyRect[3] = maxy; + + stash->params.width = width; + stash->params.height = height; + stash->itw = 1.0f/stash->params.width; + stash->ith = 1.0f/stash->params.height; + + return 1; +} + +FONS_DEF int fonsResetAtlas(FONScontext* stash, int width, int height) +{ + int i, j; + if (stash == NULL) return 0; + + // Flush pending glyphs. + fons__flush(stash); + + // Create new texture + if (stash->params.renderResize != NULL) { + if (stash->params.renderResize(stash->params.userPtr, width, height) == 0) + return 0; + } + + // Reset atlas + fons__atlasReset(stash->atlas, width, height); + + // Clear texture data. + stash->texData = (unsigned char*)realloc(stash->texData, width * height); + if (stash->texData == NULL) return 0; + memset(stash->texData, 0, width * height); + + // Reset dirty rect + stash->dirtyRect[0] = width; + stash->dirtyRect[1] = height; + stash->dirtyRect[2] = 0; + stash->dirtyRect[3] = 0; + + // Reset cached glyphs + for (i = 0; i < stash->nfonts; i++) { + FONSfont* font = stash->fonts[i]; + font->nglyphs = 0; + for (j = 0; j < FONS_HASH_LUT_SIZE; j++) + font->lut[j] = -1; + } + + stash->params.width = width; + stash->params.height = height; + stash->itw = 1.0f/stash->params.width; + stash->ith = 1.0f/stash->params.height; + + // Add white rect at 0,0 for debug drawing. + fons__addWhiteRect(stash, 2,2); + + return 1; +} + +#endif // FONTSTASH_IMPLEMENTATION diff --git a/third-party/fontstash/gl3corefontstash.h b/third-party/fontstash/gl3corefontstash.h new file mode 100644 index 0000000..2cd08c7 --- /dev/null +++ b/third-party/fontstash/gl3corefontstash.h @@ -0,0 +1,240 @@ +// +// Copyright (c) 2009-2013 Mikko Mononen memon@inside.org +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. +// +#ifndef GL3COREFONTSTASH_H +#define GL3COREFONTSTASH_H + +#ifdef __cplusplus +extern "C" { +#endif + +FONS_DEF FONScontext* glfonsCreate(int width, int height, int flags); +FONS_DEF void glfonsDelete(FONScontext* ctx); + +FONS_DEF unsigned int glfonsRGBA(unsigned char r, unsigned char g, unsigned char b, unsigned char a); + +#ifdef __cplusplus +} +#endif + +#endif // GL3COREFONTSTASH_H + +#ifdef GLFONTSTASH_IMPLEMENTATION + +#ifndef GLFONS_VERTEX_ATTRIB +# define GLFONS_VERTEX_ATTRIB 0 +#endif + +#ifndef GLFONS_TCOORD_ATTRIB +# define GLFONS_TCOORD_ATTRIB 1 +#endif + +#ifndef GLFONS_COLOR_ATTRIB +# define GLFONS_COLOR_ATTRIB 2 +#endif + +struct GLFONScontext { + GLuint tex; + int width, height; + GLuint vertexArray; + GLuint vertexBuffer; + GLuint tcoordBuffer; + GLuint colorBuffer; +}; +typedef struct GLFONScontext GLFONScontext; + +static int glfons__renderCreate(void* userPtr, int width, int height) +{ + GLFONScontext* gl = (GLFONScontext*)userPtr; + + // Create may be called multiple times, delete existing texture. + if (gl->tex != 0) { + glDeleteTextures(1, &gl->tex); + gl->tex = 0; + } + + glGenTextures(1, &gl->tex); + if (!gl->tex) return 0; + + if (!gl->vertexArray) glGenVertexArrays(1, &gl->vertexArray); + if (!gl->vertexArray) return 0; + + glBindVertexArray(gl->vertexArray); + + if (!gl->vertexBuffer) glGenBuffers(1, &gl->vertexBuffer); + if (!gl->vertexBuffer) return 0; + + if (!gl->tcoordBuffer) glGenBuffers(1, &gl->tcoordBuffer); + if (!gl->tcoordBuffer) return 0; + + if (!gl->colorBuffer) glGenBuffers(1, &gl->colorBuffer); + if (!gl->colorBuffer) return 0; + + gl->width = width; + gl->height = height; + glBindTexture(GL_TEXTURE_2D, gl->tex); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, gl->width, gl->height, 0, GL_RED, GL_UNSIGNED_BYTE, NULL); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + static GLint swizzleRgbaParams[4] = {GL_ONE, GL_ONE, GL_ONE, GL_RED}; + glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_RGBA, swizzleRgbaParams); + + return 1; +} + +static int glfons__renderResize(void* userPtr, int width, int height) +{ + // Reuse create to resize too. + return glfons__renderCreate(userPtr, width, height); +} + +static void glfons__renderUpdate(void* userPtr, int* rect, const unsigned char* data) +{ + GLFONScontext* gl = (GLFONScontext*)userPtr; + int w = rect[2] - rect[0]; + int h = rect[3] - rect[1]; + + if (gl->tex == 0) return; + + // Push old values + GLint alignment, rowLength, skipPixels, skipRows; + glGetIntegerv(GL_UNPACK_ALIGNMENT, &alignment); + glGetIntegerv(GL_UNPACK_ROW_LENGTH, &rowLength); + glGetIntegerv(GL_UNPACK_SKIP_PIXELS, &skipPixels); + glGetIntegerv(GL_UNPACK_SKIP_ROWS, &skipRows); + + glBindTexture(GL_TEXTURE_2D, gl->tex); + + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + glPixelStorei(GL_UNPACK_ROW_LENGTH, gl->width); + glPixelStorei(GL_UNPACK_SKIP_PIXELS, rect[0]); + glPixelStorei(GL_UNPACK_SKIP_ROWS, rect[1]); + + glTexSubImage2D(GL_TEXTURE_2D, 0, rect[0], rect[1], w, h, GL_RED, GL_UNSIGNED_BYTE, data); + + // Pop old values + glPixelStorei(GL_UNPACK_ALIGNMENT, alignment); + glPixelStorei(GL_UNPACK_ROW_LENGTH, rowLength); + glPixelStorei(GL_UNPACK_SKIP_PIXELS, skipPixels); + glPixelStorei(GL_UNPACK_SKIP_ROWS, skipRows); +} + +static void glfons__renderDraw(void* userPtr, const float* verts, const float* tcoords, const unsigned int* colors, int nverts) +{ + GLFONScontext* gl = (GLFONScontext*)userPtr; + if (gl->tex == 0 || gl->vertexArray == 0) return; + + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, gl->tex); + + glBindVertexArray(gl->vertexArray); + + glEnableVertexAttribArray(GLFONS_VERTEX_ATTRIB); + glBindBuffer(GL_ARRAY_BUFFER, gl->vertexBuffer); + glBufferData(GL_ARRAY_BUFFER, nverts * 2 * sizeof(float), verts, GL_DYNAMIC_DRAW); + glVertexAttribPointer(GLFONS_VERTEX_ATTRIB, 2, GL_FLOAT, GL_FALSE, 0, NULL); + + glEnableVertexAttribArray(GLFONS_TCOORD_ATTRIB); + glBindBuffer(GL_ARRAY_BUFFER, gl->tcoordBuffer); + glBufferData(GL_ARRAY_BUFFER, nverts * 2 * sizeof(float), tcoords, GL_DYNAMIC_DRAW); + glVertexAttribPointer(GLFONS_TCOORD_ATTRIB, 2, GL_FLOAT, GL_FALSE, 0, NULL); + + glEnableVertexAttribArray(GLFONS_COLOR_ATTRIB); + glBindBuffer(GL_ARRAY_BUFFER, gl->colorBuffer); + glBufferData(GL_ARRAY_BUFFER, nverts * sizeof(unsigned int), colors, GL_DYNAMIC_DRAW); + glVertexAttribPointer(GLFONS_COLOR_ATTRIB, 4, GL_UNSIGNED_BYTE, GL_FALSE, 0, NULL); + + glDrawArrays(GL_TRIANGLES, 0, nverts); + + glDisableVertexAttribArray(GLFONS_VERTEX_ATTRIB); + glDisableVertexAttribArray(GLFONS_TCOORD_ATTRIB); + glDisableVertexAttribArray(GLFONS_COLOR_ATTRIB); + + glBindVertexArray(0); +} + +static void glfons__renderDelete(void* userPtr) +{ + GLFONScontext* gl = (GLFONScontext*)userPtr; + if (gl->tex != 0) { + glDeleteTextures(1, &gl->tex); + gl->tex = 0; + } + + glBindVertexArray(0); + + if (gl->vertexBuffer != 0) { + glDeleteBuffers(1, &gl->vertexBuffer); + gl->vertexBuffer = 0; + } + + if (gl->tcoordBuffer != 0) { + glDeleteBuffers(1, &gl->tcoordBuffer); + gl->tcoordBuffer = 0; + } + + if (gl->colorBuffer != 0) { + glDeleteBuffers(1, &gl->colorBuffer); + gl->colorBuffer = 0; + } + + if (gl->vertexArray != 0) { + glDeleteVertexArrays(1, &gl->vertexArray); + gl->vertexArray = 0; + } + + free(gl); +} + +FONS_DEF FONScontext* glfonsCreate(int width, int height, int flags) +{ + FONSparams params; + GLFONScontext* gl; + + gl = (GLFONScontext*)malloc(sizeof(GLFONScontext)); + if (gl == NULL) goto error; + memset(gl, 0, sizeof(GLFONScontext)); + + memset(¶ms, 0, sizeof(params)); + params.width = width; + params.height = height; + params.flags = (unsigned char)flags; + params.renderCreate = glfons__renderCreate; + params.renderResize = glfons__renderResize; + params.renderUpdate = glfons__renderUpdate; + params.renderDraw = glfons__renderDraw; + params.renderDelete = glfons__renderDelete; + params.userPtr = gl; + + return fonsCreateInternal(¶ms); + +error: + if (gl != NULL) free(gl); + return NULL; +} + +FONS_DEF void glfonsDelete(FONScontext* ctx) +{ + fonsDeleteInternal(ctx); +} + +FONS_DEF unsigned int glfonsRGBA(unsigned char r, unsigned char g, unsigned char b, unsigned char a) +{ + return (r) | (g << 8) | (b << 16) | (a << 24); +} + +#endif // GLFONTSTASH_IMPLEMENTATION diff --git a/third-party/fontstash/glfontstash.h b/third-party/fontstash/glfontstash.h new file mode 100644 index 0000000..072bbb8 --- /dev/null +++ b/third-party/fontstash/glfontstash.h @@ -0,0 +1,146 @@ +// +// Copyright (c) 2009-2013 Mikko Mononen memon@inside.org +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. +// +#ifndef GLFONTSTASH_H +#define GLFONTSTASH_H + +FONScontext* glfonsCreate(int width, int height, int flags); +void glfonsDelete(FONScontext* ctx); + +unsigned int glfonsRGBA(unsigned char r, unsigned char g, unsigned char b, unsigned char a); + +#endif + +#ifdef GLFONTSTASH_IMPLEMENTATION + +struct GLFONScontext { + GLuint tex; + int width, height; +}; +typedef struct GLFONScontext GLFONScontext; + +static int glfons__renderCreate(void* userPtr, int width, int height) +{ + GLFONScontext* gl = (GLFONScontext*)userPtr; + // Create may be called multiple times, delete existing texture. + if (gl->tex != 0) { + glDeleteTextures(1, &gl->tex); + gl->tex = 0; + } + glGenTextures(1, &gl->tex); + if (!gl->tex) return 0; + gl->width = width; + gl->height = height; + glBindTexture(GL_TEXTURE_2D, gl->tex); + glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, gl->width, gl->height, 0, GL_ALPHA, GL_UNSIGNED_BYTE, 0); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + return 1; +} + +static int glfons__renderResize(void* userPtr, int width, int height) +{ + // Reuse create to resize too. + return glfons__renderCreate(userPtr, width, height); +} + +static void glfons__renderUpdate(void* userPtr, int* rect, const unsigned char* data) +{ + GLFONScontext* gl = (GLFONScontext*)userPtr; + int w = rect[2] - rect[0]; + int h = rect[3] - rect[1]; + + if (gl->tex == 0) return; + glPushClientAttrib(GL_CLIENT_PIXEL_STORE_BIT); + glBindTexture(GL_TEXTURE_2D, gl->tex); + glPixelStorei(GL_UNPACK_ALIGNMENT,1); + glPixelStorei(GL_UNPACK_ROW_LENGTH, gl->width); + glPixelStorei(GL_UNPACK_SKIP_PIXELS, rect[0]); + glPixelStorei(GL_UNPACK_SKIP_ROWS, rect[1]); + glTexSubImage2D(GL_TEXTURE_2D, 0, rect[0], rect[1], w, h, GL_ALPHA,GL_UNSIGNED_BYTE, data); + glPopClientAttrib(); +} + +static void glfons__renderDraw(void* userPtr, const float* verts, const float* tcoords, const unsigned int* colors, int nverts) +{ + GLFONScontext* gl = (GLFONScontext*)userPtr; + if (gl->tex == 0) return; + glBindTexture(GL_TEXTURE_2D, gl->tex); + glEnable(GL_TEXTURE_2D); + glEnableClientState(GL_VERTEX_ARRAY); + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + glEnableClientState(GL_COLOR_ARRAY); + + glVertexPointer(2, GL_FLOAT, sizeof(float)*2, verts); + glTexCoordPointer(2, GL_FLOAT, sizeof(float)*2, tcoords); + glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(unsigned int), colors); + + glDrawArrays(GL_TRIANGLES, 0, nverts); + + glDisable(GL_TEXTURE_2D); + glDisableClientState(GL_VERTEX_ARRAY); + glDisableClientState(GL_TEXTURE_COORD_ARRAY); + glDisableClientState(GL_COLOR_ARRAY); +} + +static void glfons__renderDelete(void* userPtr) +{ + GLFONScontext* gl = (GLFONScontext*)userPtr; + if (gl->tex != 0) + glDeleteTextures(1, &gl->tex); + gl->tex = 0; + free(gl); +} + + +FONScontext* glfonsCreate(int width, int height, int flags) +{ + FONSparams params; + GLFONScontext* gl; + + gl = (GLFONScontext*)malloc(sizeof(GLFONScontext)); + if (gl == NULL) goto error; + memset(gl, 0, sizeof(GLFONScontext)); + + memset(¶ms, 0, sizeof(params)); + params.width = width; + params.height = height; + params.flags = (unsigned char)flags; + params.renderCreate = glfons__renderCreate; + params.renderResize = glfons__renderResize; + params.renderUpdate = glfons__renderUpdate; + params.renderDraw = glfons__renderDraw; + params.renderDelete = glfons__renderDelete; + params.userPtr = gl; + + return fonsCreateInternal(¶ms); + +error: + if (gl != NULL) free(gl); + return NULL; +} + +void glfonsDelete(FONScontext* ctx) +{ + fonsDeleteInternal(ctx); +} + +unsigned int glfonsRGBA(unsigned char r, unsigned char g, unsigned char b, unsigned char a) +{ + return (r) | (g << 8) | (b << 16) | (a << 24); +} + +#endif diff --git a/third-party/fontstash/stb_truetype.h b/third-party/fontstash/stb_truetype.h new file mode 100644 index 0000000..1327ba3 --- /dev/null +++ b/third-party/fontstash/stb_truetype.h @@ -0,0 +1,4545 @@ +// stb_truetype.h - v1.16 - public domain +// authored from 2009-2016 by Sean Barrett / RAD Game Tools +// +// This library processes TrueType files: +// parse files +// extract glyph metrics +// extract glyph shapes +// render glyphs to one-channel bitmaps with antialiasing (box filter) +// render glyphs to one-channel SDF bitmaps (signed-distance field/function) +// +// Todo: +// non-MS cmaps +// crashproof on bad data +// hinting? (no longer patented) +// cleartype-style AA? +// optimize: use simple memory allocator for intermediates +// optimize: build edge-list directly from curves +// optimize: rasterize directly from curves? +// +// ADDITIONAL CONTRIBUTORS +// +// Mikko Mononen: compound shape support, more cmap formats +// Tor Andersson: kerning, subpixel rendering +// Dougall Johnson: OpenType / Type 2 font handling +// +// Misc other: +// Ryan Gordon +// Simon Glass +// github:IntellectualKitty +// +// Bug/warning reports/fixes: +// "Zer" on mollyrocket +// Cass Everitt +// stoiko (Haemimont Games) +// Brian Hook +// Walter van Niftrik +// David Gow +// David Given +// Ivan-Assen Ivanov +// Anthony Pesch +// Johan Duparc +// Hou Qiming +// Fabian "ryg" Giesen +// Martins Mozeiko +// Cap Petschulat +// Omar Cornut +// github:aloucks +// Peter LaValle +// Sergey Popov +// Giumo X. Clanjor +// Higor Euripedes +// Thomas Fields +// Derek Vinyard +// Cort Stratton +// +// VERSION HISTORY +// +// 1.16 (2017-07-12) SDF support +// 1.15 (2017-03-03) make more arguments const +// 1.14 (2017-01-16) num-fonts-in-TTC function +// 1.13 (2017-01-02) support OpenType fonts, certain Apple fonts +// 1.12 (2016-10-25) suppress warnings about casting away const with -Wcast-qual +// 1.11 (2016-04-02) fix unused-variable warning +// 1.10 (2016-04-02) user-defined fabs(); rare memory leak; remove duplicate typedef +// 1.09 (2016-01-16) warning fix; avoid crash on outofmem; use allocation userdata properly +// 1.08 (2015-09-13) document stbtt_Rasterize(); fixes for vertical & horizontal edges +// 1.07 (2015-08-01) allow PackFontRanges to accept arrays of sparse codepoints; +// variant PackFontRanges to pack and render in separate phases; +// fix stbtt_GetFontOFfsetForIndex (never worked for non-0 input?); +// fixed an assert() bug in the new rasterizer +// replace assert() with STBTT_assert() in new rasterizer +// +// Full history can be found at the end of this file. +// +// LICENSE +// +// See end of file for license information. +// +// USAGE +// +// Include this file in whatever places neeed to refer to it. In ONE C/C++ +// file, write: +// #define STB_TRUETYPE_IMPLEMENTATION +// before the #include of this file. This expands out the actual +// implementation into that C/C++ file. +// +// To make the implementation private to the file that generates the implementation, +// #define STBTT_STATIC +// +// Simple 3D API (don't ship this, but it's fine for tools and quick start) +// stbtt_BakeFontBitmap() -- bake a font to a bitmap for use as texture +// stbtt_GetBakedQuad() -- compute quad to draw for a given char +// +// Improved 3D API (more shippable): +// #include "stb_rect_pack.h" -- optional, but you really want it +// stbtt_PackBegin() +// stbtt_PackSetOversample() -- for improved quality on small fonts +// stbtt_PackFontRanges() -- pack and renders +// stbtt_PackEnd() +// stbtt_GetPackedQuad() +// +// "Load" a font file from a memory buffer (you have to keep the buffer loaded) +// stbtt_InitFont() +// stbtt_GetFontOffsetForIndex() -- indexing for TTC font collections +// stbtt_GetNumberOfFonts() -- number of fonts for TTC font collections +// +// Render a unicode codepoint to a bitmap +// stbtt_GetCodepointBitmap() -- allocates and returns a bitmap +// stbtt_MakeCodepointBitmap() -- renders into bitmap you provide +// stbtt_GetCodepointBitmapBox() -- how big the bitmap must be +// +// Character advance/positioning +// stbtt_GetCodepointHMetrics() +// stbtt_GetFontVMetrics() +// stbtt_GetCodepointKernAdvance() +// +// Starting with version 1.06, the rasterizer was replaced with a new, +// faster and generally-more-precise rasterizer. The new rasterizer more +// accurately measures pixel coverage for anti-aliasing, except in the case +// where multiple shapes overlap, in which case it overestimates the AA pixel +// coverage. Thus, anti-aliasing of intersecting shapes may look wrong. If +// this turns out to be a problem, you can re-enable the old rasterizer with +// #define STBTT_RASTERIZER_VERSION 1 +// which will incur about a 15% speed hit. +// +// ADDITIONAL DOCUMENTATION +// +// Immediately after this block comment are a series of sample programs. +// +// After the sample programs is the "header file" section. This section +// includes documentation for each API function. +// +// Some important concepts to understand to use this library: +// +// Codepoint +// Characters are defined by unicode codepoints, e.g. 65 is +// uppercase A, 231 is lowercase c with a cedilla, 0x7e30 is +// the hiragana for "ma". +// +// Glyph +// A visual character shape (every codepoint is rendered as +// some glyph) +// +// Glyph index +// A font-specific integer ID representing a glyph +// +// Baseline +// Glyph shapes are defined relative to a baseline, which is the +// bottom of uppercase characters. Characters extend both above +// and below the baseline. +// +// Current Point +// As you draw text to the screen, you keep track of a "current point" +// which is the origin of each character. The current point's vertical +// position is the baseline. Even "baked fonts" use this model. +// +// Vertical Font Metrics +// The vertical qualities of the font, used to vertically position +// and space the characters. See docs for stbtt_GetFontVMetrics. +// +// Font Size in Pixels or Points +// The preferred interface for specifying font sizes in stb_truetype +// is to specify how tall the font's vertical extent should be in pixels. +// If that sounds good enough, skip the next paragraph. +// +// Most font APIs instead use "points", which are a common typographic +// measurement for describing font size, defined as 72 points per inch. +// stb_truetype provides a point API for compatibility. However, true +// "per inch" conventions don't make much sense on computer displays +// since they different monitors have different number of pixels per +// inch. For example, Windows traditionally uses a convention that +// there are 96 pixels per inch, thus making 'inch' measurements have +// nothing to do with inches, and thus effectively defining a point to +// be 1.333 pixels. Additionally, the TrueType font data provides +// an explicit scale factor to scale a given font's glyphs to points, +// but the author has observed that this scale factor is often wrong +// for non-commercial fonts, thus making fonts scaled in points +// according to the TrueType spec incoherently sized in practice. +// +// ADVANCED USAGE +// +// Quality: +// +// - Use the functions with Subpixel at the end to allow your characters +// to have subpixel positioning. Since the font is anti-aliased, not +// hinted, this is very import for quality. (This is not possible with +// baked fonts.) +// +// - Kerning is now supported, and if you're supporting subpixel rendering +// then kerning is worth using to give your text a polished look. +// +// Performance: +// +// - Convert Unicode codepoints to glyph indexes and operate on the glyphs; +// if you don't do this, stb_truetype is forced to do the conversion on +// every call. +// +// - There are a lot of memory allocations. We should modify it to take +// a temp buffer and allocate from the temp buffer (without freeing), +// should help performance a lot. +// +// NOTES +// +// The system uses the raw data found in the .ttf file without changing it +// and without building auxiliary data structures. This is a bit inefficient +// on little-endian systems (the data is big-endian), but assuming you're +// caching the bitmaps or glyph shapes this shouldn't be a big deal. +// +// It appears to be very hard to programmatically determine what font a +// given file is in a general way. I provide an API for this, but I don't +// recommend it. +// +// +// SOURCE STATISTICS (based on v0.6c, 2050 LOC) +// +// Documentation & header file 520 LOC \___ 660 LOC documentation +// Sample code 140 LOC / +// Truetype parsing 620 LOC ---- 620 LOC TrueType +// Software rasterization 240 LOC \ . +// Curve tesselation 120 LOC \__ 550 LOC Bitmap creation +// Bitmap management 100 LOC / +// Baked bitmap interface 70 LOC / +// Font name matching & access 150 LOC ---- 150 +// C runtime library abstraction 60 LOC ---- 60 +// +// +// PERFORMANCE MEASUREMENTS FOR 1.06: +// +// 32-bit 64-bit +// Previous release: 8.83 s 7.68 s +// Pool allocations: 7.72 s 6.34 s +// Inline sort : 6.54 s 5.65 s +// New rasterizer : 5.63 s 5.00 s + +////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// +//// +//// SAMPLE PROGRAMS +//// +// +// Incomplete text-in-3d-api example, which draws quads properly aligned to be lossless +// +#if 0 +#define STB_TRUETYPE_IMPLEMENTATION // force following include to generate implementation +#include "stb_truetype.h" + +unsigned char ttf_buffer[1<<20]; +unsigned char temp_bitmap[512*512]; + +stbtt_bakedchar cdata[96]; // ASCII 32..126 is 95 glyphs +GLuint ftex; + +void my_stbtt_initfont(void) +{ + fread(ttf_buffer, 1, 1<<20, fopen("c:/windows/fonts/times.ttf", "rb")); + stbtt_BakeFontBitmap(ttf_buffer,0, 32.0, temp_bitmap,512,512, 32,96, cdata); // no guarantee this fits! + // can free ttf_buffer at this point + glGenTextures(1, &ftex); + glBindTexture(GL_TEXTURE_2D, ftex); + glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, 512,512, 0, GL_ALPHA, GL_UNSIGNED_BYTE, temp_bitmap); + // can free temp_bitmap at this point + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); +} + +void my_stbtt_print(float x, float y, char *text) +{ + // assume orthographic projection with units = screen pixels, origin at top left + glEnable(GL_TEXTURE_2D); + glBindTexture(GL_TEXTURE_2D, ftex); + glBegin(GL_QUADS); + while (*text) { + if (*text >= 32 && *text < 128) { + stbtt_aligned_quad q; + stbtt_GetBakedQuad(cdata, 512,512, *text-32, &x,&y,&q,1);//1=opengl & d3d10+,0=d3d9 + glTexCoord2f(q.s0,q.t1); glVertex2f(q.x0,q.y0); + glTexCoord2f(q.s1,q.t1); glVertex2f(q.x1,q.y0); + glTexCoord2f(q.s1,q.t0); glVertex2f(q.x1,q.y1); + glTexCoord2f(q.s0,q.t0); glVertex2f(q.x0,q.y1); + } + ++text; + } + glEnd(); +} +#endif +// +// +////////////////////////////////////////////////////////////////////////////// +// +// Complete program (this compiles): get a single bitmap, print as ASCII art +// +#if 0 +#include +#define STB_TRUETYPE_IMPLEMENTATION // force following include to generate implementation +#include "stb_truetype.h" + +char ttf_buffer[1<<25]; + +int main(int argc, char **argv) +{ + stbtt_fontinfo font; + unsigned char *bitmap; + int w,h,i,j,c = (argc > 1 ? atoi(argv[1]) : 'a'), s = (argc > 2 ? atoi(argv[2]) : 20); + + fread(ttf_buffer, 1, 1<<25, fopen(argc > 3 ? argv[3] : "c:/windows/fonts/arialbd.ttf", "rb")); + + stbtt_InitFont(&font, ttf_buffer, stbtt_GetFontOffsetForIndex(ttf_buffer,0)); + bitmap = stbtt_GetCodepointBitmap(&font, 0,stbtt_ScaleForPixelHeight(&font, s), c, &w, &h, 0,0); + + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) + putchar(" .:ioVM@"[bitmap[j*w+i]>>5]); + putchar('\n'); + } + return 0; +} +#endif +// +// Output: +// +// .ii. +// @@@@@@. +// V@Mio@@o +// :i. V@V +// :oM@@M +// :@@@MM@M +// @@o o@M +// :@@. M@M +// @@@o@@@@ +// :M@@V:@@. +// +////////////////////////////////////////////////////////////////////////////// +// +// Complete program: print "Hello World!" banner, with bugs +// +#if 0 +char buffer[24<<20]; +unsigned char screen[20][79]; + +int main(int arg, char **argv) +{ + stbtt_fontinfo font; + int i,j,ascent,baseline,ch=0; + float scale, xpos=2; // leave a little padding in case the character extends left + char *text = "Heljo World!"; // intentionally misspelled to show 'lj' brokenness + + fread(buffer, 1, 1000000, fopen("c:/windows/fonts/arialbd.ttf", "rb")); + stbtt_InitFont(&font, buffer, 0); + + scale = stbtt_ScaleForPixelHeight(&font, 15); + stbtt_GetFontVMetrics(&font, &ascent,0,0); + baseline = (int) (ascent*scale); + + while (text[ch]) { + int advance,lsb,x0,y0,x1,y1; + float x_shift = xpos - (float) floor(xpos); + stbtt_GetCodepointHMetrics(&font, text[ch], &advance, &lsb); + stbtt_GetCodepointBitmapBoxSubpixel(&font, text[ch], scale,scale,x_shift,0, &x0,&y0,&x1,&y1); + stbtt_MakeCodepointBitmapSubpixel(&font, &screen[baseline + y0][(int) xpos + x0], x1-x0,y1-y0, 79, scale,scale,x_shift,0, text[ch]); + // note that this stomps the old data, so where character boxes overlap (e.g. 'lj') it's wrong + // because this API is really for baking character bitmaps into textures. if you want to render + // a sequence of characters, you really need to render each bitmap to a temp buffer, then + // "alpha blend" that into the working buffer + xpos += (advance * scale); + if (text[ch+1]) + xpos += scale*stbtt_GetCodepointKernAdvance(&font, text[ch],text[ch+1]); + ++ch; + } + + for (j=0; j < 20; ++j) { + for (i=0; i < 78; ++i) + putchar(" .:ioVM@"[screen[j][i]>>5]); + putchar('\n'); + } + + return 0; +} +#endif + + +////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// +//// +//// INTEGRATION WITH YOUR CODEBASE +//// +//// The following sections allow you to supply alternate definitions +//// of C library functions used by stb_truetype. + +#ifdef STB_TRUETYPE_IMPLEMENTATION + // #define your own (u)stbtt_int8/16/32 before including to override this + #ifndef stbtt_uint8 + typedef unsigned char stbtt_uint8; + typedef signed char stbtt_int8; + typedef unsigned short stbtt_uint16; + typedef signed short stbtt_int16; + typedef unsigned int stbtt_uint32; + typedef signed int stbtt_int32; + #endif + + typedef char stbtt__check_size32[sizeof(stbtt_int32)==4 ? 1 : -1]; + typedef char stbtt__check_size16[sizeof(stbtt_int16)==2 ? 1 : -1]; + + // #define your own STBTT_ifloor/STBTT_iceil() to avoid math.h + #ifndef STBTT_ifloor + #include + #define STBTT_ifloor(x) ((int) floor(x)) + #define STBTT_iceil(x) ((int) ceil(x)) + #endif + + #ifndef STBTT_sqrt + #include + #define STBTT_sqrt(x) sqrt(x) + #define STBTT_pow(x,y) pow(x,y) + #endif + + #ifndef STBTT_cos + #include + #define STBTT_cos(x) cos(x) + #define STBTT_acos(x) acos(x) + #endif + + #ifndef STBTT_fabs + #include + #define STBTT_fabs(x) fabs(x) + #endif + + #ifndef STBTT_fabs + #include + #define STBTT_fabs(x) fabs(x) + #endif + + // #define your own functions "STBTT_malloc" / "STBTT_free" to avoid malloc.h + #ifndef STBTT_malloc + #include + #define STBTT_malloc(x,u) ((void)(u),malloc(x)) + #define STBTT_free(x,u) ((void)(u),free(x)) + #endif + + #ifndef STBTT_assert + #include + #define STBTT_assert(x) assert(x) + #endif + + #ifndef STBTT_strlen + #include + #define STBTT_strlen(x) strlen(x) + #endif + + #ifndef STBTT_memcpy + #include + #define STBTT_memcpy memcpy + #define STBTT_memset memset + #endif +#endif + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +//// +//// INTERFACE +//// +//// + +#ifndef __STB_INCLUDE_STB_TRUETYPE_H__ +#define __STB_INCLUDE_STB_TRUETYPE_H__ + +#ifdef STBTT_STATIC +#define STBTT_DEF static +#else +#define STBTT_DEF extern +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +// private structure +typedef struct +{ + unsigned char *data; + int cursor; + int size; +} stbtt__buf; + +////////////////////////////////////////////////////////////////////////////// +// +// TEXTURE BAKING API +// +// If you use this API, you only have to call two functions ever. +// + +typedef struct +{ + unsigned short x0,y0,x1,y1; // coordinates of bbox in bitmap + float xoff,yoff,xadvance; +} stbtt_bakedchar; + +STBTT_DEF int stbtt_BakeFontBitmap(const unsigned char *data, int offset, // font location (use offset=0 for plain .ttf) + float pixel_height, // height of font in pixels + unsigned char *pixels, int pw, int ph, // bitmap to be filled in + int first_char, int num_chars, // characters to bake + stbtt_bakedchar *chardata); // you allocate this, it's num_chars long +// if return is positive, the first unused row of the bitmap +// if return is negative, returns the negative of the number of characters that fit +// if return is 0, no characters fit and no rows were used +// This uses a very crappy packing. + +typedef struct +{ + float x0,y0,s0,t0; // top-left + float x1,y1,s1,t1; // bottom-right +} stbtt_aligned_quad; + +STBTT_DEF void stbtt_GetBakedQuad(const stbtt_bakedchar *chardata, int pw, int ph, // same data as above + int char_index, // character to display + float *xpos, float *ypos, // pointers to current position in screen pixel space + stbtt_aligned_quad *q, // output: quad to draw + int opengl_fillrule); // true if opengl fill rule; false if DX9 or earlier +// Call GetBakedQuad with char_index = 'character - first_char', and it +// creates the quad you need to draw and advances the current position. +// +// The coordinate system used assumes y increases downwards. +// +// Characters will extend both above and below the current position; +// see discussion of "BASELINE" above. +// +// It's inefficient; you might want to c&p it and optimize it. + + + +////////////////////////////////////////////////////////////////////////////// +// +// NEW TEXTURE BAKING API +// +// This provides options for packing multiple fonts into one atlas, not +// perfectly but better than nothing. + +typedef struct +{ + unsigned short x0,y0,x1,y1; // coordinates of bbox in bitmap + float xoff,yoff,xadvance; + float xoff2,yoff2; +} stbtt_packedchar; + +typedef struct stbtt_pack_context stbtt_pack_context; +typedef struct stbtt_fontinfo stbtt_fontinfo; +#ifndef STB_RECT_PACK_VERSION +typedef struct stbrp_rect stbrp_rect; +#endif + +STBTT_DEF int stbtt_PackBegin(stbtt_pack_context *spc, unsigned char *pixels, int width, int height, int stride_in_bytes, int padding, void *alloc_context); +// Initializes a packing context stored in the passed-in stbtt_pack_context. +// Future calls using this context will pack characters into the bitmap passed +// in here: a 1-channel bitmap that is width * height. stride_in_bytes is +// the distance from one row to the next (or 0 to mean they are packed tightly +// together). "padding" is the amount of padding to leave between each +// character (normally you want '1' for bitmaps you'll use as textures with +// bilinear filtering). +// +// Returns 0 on failure, 1 on success. + +STBTT_DEF void stbtt_PackEnd (stbtt_pack_context *spc); +// Cleans up the packing context and frees all memory. + +#define STBTT_POINT_SIZE(x) (-(x)) + +STBTT_DEF int stbtt_PackFontRange(stbtt_pack_context *spc, unsigned char *fontdata, int font_index, float font_size, + int first_unicode_char_in_range, int num_chars_in_range, stbtt_packedchar *chardata_for_range); +// Creates character bitmaps from the font_index'th font found in fontdata (use +// font_index=0 if you don't know what that is). It creates num_chars_in_range +// bitmaps for characters with unicode values starting at first_unicode_char_in_range +// and increasing. Data for how to render them is stored in chardata_for_range; +// pass these to stbtt_GetPackedQuad to get back renderable quads. +// +// font_size is the full height of the character from ascender to descender, +// as computed by stbtt_ScaleForPixelHeight. To use a point size as computed +// by stbtt_ScaleForMappingEmToPixels, wrap the point size in STBTT_POINT_SIZE() +// and pass that result as 'font_size': +// ..., 20 , ... // font max minus min y is 20 pixels tall +// ..., STBTT_POINT_SIZE(20), ... // 'M' is 20 pixels tall + +typedef struct +{ + float font_size; + int first_unicode_codepoint_in_range; // if non-zero, then the chars are continuous, and this is the first codepoint + int *array_of_unicode_codepoints; // if non-zero, then this is an array of unicode codepoints + int num_chars; + stbtt_packedchar *chardata_for_range; // output + unsigned char h_oversample, v_oversample; // don't set these, they're used internally +} stbtt_pack_range; + +STBTT_DEF int stbtt_PackFontRanges(stbtt_pack_context *spc, unsigned char *fontdata, int font_index, stbtt_pack_range *ranges, int num_ranges); +// Creates character bitmaps from multiple ranges of characters stored in +// ranges. This will usually create a better-packed bitmap than multiple +// calls to stbtt_PackFontRange. Note that you can call this multiple +// times within a single PackBegin/PackEnd. + +STBTT_DEF void stbtt_PackSetOversampling(stbtt_pack_context *spc, unsigned int h_oversample, unsigned int v_oversample); +// Oversampling a font increases the quality by allowing higher-quality subpixel +// positioning, and is especially valuable at smaller text sizes. +// +// This function sets the amount of oversampling for all following calls to +// stbtt_PackFontRange(s) or stbtt_PackFontRangesGatherRects for a given +// pack context. The default (no oversampling) is achieved by h_oversample=1 +// and v_oversample=1. The total number of pixels required is +// h_oversample*v_oversample larger than the default; for example, 2x2 +// oversampling requires 4x the storage of 1x1. For best results, render +// oversampled textures with bilinear filtering. Look at the readme in +// stb/tests/oversample for information about oversampled fonts +// +// To use with PackFontRangesGather etc., you must set it before calls +// call to PackFontRangesGatherRects. + +STBTT_DEF void stbtt_GetPackedQuad(const stbtt_packedchar *chardata, int pw, int ph, // same data as above + int char_index, // character to display + float *xpos, float *ypos, // pointers to current position in screen pixel space + stbtt_aligned_quad *q, // output: quad to draw + int align_to_integer); + +STBTT_DEF int stbtt_PackFontRangesGatherRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects); +STBTT_DEF void stbtt_PackFontRangesPackRects(stbtt_pack_context *spc, stbrp_rect *rects, int num_rects); +STBTT_DEF int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects); +// Calling these functions in sequence is roughly equivalent to calling +// stbtt_PackFontRanges(). If you more control over the packing of multiple +// fonts, or if you want to pack custom data into a font texture, take a look +// at the source to of stbtt_PackFontRanges() and create a custom version +// using these functions, e.g. call GatherRects multiple times, +// building up a single array of rects, then call PackRects once, +// then call RenderIntoRects repeatedly. This may result in a +// better packing than calling PackFontRanges multiple times +// (or it may not). + +// this is an opaque structure that you shouldn't mess with which holds +// all the context needed from PackBegin to PackEnd. +struct stbtt_pack_context { + void *user_allocator_context; + void *pack_info; + int width; + int height; + int stride_in_bytes; + int padding; + unsigned int h_oversample, v_oversample; + unsigned char *pixels; + void *nodes; +}; + +////////////////////////////////////////////////////////////////////////////// +// +// FONT LOADING +// +// + +STBTT_DEF int stbtt_GetNumberOfFonts(const unsigned char *data); +// This function will determine the number of fonts in a font file. TrueType +// collection (.ttc) files may contain multiple fonts, while TrueType font +// (.ttf) files only contain one font. The number of fonts can be used for +// indexing with the previous function where the index is between zero and one +// less than the total fonts. If an error occurs, -1 is returned. + +STBTT_DEF int stbtt_GetFontOffsetForIndex(const unsigned char *data, int index); +// Each .ttf/.ttc file may have more than one font. Each font has a sequential +// index number starting from 0. Call this function to get the font offset for +// a given index; it returns -1 if the index is out of range. A regular .ttf +// file will only define one font and it always be at offset 0, so it will +// return '0' for index 0, and -1 for all other indices. + +// The following structure is defined publically so you can declare one on +// the stack or as a global or etc, but you should treat it as opaque. +struct stbtt_fontinfo +{ + void * userdata; + unsigned char * data; // pointer to .ttf file + int fontstart; // offset of start of font + + int numGlyphs; // number of glyphs, needed for range checking + + int loca,head,glyf,hhea,hmtx,kern; // table locations as offset from start of .ttf + int index_map; // a cmap mapping for our chosen character encoding + int indexToLocFormat; // format needed to map from glyph index to glyph + + stbtt__buf cff; // cff font data + stbtt__buf charstrings; // the charstring index + stbtt__buf gsubrs; // global charstring subroutines index + stbtt__buf subrs; // private charstring subroutines index + stbtt__buf fontdicts; // array of font dicts + stbtt__buf fdselect; // map from glyph to fontdict +}; + +STBTT_DEF int stbtt_InitFont(stbtt_fontinfo *info, const unsigned char *data, int offset); +// Given an offset into the file that defines a font, this function builds +// the necessary cached info for the rest of the system. You must allocate +// the stbtt_fontinfo yourself, and stbtt_InitFont will fill it out. You don't +// need to do anything special to free it, because the contents are pure +// value data with no additional data structures. Returns 0 on failure. + + +////////////////////////////////////////////////////////////////////////////// +// +// CHARACTER TO GLYPH-INDEX CONVERSIOn + +STBTT_DEF int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codepoint); +// If you're going to perform multiple operations on the same character +// and you want a speed-up, call this function with the character you're +// going to process, then use glyph-based functions instead of the +// codepoint-based functions. + + +////////////////////////////////////////////////////////////////////////////// +// +// CHARACTER PROPERTIES +// + +STBTT_DEF float stbtt_ScaleForPixelHeight(const stbtt_fontinfo *info, float pixels); +// computes a scale factor to produce a font whose "height" is 'pixels' tall. +// Height is measured as the distance from the highest ascender to the lowest +// descender; in other words, it's equivalent to calling stbtt_GetFontVMetrics +// and computing: +// scale = pixels / (ascent - descent) +// so if you prefer to measure height by the ascent only, use a similar calculation. + +STBTT_DEF float stbtt_ScaleForMappingEmToPixels(const stbtt_fontinfo *info, float pixels); +// computes a scale factor to produce a font whose EM size is mapped to +// 'pixels' tall. This is probably what traditional APIs compute, but +// I'm not positive. + +STBTT_DEF void stbtt_GetFontVMetrics(const stbtt_fontinfo *info, int *ascent, int *descent, int *lineGap); +// ascent is the coordinate above the baseline the font extends; descent +// is the coordinate below the baseline the font extends (i.e. it is typically negative) +// lineGap is the spacing between one row's descent and the next row's ascent... +// so you should advance the vertical position by "*ascent - *descent + *lineGap" +// these are expressed in unscaled coordinates, so you must multiply by +// the scale factor for a given size + +STBTT_DEF void stbtt_GetFontBoundingBox(const stbtt_fontinfo *info, int *x0, int *y0, int *x1, int *y1); +// the bounding box around all possible characters + +STBTT_DEF void stbtt_GetCodepointHMetrics(const stbtt_fontinfo *info, int codepoint, int *advanceWidth, int *leftSideBearing); +// leftSideBearing is the offset from the current horizontal position to the left edge of the character +// advanceWidth is the offset from the current horizontal position to the next horizontal position +// these are expressed in unscaled coordinates + +STBTT_DEF int stbtt_GetCodepointKernAdvance(const stbtt_fontinfo *info, int ch1, int ch2); +// an additional amount to add to the 'advance' value between ch1 and ch2 + +STBTT_DEF int stbtt_GetCodepointBox(const stbtt_fontinfo *info, int codepoint, int *x0, int *y0, int *x1, int *y1); +// Gets the bounding box of the visible part of the glyph, in unscaled coordinates + +STBTT_DEF void stbtt_GetGlyphHMetrics(const stbtt_fontinfo *info, int glyph_index, int *advanceWidth, int *leftSideBearing); +STBTT_DEF int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2); +STBTT_DEF int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1); +// as above, but takes one or more glyph indices for greater efficiency + + +////////////////////////////////////////////////////////////////////////////// +// +// GLYPH SHAPES (you probably don't need these, but they have to go before +// the bitmaps for C declaration-order reasons) +// + +#ifndef STBTT_vmove // you can predefine these to use different values (but why?) + enum { + STBTT_vmove=1, + STBTT_vline, + STBTT_vcurve, + STBTT_vcubic + }; +#endif + +#ifndef stbtt_vertex // you can predefine this to use different values + // (we share this with other code at RAD) + #define stbtt_vertex_type short // can't use stbtt_int16 because that's not visible in the header file + typedef struct + { + stbtt_vertex_type x,y,cx,cy,cx1,cy1; + unsigned char type,padding; + } stbtt_vertex; +#endif + +STBTT_DEF int stbtt_IsGlyphEmpty(const stbtt_fontinfo *info, int glyph_index); +// returns non-zero if nothing is drawn for this glyph + +STBTT_DEF int stbtt_GetCodepointShape(const stbtt_fontinfo *info, int unicode_codepoint, stbtt_vertex **vertices); +STBTT_DEF int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **vertices); +// returns # of vertices and fills *vertices with the pointer to them +// these are expressed in "unscaled" coordinates +// +// The shape is a series of countours. Each one starts with +// a STBTT_moveto, then consists of a series of mixed +// STBTT_lineto and STBTT_curveto segments. A lineto +// draws a line from previous endpoint to its x,y; a curveto +// draws a quadratic bezier from previous endpoint to +// its x,y, using cx,cy as the bezier control point. + +STBTT_DEF void stbtt_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *vertices); +// frees the data allocated above + +////////////////////////////////////////////////////////////////////////////// +// +// BITMAP RENDERING +// + +STBTT_DEF void stbtt_FreeBitmap(unsigned char *bitmap, void *userdata); +// frees the bitmap allocated below + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int codepoint, int *width, int *height, int *xoff, int *yoff); +// allocates a large-enough single-channel 8bpp bitmap and renders the +// specified character/glyph at the specified scale into it, with +// antialiasing. 0 is no coverage (transparent), 255 is fully covered (opaque). +// *width & *height are filled out with the width & height of the bitmap, +// which is stored left-to-right, top-to-bottom. +// +// xoff/yoff are the offset it pixel space from the glyph origin to the top-left of the bitmap + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int *width, int *height, int *xoff, int *yoff); +// the same as stbtt_GetCodepoitnBitmap, but you can specify a subpixel +// shift for the character + +STBTT_DEF void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint); +// the same as stbtt_GetCodepointBitmap, but you pass in storage for the bitmap +// in the form of 'output', with row spacing of 'out_stride' bytes. the bitmap +// is clipped to out_w/out_h bytes. Call stbtt_GetCodepointBitmapBox to get the +// width and height and positioning info for it first. + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint); +// same as stbtt_MakeCodepointBitmap, but you can specify a subpixel +// shift for the character + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int codepoint); +// same as stbtt_MakeCodepointBitmapSubpixel, but prefiltering +// is performed (see stbtt_PackSetOversampling) + +STBTT_DEF void stbtt_GetCodepointBitmapBox(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1); +// get the bbox of the bitmap centered around the glyph origin; so the +// bitmap width is ix1-ix0, height is iy1-iy0, and location to place +// the bitmap top left is (leftSideBearing*scale,iy0). +// (Note that the bitmap uses y-increases-down, but the shape uses +// y-increases-up, so CodepointBitmapBox and CodepointBox are inverted.) + +STBTT_DEF void stbtt_GetCodepointBitmapBoxSubpixel(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1); +// same as stbtt_GetCodepointBitmapBox, but you can specify a subpixel +// shift for the character + +// the following functions are equivalent to the above functions, but operate +// on glyph indices instead of Unicode codepoints (for efficiency) +STBTT_DEF unsigned char *stbtt_GetGlyphBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int glyph, int *width, int *height, int *xoff, int *yoff); +STBTT_DEF unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int glyph, int *width, int *height, int *xoff, int *yoff); +STBTT_DEF void stbtt_MakeGlyphBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int glyph); +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int glyph); +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int glyph); +STBTT_DEF void stbtt_GetGlyphBitmapBox(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1); +STBTT_DEF void stbtt_GetGlyphBitmapBoxSubpixel(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y,float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1); + + +// @TODO: don't expose this structure +typedef struct +{ + int w,h,stride; + unsigned char *pixels; +} stbtt__bitmap; + +// rasterize a shape with quadratic beziers into a bitmap +STBTT_DEF void stbtt_Rasterize(stbtt__bitmap *result, // 1-channel bitmap to draw into + float flatness_in_pixels, // allowable error of curve in pixels + stbtt_vertex *vertices, // array of vertices defining shape + int num_verts, // number of vertices in above array + float scale_x, float scale_y, // scale applied to input vertices + float shift_x, float shift_y, // translation applied to input vertices + int x_off, int y_off, // another translation applied to input + int invert, // if non-zero, vertically flip shape + void *userdata); // context for to STBTT_MALLOC + +////////////////////////////////////////////////////////////////////////////// +// +// Signed Distance Function (or Field) rendering + +STBTT_DEF void stbtt_FreeSDF(unsigned char *bitmap, void *userdata); +// frees the SDF bitmap allocated below + +STBTT_DEF unsigned char * stbtt_GetGlyphSDF(const stbtt_fontinfo *info, float scale, int glyph, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff); +STBTT_DEF unsigned char * stbtt_GetCodepointSDF(const stbtt_fontinfo *info, float scale, int codepoint, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff); +// These functions compute a discretized SDF field for a single character, suitable for storing +// in a single-channel texture, sampling with bilinear filtering, and testing against +// larger than some threshhold to produce scalable fonts. +// info -- the font +// scale -- controls the size of the resulting SDF bitmap, same as it would be creating a regular bitmap +// glyph/codepoint -- the character to generate the SDF for +// padding -- extra "pixels" around the character which are filled with the distance to the character (not 0), +// which allows effects like bit outlines +// onedge_value -- value 0-255 to test the SDF against to reconstruct the character (i.e. the isocontour of the character) +// pixel_dist_scale -- what value the SDF should increase by when moving one SDF "pixel" away from the edge (on the 0..255 scale) +// if positive, > onedge_value is inside; if negative, < onedge_value is inside +// width,height -- output height & width of the SDF bitmap (including padding) +// xoff,yoff -- output origin of the character +// return value -- a 2D array of bytes 0..255, width*height in size +// +// pixel_dist_scale & onedge_value are a scale & bias that allows you to make +// optimal use of the limited 0..255 for your application, trading off precision +// and special effects. SDF values outside the range 0..255 are clamped to 0..255. +// +// Example: +// scale = stbtt_ScaleForPixelHeight(22) +// padding = 5 +// onedge_value = 180 +// pixel_dist_scale = 180/5.0 = 36.0 +// +// This will create an SDF bitmap in which the character is about 22 pixels +// high but the whole bitmap is about 22+5+5=32 pixels high. To produce a filled +// shape, sample the SDF at each pixel and fill the pixel if the SDF value +// is greater than or equal to 180/255. (You'll actually want to antialias, +// which is beyond the scope of this example.) Additionally, you can compute +// offset outlines (e.g. to stroke the character border inside & outside, +// or only outside). For example, to fill outside the character up to 3 SDF +// pixels, you would compare against (180-36.0*3)/255 = 72/255. The above +// choice of variables maps a range from 5 pixels outside the shape to +// 2 pixels inside the shape to 0..255; this is intended primarily for apply +// outside effects only (the interior range is needed to allow proper +// antialiasing of the font at *smaller* sizes) +// +// The function computes the SDF analytically at each SDF pixel, not by e.g. +// building a higher-res bitmap and approximating it. In theory the quality +// should be as high as possible for an SDF of this size & representation, but +// unclear if this is true in practice (perhaps building a higher-res bitmap +// and computing from that can allow drop-out prevention). +// +// The algorithm has not been optimized at all, so expect it to be slow +// if computing lots of characters or very large sizes. + + + +////////////////////////////////////////////////////////////////////////////// +// +// Finding the right font... +// +// You should really just solve this offline, keep your own tables +// of what font is what, and don't try to get it out of the .ttf file. +// That's because getting it out of the .ttf file is really hard, because +// the names in the file can appear in many possible encodings, in many +// possible languages, and e.g. if you need a case-insensitive comparison, +// the details of that depend on the encoding & language in a complex way +// (actually underspecified in truetype, but also gigantic). +// +// But you can use the provided functions in two possible ways: +// stbtt_FindMatchingFont() will use *case-sensitive* comparisons on +// unicode-encoded names to try to find the font you want; +// you can run this before calling stbtt_InitFont() +// +// stbtt_GetFontNameString() lets you get any of the various strings +// from the file yourself and do your own comparisons on them. +// You have to have called stbtt_InitFont() first. + + +STBTT_DEF int stbtt_FindMatchingFont(const unsigned char *fontdata, const char *name, int flags); +// returns the offset (not index) of the font that matches, or -1 if none +// if you use STBTT_MACSTYLE_DONTCARE, use a font name like "Arial Bold". +// if you use any other flag, use a font name like "Arial"; this checks +// the 'macStyle' header field; i don't know if fonts set this consistently +#define STBTT_MACSTYLE_DONTCARE 0 +#define STBTT_MACSTYLE_BOLD 1 +#define STBTT_MACSTYLE_ITALIC 2 +#define STBTT_MACSTYLE_UNDERSCORE 4 +#define STBTT_MACSTYLE_NONE 8 // <= not same as 0, this makes us check the bitfield is 0 + +STBTT_DEF int stbtt_CompareUTF8toUTF16_bigendian(const char *s1, int len1, const char *s2, int len2); +// returns 1/0 whether the first string interpreted as utf8 is identical to +// the second string interpreted as big-endian utf16... useful for strings from next func + +STBTT_DEF const char *stbtt_GetFontNameString(const stbtt_fontinfo *font, int *length, int platformID, int encodingID, int languageID, int nameID); +// returns the string (which may be big-endian double byte, e.g. for unicode) +// and puts the length in bytes in *length. +// +// some of the values for the IDs are below; for more see the truetype spec: +// http://developer.apple.com/textfonts/TTRefMan/RM06/Chap6name.html +// http://www.microsoft.com/typography/otspec/name.htm + +enum { // platformID + STBTT_PLATFORM_ID_UNICODE =0, + STBTT_PLATFORM_ID_MAC =1, + STBTT_PLATFORM_ID_ISO =2, + STBTT_PLATFORM_ID_MICROSOFT =3 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_UNICODE + STBTT_UNICODE_EID_UNICODE_1_0 =0, + STBTT_UNICODE_EID_UNICODE_1_1 =1, + STBTT_UNICODE_EID_ISO_10646 =2, + STBTT_UNICODE_EID_UNICODE_2_0_BMP=3, + STBTT_UNICODE_EID_UNICODE_2_0_FULL=4 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_MICROSOFT + STBTT_MS_EID_SYMBOL =0, + STBTT_MS_EID_UNICODE_BMP =1, + STBTT_MS_EID_SHIFTJIS =2, + STBTT_MS_EID_UNICODE_FULL =10 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_MAC; same as Script Manager codes + STBTT_MAC_EID_ROMAN =0, STBTT_MAC_EID_ARABIC =4, + STBTT_MAC_EID_JAPANESE =1, STBTT_MAC_EID_HEBREW =5, + STBTT_MAC_EID_CHINESE_TRAD =2, STBTT_MAC_EID_GREEK =6, + STBTT_MAC_EID_KOREAN =3, STBTT_MAC_EID_RUSSIAN =7 +}; + +enum { // languageID for STBTT_PLATFORM_ID_MICROSOFT; same as LCID... + // problematic because there are e.g. 16 english LCIDs and 16 arabic LCIDs + STBTT_MS_LANG_ENGLISH =0x0409, STBTT_MS_LANG_ITALIAN =0x0410, + STBTT_MS_LANG_CHINESE =0x0804, STBTT_MS_LANG_JAPANESE =0x0411, + STBTT_MS_LANG_DUTCH =0x0413, STBTT_MS_LANG_KOREAN =0x0412, + STBTT_MS_LANG_FRENCH =0x040c, STBTT_MS_LANG_RUSSIAN =0x0419, + STBTT_MS_LANG_GERMAN =0x0407, STBTT_MS_LANG_SPANISH =0x0409, + STBTT_MS_LANG_HEBREW =0x040d, STBTT_MS_LANG_SWEDISH =0x041D +}; + +enum { // languageID for STBTT_PLATFORM_ID_MAC + STBTT_MAC_LANG_ENGLISH =0 , STBTT_MAC_LANG_JAPANESE =11, + STBTT_MAC_LANG_ARABIC =12, STBTT_MAC_LANG_KOREAN =23, + STBTT_MAC_LANG_DUTCH =4 , STBTT_MAC_LANG_RUSSIAN =32, + STBTT_MAC_LANG_FRENCH =1 , STBTT_MAC_LANG_SPANISH =6 , + STBTT_MAC_LANG_GERMAN =2 , STBTT_MAC_LANG_SWEDISH =5 , + STBTT_MAC_LANG_HEBREW =10, STBTT_MAC_LANG_CHINESE_SIMPLIFIED =33, + STBTT_MAC_LANG_ITALIAN =3 , STBTT_MAC_LANG_CHINESE_TRAD =19 +}; + +#ifdef __cplusplus +} +#endif + +#endif // __STB_INCLUDE_STB_TRUETYPE_H__ + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +//// +//// IMPLEMENTATION +//// +//// + +#ifdef STB_TRUETYPE_IMPLEMENTATION + +#ifndef STBTT_MAX_OVERSAMPLE +#define STBTT_MAX_OVERSAMPLE 8 +#endif + +#if STBTT_MAX_OVERSAMPLE > 255 +#error "STBTT_MAX_OVERSAMPLE cannot be > 255" +#endif + +typedef int stbtt__test_oversample_pow2[(STBTT_MAX_OVERSAMPLE & (STBTT_MAX_OVERSAMPLE-1)) == 0 ? 1 : -1]; + +#ifndef STBTT_RASTERIZER_VERSION +#define STBTT_RASTERIZER_VERSION 2 +#endif + +#ifdef _MSC_VER +#define STBTT__NOTUSED(v) (void)(v) +#else +#define STBTT__NOTUSED(v) (void)sizeof(v) +#endif + +////////////////////////////////////////////////////////////////////////// +// +// stbtt__buf helpers to parse data from file +// + +static stbtt_uint8 stbtt__buf_get8(stbtt__buf *b) +{ + if (b->cursor >= b->size) + return 0; + return b->data[b->cursor++]; +} + +static stbtt_uint8 stbtt__buf_peek8(stbtt__buf *b) +{ + if (b->cursor >= b->size) + return 0; + return b->data[b->cursor]; +} + +static void stbtt__buf_seek(stbtt__buf *b, int o) +{ + STBTT_assert(!(o > b->size || o < 0)); + b->cursor = (o > b->size || o < 0) ? b->size : o; +} + +static void stbtt__buf_skip(stbtt__buf *b, int o) +{ + stbtt__buf_seek(b, b->cursor + o); +} + +static stbtt_uint32 stbtt__buf_get(stbtt__buf *b, int n) +{ + stbtt_uint32 v = 0; + int i; + STBTT_assert(n >= 1 && n <= 4); + for (i = 0; i < n; i++) + v = (v << 8) | stbtt__buf_get8(b); + return v; +} + +static stbtt__buf stbtt__new_buf(const void *p, size_t size) +{ + stbtt__buf r; + STBTT_assert(size < 0x40000000); + r.data = (stbtt_uint8*) p; + r.size = (int) size; + r.cursor = 0; + return r; +} + +#define stbtt__buf_get16(b) stbtt__buf_get((b), 2) +#define stbtt__buf_get32(b) stbtt__buf_get((b), 4) + +static stbtt__buf stbtt__buf_range(const stbtt__buf *b, int o, int s) +{ + stbtt__buf r = stbtt__new_buf(NULL, 0); + if (o < 0 || s < 0 || o > b->size || s > b->size - o) return r; + r.data = b->data + o; + r.size = s; + return r; +} + +static stbtt__buf stbtt__cff_get_index(stbtt__buf *b) +{ + int count, start, offsize; + start = b->cursor; + count = stbtt__buf_get16(b); + if (count) { + offsize = stbtt__buf_get8(b); + STBTT_assert(offsize >= 1 && offsize <= 4); + stbtt__buf_skip(b, offsize * count); + stbtt__buf_skip(b, stbtt__buf_get(b, offsize) - 1); + } + return stbtt__buf_range(b, start, b->cursor - start); +} + +static stbtt_uint32 stbtt__cff_int(stbtt__buf *b) +{ + int b0 = stbtt__buf_get8(b); + if (b0 >= 32 && b0 <= 246) return b0 - 139; + else if (b0 >= 247 && b0 <= 250) return (b0 - 247)*256 + stbtt__buf_get8(b) + 108; + else if (b0 >= 251 && b0 <= 254) return -(b0 - 251)*256 - stbtt__buf_get8(b) - 108; + else if (b0 == 28) return stbtt__buf_get16(b); + else if (b0 == 29) return stbtt__buf_get32(b); + STBTT_assert(0); + return 0; +} + +static void stbtt__cff_skip_operand(stbtt__buf *b) { + int v, b0 = stbtt__buf_peek8(b); + STBTT_assert(b0 >= 28); + if (b0 == 30) { + stbtt__buf_skip(b, 1); + while (b->cursor < b->size) { + v = stbtt__buf_get8(b); + if ((v & 0xF) == 0xF || (v >> 4) == 0xF) + break; + } + } else { + stbtt__cff_int(b); + } +} + +static stbtt__buf stbtt__dict_get(stbtt__buf *b, int key) +{ + stbtt__buf_seek(b, 0); + while (b->cursor < b->size) { + int start = b->cursor, end, op; + while (stbtt__buf_peek8(b) >= 28) + stbtt__cff_skip_operand(b); + end = b->cursor; + op = stbtt__buf_get8(b); + if (op == 12) op = stbtt__buf_get8(b) | 0x100; + if (op == key) return stbtt__buf_range(b, start, end-start); + } + return stbtt__buf_range(b, 0, 0); +} + +static void stbtt__dict_get_ints(stbtt__buf *b, int key, int outcount, stbtt_uint32 *out) +{ + int i; + stbtt__buf operands = stbtt__dict_get(b, key); + for (i = 0; i < outcount && operands.cursor < operands.size; i++) + out[i] = stbtt__cff_int(&operands); +} + +static int stbtt__cff_index_count(stbtt__buf *b) +{ + stbtt__buf_seek(b, 0); + return stbtt__buf_get16(b); +} + +static stbtt__buf stbtt__cff_index_get(stbtt__buf b, int i) +{ + int count, offsize, start, end; + stbtt__buf_seek(&b, 0); + count = stbtt__buf_get16(&b); + offsize = stbtt__buf_get8(&b); + STBTT_assert(i >= 0 && i < count); + STBTT_assert(offsize >= 1 && offsize <= 4); + stbtt__buf_skip(&b, i*offsize); + start = stbtt__buf_get(&b, offsize); + end = stbtt__buf_get(&b, offsize); + return stbtt__buf_range(&b, 2+(count+1)*offsize+start, end - start); +} + +////////////////////////////////////////////////////////////////////////// +// +// accessors to parse data from file +// + +// on platforms that don't allow misaligned reads, if we want to allow +// truetype fonts that aren't padded to alignment, define ALLOW_UNALIGNED_TRUETYPE + +#define ttBYTE(p) (* (stbtt_uint8 *) (p)) +#define ttCHAR(p) (* (stbtt_int8 *) (p)) +#define ttFixed(p) ttLONG(p) + +static stbtt_uint16 ttUSHORT(stbtt_uint8 *p) { return p[0]*256 + p[1]; } +static stbtt_int16 ttSHORT(stbtt_uint8 *p) { return p[0]*256 + p[1]; } +static stbtt_uint32 ttULONG(stbtt_uint8 *p) { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; } +static stbtt_int32 ttLONG(stbtt_uint8 *p) { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; } + +#define stbtt_tag4(p,c0,c1,c2,c3) ((p)[0] == (c0) && (p)[1] == (c1) && (p)[2] == (c2) && (p)[3] == (c3)) +#define stbtt_tag(p,str) stbtt_tag4(p,str[0],str[1],str[2],str[3]) + +static int stbtt__isfont(stbtt_uint8 *font) +{ + // check the version number + if (stbtt_tag4(font, '1',0,0,0)) return 1; // TrueType 1 + if (stbtt_tag(font, "typ1")) return 1; // TrueType with type 1 font -- we don't support this! + if (stbtt_tag(font, "OTTO")) return 1; // OpenType with CFF + if (stbtt_tag4(font, 0,1,0,0)) return 1; // OpenType 1.0 + if (stbtt_tag(font, "true")) return 1; // Apple specification for TrueType fonts + return 0; +} + +// @OPTIMIZE: binary search +static stbtt_uint32 stbtt__find_table(stbtt_uint8 *data, stbtt_uint32 fontstart, const char *tag) +{ + stbtt_int32 num_tables = ttUSHORT(data+fontstart+4); + stbtt_uint32 tabledir = fontstart + 12; + stbtt_int32 i; + for (i=0; i < num_tables; ++i) { + stbtt_uint32 loc = tabledir + 16*i; + if (stbtt_tag(data+loc+0, tag)) + return ttULONG(data+loc+8); + } + return 0; +} + +static int stbtt_GetFontOffsetForIndex_internal(unsigned char *font_collection, int index) +{ + // if it's just a font, there's only one valid index + if (stbtt__isfont(font_collection)) + return index == 0 ? 0 : -1; + + // check if it's a TTC + if (stbtt_tag(font_collection, "ttcf")) { + // version 1? + if (ttULONG(font_collection+4) == 0x00010000 || ttULONG(font_collection+4) == 0x00020000) { + stbtt_int32 n = ttLONG(font_collection+8); + if (index >= n) + return -1; + return ttULONG(font_collection+12+index*4); + } + } + return -1; +} + +static int stbtt_GetNumberOfFonts_internal(unsigned char *font_collection) +{ + // if it's just a font, there's only one valid font + if (stbtt__isfont(font_collection)) + return 1; + + // check if it's a TTC + if (stbtt_tag(font_collection, "ttcf")) { + // version 1? + if (ttULONG(font_collection+4) == 0x00010000 || ttULONG(font_collection+4) == 0x00020000) { + return ttLONG(font_collection+8); + } + } + return 0; +} + +static stbtt__buf stbtt__get_subrs(stbtt__buf cff, stbtt__buf fontdict) +{ + stbtt_uint32 subrsoff = 0, private_loc[2] = { 0, 0 }; + stbtt__buf pdict; + stbtt__dict_get_ints(&fontdict, 18, 2, private_loc); + if (!private_loc[1] || !private_loc[0]) return stbtt__new_buf(NULL, 0); + pdict = stbtt__buf_range(&cff, private_loc[1], private_loc[0]); + stbtt__dict_get_ints(&pdict, 19, 1, &subrsoff); + if (!subrsoff) return stbtt__new_buf(NULL, 0); + stbtt__buf_seek(&cff, private_loc[1]+subrsoff); + return stbtt__cff_get_index(&cff); +} + +static int stbtt_InitFont_internal(stbtt_fontinfo *info, unsigned char *data, int fontstart) +{ + stbtt_uint32 cmap, t; + stbtt_int32 i,numTables; + + info->data = data; + info->fontstart = fontstart; + info->cff = stbtt__new_buf(NULL, 0); + + cmap = stbtt__find_table(data, fontstart, "cmap"); // required + info->loca = stbtt__find_table(data, fontstart, "loca"); // required + info->head = stbtt__find_table(data, fontstart, "head"); // required + info->glyf = stbtt__find_table(data, fontstart, "glyf"); // required + info->hhea = stbtt__find_table(data, fontstart, "hhea"); // required + info->hmtx = stbtt__find_table(data, fontstart, "hmtx"); // required + info->kern = stbtt__find_table(data, fontstart, "kern"); // not required + + if (!cmap || !info->head || !info->hhea || !info->hmtx) + return 0; + if (info->glyf) { + // required for truetype + if (!info->loca) return 0; + } else { + // initialization for CFF / Type2 fonts (OTF) + stbtt__buf b, topdict, topdictidx; + stbtt_uint32 cstype = 2, charstrings = 0, fdarrayoff = 0, fdselectoff = 0; + stbtt_uint32 cff; + + cff = stbtt__find_table(data, fontstart, "CFF "); + if (!cff) return 0; + + info->fontdicts = stbtt__new_buf(NULL, 0); + info->fdselect = stbtt__new_buf(NULL, 0); + + // @TODO this should use size from table (not 512MB) + info->cff = stbtt__new_buf(data+cff, 512*1024*1024); + b = info->cff; + + // read the header + stbtt__buf_skip(&b, 2); + stbtt__buf_seek(&b, stbtt__buf_get8(&b)); // hdrsize + + // @TODO the name INDEX could list multiple fonts, + // but we just use the first one. + stbtt__cff_get_index(&b); // name INDEX + topdictidx = stbtt__cff_get_index(&b); + topdict = stbtt__cff_index_get(topdictidx, 0); + stbtt__cff_get_index(&b); // string INDEX + info->gsubrs = stbtt__cff_get_index(&b); + + stbtt__dict_get_ints(&topdict, 17, 1, &charstrings); + stbtt__dict_get_ints(&topdict, 0x100 | 6, 1, &cstype); + stbtt__dict_get_ints(&topdict, 0x100 | 36, 1, &fdarrayoff); + stbtt__dict_get_ints(&topdict, 0x100 | 37, 1, &fdselectoff); + info->subrs = stbtt__get_subrs(b, topdict); + + // we only support Type 2 charstrings + if (cstype != 2) return 0; + if (charstrings == 0) return 0; + + if (fdarrayoff) { + // looks like a CID font + if (!fdselectoff) return 0; + stbtt__buf_seek(&b, fdarrayoff); + info->fontdicts = stbtt__cff_get_index(&b); + info->fdselect = stbtt__buf_range(&b, fdselectoff, b.size-fdselectoff); + } + + stbtt__buf_seek(&b, charstrings); + info->charstrings = stbtt__cff_get_index(&b); + } + + t = stbtt__find_table(data, fontstart, "maxp"); + if (t) + info->numGlyphs = ttUSHORT(data+t+4); + else + info->numGlyphs = 0xffff; + + // find a cmap encoding table we understand *now* to avoid searching + // later. (todo: could make this installable) + // the same regardless of glyph. + numTables = ttUSHORT(data + cmap + 2); + info->index_map = 0; + for (i=0; i < numTables; ++i) { + stbtt_uint32 encoding_record = cmap + 4 + 8 * i; + // find an encoding we understand: + switch(ttUSHORT(data+encoding_record)) { + case STBTT_PLATFORM_ID_MICROSOFT: + switch (ttUSHORT(data+encoding_record+2)) { + case STBTT_MS_EID_UNICODE_BMP: + case STBTT_MS_EID_UNICODE_FULL: + // MS/Unicode + info->index_map = cmap + ttULONG(data+encoding_record+4); + break; + } + break; + case STBTT_PLATFORM_ID_UNICODE: + // Mac/iOS has these + // all the encodingIDs are unicode, so we don't bother to check it + info->index_map = cmap + ttULONG(data+encoding_record+4); + break; + } + } + if (info->index_map == 0) + return 0; + + info->indexToLocFormat = ttUSHORT(data+info->head + 50); + return 1; +} + +STBTT_DEF int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codepoint) +{ + stbtt_uint8 *data = info->data; + stbtt_uint32 index_map = info->index_map; + + stbtt_uint16 format = ttUSHORT(data + index_map + 0); + if (format == 0) { // apple byte encoding + stbtt_int32 bytes = ttUSHORT(data + index_map + 2); + if (unicode_codepoint < bytes-6) + return ttBYTE(data + index_map + 6 + unicode_codepoint); + return 0; + } else if (format == 6) { + stbtt_uint32 first = ttUSHORT(data + index_map + 6); + stbtt_uint32 count = ttUSHORT(data + index_map + 8); + if ((stbtt_uint32) unicode_codepoint >= first && (stbtt_uint32) unicode_codepoint < first+count) + return ttUSHORT(data + index_map + 10 + (unicode_codepoint - first)*2); + return 0; + } else if (format == 2) { + STBTT_assert(0); // @TODO: high-byte mapping for japanese/chinese/korean + return 0; + } else if (format == 4) { // standard mapping for windows fonts: binary search collection of ranges + stbtt_uint16 segcount = ttUSHORT(data+index_map+6) >> 1; + stbtt_uint16 searchRange = ttUSHORT(data+index_map+8) >> 1; + stbtt_uint16 entrySelector = ttUSHORT(data+index_map+10); + stbtt_uint16 rangeShift = ttUSHORT(data+index_map+12) >> 1; + + // do a binary search of the segments + stbtt_uint32 endCount = index_map + 14; + stbtt_uint32 search = endCount; + + if (unicode_codepoint > 0xffff) + return 0; + + // they lie from endCount .. endCount + segCount + // but searchRange is the nearest power of two, so... + if (unicode_codepoint >= ttUSHORT(data + search + rangeShift*2)) + search += rangeShift*2; + + // now decrement to bias correctly to find smallest + search -= 2; + while (entrySelector) { + stbtt_uint16 end; + searchRange >>= 1; + end = ttUSHORT(data + search + searchRange*2); + if (unicode_codepoint > end) + search += searchRange*2; + --entrySelector; + } + search += 2; + + { + stbtt_uint16 offset, start; + stbtt_uint16 item = (stbtt_uint16) ((search - endCount) >> 1); + + STBTT_assert(unicode_codepoint <= ttUSHORT(data + endCount + 2*item)); + start = ttUSHORT(data + index_map + 14 + segcount*2 + 2 + 2*item); + if (unicode_codepoint < start) + return 0; + + offset = ttUSHORT(data + index_map + 14 + segcount*6 + 2 + 2*item); + if (offset == 0) + return (stbtt_uint16) (unicode_codepoint + ttSHORT(data + index_map + 14 + segcount*4 + 2 + 2*item)); + + return ttUSHORT(data + offset + (unicode_codepoint-start)*2 + index_map + 14 + segcount*6 + 2 + 2*item); + } + } else if (format == 12 || format == 13) { + stbtt_uint32 ngroups = ttULONG(data+index_map+12); + stbtt_int32 low,high; + low = 0; high = (stbtt_int32)ngroups; + // Binary search the right group. + while (low < high) { + stbtt_int32 mid = low + ((high-low) >> 1); // rounds down, so low <= mid < high + stbtt_uint32 start_char = ttULONG(data+index_map+16+mid*12); + stbtt_uint32 end_char = ttULONG(data+index_map+16+mid*12+4); + if ((stbtt_uint32) unicode_codepoint < start_char) + high = mid; + else if ((stbtt_uint32) unicode_codepoint > end_char) + low = mid+1; + else { + stbtt_uint32 start_glyph = ttULONG(data+index_map+16+mid*12+8); + if (format == 12) + return start_glyph + unicode_codepoint-start_char; + else // format == 13 + return start_glyph; + } + } + return 0; // not found + } + // @TODO + STBTT_assert(0); + return 0; +} + +STBTT_DEF int stbtt_GetCodepointShape(const stbtt_fontinfo *info, int unicode_codepoint, stbtt_vertex **vertices) +{ + return stbtt_GetGlyphShape(info, stbtt_FindGlyphIndex(info, unicode_codepoint), vertices); +} + +static void stbtt_setvertex(stbtt_vertex *v, stbtt_uint8 type, stbtt_int32 x, stbtt_int32 y, stbtt_int32 cx, stbtt_int32 cy) +{ + v->type = type; + v->x = (stbtt_int16) x; + v->y = (stbtt_int16) y; + v->cx = (stbtt_int16) cx; + v->cy = (stbtt_int16) cy; +} + +static int stbtt__GetGlyfOffset(const stbtt_fontinfo *info, int glyph_index) +{ + int g1,g2; + + STBTT_assert(!info->cff.size); + + if (glyph_index >= info->numGlyphs) return -1; // glyph index out of range + if (info->indexToLocFormat >= 2) return -1; // unknown index->glyph map format + + if (info->indexToLocFormat == 0) { + g1 = info->glyf + ttUSHORT(info->data + info->loca + glyph_index * 2) * 2; + g2 = info->glyf + ttUSHORT(info->data + info->loca + glyph_index * 2 + 2) * 2; + } else { + g1 = info->glyf + ttULONG (info->data + info->loca + glyph_index * 4); + g2 = info->glyf + ttULONG (info->data + info->loca + glyph_index * 4 + 4); + } + + return g1==g2 ? -1 : g1; // if length is 0, return -1 +} + +static int stbtt__GetGlyphInfoT2(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1); + +STBTT_DEF int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1) +{ + if (info->cff.size) { + stbtt__GetGlyphInfoT2(info, glyph_index, x0, y0, x1, y1); + } else { + int g = stbtt__GetGlyfOffset(info, glyph_index); + if (g < 0) return 0; + + if (x0) *x0 = ttSHORT(info->data + g + 2); + if (y0) *y0 = ttSHORT(info->data + g + 4); + if (x1) *x1 = ttSHORT(info->data + g + 6); + if (y1) *y1 = ttSHORT(info->data + g + 8); + } + return 1; +} + +STBTT_DEF int stbtt_GetCodepointBox(const stbtt_fontinfo *info, int codepoint, int *x0, int *y0, int *x1, int *y1) +{ + return stbtt_GetGlyphBox(info, stbtt_FindGlyphIndex(info,codepoint), x0,y0,x1,y1); +} + +STBTT_DEF int stbtt_IsGlyphEmpty(const stbtt_fontinfo *info, int glyph_index) +{ + stbtt_int16 numberOfContours; + int g; + if (info->cff.size) + return stbtt__GetGlyphInfoT2(info, glyph_index, NULL, NULL, NULL, NULL) == 0; + g = stbtt__GetGlyfOffset(info, glyph_index); + if (g < 0) return 1; + numberOfContours = ttSHORT(info->data + g); + return numberOfContours == 0; +} + +static int stbtt__close_shape(stbtt_vertex *vertices, int num_vertices, int was_off, int start_off, + stbtt_int32 sx, stbtt_int32 sy, stbtt_int32 scx, stbtt_int32 scy, stbtt_int32 cx, stbtt_int32 cy) +{ + if (start_off) { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, (cx+scx)>>1, (cy+scy)>>1, cx,cy); + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, sx,sy,scx,scy); + } else { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve,sx,sy,cx,cy); + else + stbtt_setvertex(&vertices[num_vertices++], STBTT_vline,sx,sy,0,0); + } + return num_vertices; +} + +static int stbtt__GetGlyphShapeTT(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) +{ + stbtt_int16 numberOfContours; + stbtt_uint8 *endPtsOfContours; + stbtt_uint8 *data = info->data; + stbtt_vertex *vertices=0; + int num_vertices=0; + int g = stbtt__GetGlyfOffset(info, glyph_index); + + *pvertices = NULL; + + if (g < 0) return 0; + + numberOfContours = ttSHORT(data + g); + + if (numberOfContours > 0) { + stbtt_uint8 flags=0,flagcount; + stbtt_int32 ins, i,j=0,m,n, next_move, was_off=0, off, start_off=0; + stbtt_int32 x,y,cx,cy,sx,sy, scx,scy; + stbtt_uint8 *points; + endPtsOfContours = (data + g + 10); + ins = ttUSHORT(data + g + 10 + numberOfContours * 2); + points = data + g + 10 + numberOfContours * 2 + 2 + ins; + + n = 1+ttUSHORT(endPtsOfContours + numberOfContours*2-2); + + m = n + 2*numberOfContours; // a loose bound on how many vertices we might need + vertices = (stbtt_vertex *) STBTT_malloc(m * sizeof(vertices[0]), info->userdata); + if (vertices == 0) + return 0; + + next_move = 0; + flagcount=0; + + // in first pass, we load uninterpreted data into the allocated array + // above, shifted to the end of the array so we won't overwrite it when + // we create our final data starting from the front + + off = m - n; // starting offset for uninterpreted data, regardless of how m ends up being calculated + + // first load flags + + for (i=0; i < n; ++i) { + if (flagcount == 0) { + flags = *points++; + if (flags & 8) + flagcount = *points++; + } else + --flagcount; + vertices[off+i].type = flags; + } + + // now load x coordinates + x=0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + if (flags & 2) { + stbtt_int16 dx = *points++; + x += (flags & 16) ? dx : -dx; // ??? + } else { + if (!(flags & 16)) { + x = x + (stbtt_int16) (points[0]*256 + points[1]); + points += 2; + } + } + vertices[off+i].x = (stbtt_int16) x; + } + + // now load y coordinates + y=0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + if (flags & 4) { + stbtt_int16 dy = *points++; + y += (flags & 32) ? dy : -dy; // ??? + } else { + if (!(flags & 32)) { + y = y + (stbtt_int16) (points[0]*256 + points[1]); + points += 2; + } + } + vertices[off+i].y = (stbtt_int16) y; + } + + // now convert them to our format + num_vertices=0; + sx = sy = cx = cy = scx = scy = 0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + x = (stbtt_int16) vertices[off+i].x; + y = (stbtt_int16) vertices[off+i].y; + + if (next_move == i) { + if (i != 0) + num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy); + + // now start the new one + start_off = !(flags & 1); + if (start_off) { + // if we start off with an off-curve point, then when we need to find a point on the curve + // where we can start, and we need to save some state for when we wraparound. + scx = x; + scy = y; + if (!(vertices[off+i+1].type & 1)) { + // next point is also a curve point, so interpolate an on-point curve + sx = (x + (stbtt_int32) vertices[off+i+1].x) >> 1; + sy = (y + (stbtt_int32) vertices[off+i+1].y) >> 1; + } else { + // otherwise just use the next point as our start point + sx = (stbtt_int32) vertices[off+i+1].x; + sy = (stbtt_int32) vertices[off+i+1].y; + ++i; // we're using point i+1 as the starting point, so skip it + } + } else { + sx = x; + sy = y; + } + stbtt_setvertex(&vertices[num_vertices++], STBTT_vmove,sx,sy,0,0); + was_off = 0; + next_move = 1 + ttUSHORT(endPtsOfContours+j*2); + ++j; + } else { + if (!(flags & 1)) { // if it's a curve + if (was_off) // two off-curve control points in a row means interpolate an on-curve midpoint + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, (cx+x)>>1, (cy+y)>>1, cx, cy); + cx = x; + cy = y; + was_off = 1; + } else { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, x,y, cx, cy); + else + stbtt_setvertex(&vertices[num_vertices++], STBTT_vline, x,y,0,0); + was_off = 0; + } + } + } + num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy); + } else if (numberOfContours == -1) { + // Compound shapes. + int more = 1; + stbtt_uint8 *comp = data + g + 10; + num_vertices = 0; + vertices = 0; + while (more) { + stbtt_uint16 flags, gidx; + int comp_num_verts = 0, i; + stbtt_vertex *comp_verts = 0, *tmp = 0; + float mtx[6] = {1,0,0,1,0,0}, m, n; + + flags = ttSHORT(comp); comp+=2; + gidx = ttSHORT(comp); comp+=2; + + if (flags & 2) { // XY values + if (flags & 1) { // shorts + mtx[4] = ttSHORT(comp); comp+=2; + mtx[5] = ttSHORT(comp); comp+=2; + } else { + mtx[4] = ttCHAR(comp); comp+=1; + mtx[5] = ttCHAR(comp); comp+=1; + } + } + else { + // @TODO handle matching point + STBTT_assert(0); + } + if (flags & (1<<3)) { // WE_HAVE_A_SCALE + mtx[0] = mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = mtx[2] = 0; + } else if (flags & (1<<6)) { // WE_HAVE_AN_X_AND_YSCALE + mtx[0] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = mtx[2] = 0; + mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + } else if (flags & (1<<7)) { // WE_HAVE_A_TWO_BY_TWO + mtx[0] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[2] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + } + + // Find transformation scales. + m = (float) STBTT_sqrt(mtx[0]*mtx[0] + mtx[1]*mtx[1]); + n = (float) STBTT_sqrt(mtx[2]*mtx[2] + mtx[3]*mtx[3]); + + // Get indexed glyph. + comp_num_verts = stbtt_GetGlyphShape(info, gidx, &comp_verts); + if (comp_num_verts > 0) { + // Transform vertices. + for (i = 0; i < comp_num_verts; ++i) { + stbtt_vertex* v = &comp_verts[i]; + stbtt_vertex_type x,y; + x=v->x; y=v->y; + v->x = (stbtt_vertex_type)(m * (mtx[0]*x + mtx[2]*y + mtx[4])); + v->y = (stbtt_vertex_type)(n * (mtx[1]*x + mtx[3]*y + mtx[5])); + x=v->cx; y=v->cy; + v->cx = (stbtt_vertex_type)(m * (mtx[0]*x + mtx[2]*y + mtx[4])); + v->cy = (stbtt_vertex_type)(n * (mtx[1]*x + mtx[3]*y + mtx[5])); + } + // Append vertices. + tmp = (stbtt_vertex*)STBTT_malloc((num_vertices+comp_num_verts)*sizeof(stbtt_vertex), info->userdata); + if (!tmp) { + if (vertices) STBTT_free(vertices, info->userdata); + if (comp_verts) STBTT_free(comp_verts, info->userdata); + return 0; + } + if (num_vertices > 0) STBTT_memcpy(tmp, vertices, num_vertices*sizeof(stbtt_vertex)); + STBTT_memcpy(tmp+num_vertices, comp_verts, comp_num_verts*sizeof(stbtt_vertex)); + if (vertices) STBTT_free(vertices, info->userdata); + vertices = tmp; + STBTT_free(comp_verts, info->userdata); + num_vertices += comp_num_verts; + } + // More components ? + more = flags & (1<<5); + } + } else if (numberOfContours < 0) { + // @TODO other compound variations? + STBTT_assert(0); + } else { + // numberOfCounters == 0, do nothing + } + + *pvertices = vertices; + return num_vertices; +} + +typedef struct +{ + int bounds; + int started; + float first_x, first_y; + float x, y; + stbtt_int32 min_x, max_x, min_y, max_y; + + stbtt_vertex *pvertices; + int num_vertices; +} stbtt__csctx; + +#define STBTT__CSCTX_INIT(bounds) {bounds,0, 0,0, 0,0, 0,0,0,0, NULL, 0} + +static void stbtt__track_vertex(stbtt__csctx *c, stbtt_int32 x, stbtt_int32 y) +{ + if (x > c->max_x || !c->started) c->max_x = x; + if (y > c->max_y || !c->started) c->max_y = y; + if (x < c->min_x || !c->started) c->min_x = x; + if (y < c->min_y || !c->started) c->min_y = y; + c->started = 1; +} + +static void stbtt__csctx_v(stbtt__csctx *c, stbtt_uint8 type, stbtt_int32 x, stbtt_int32 y, stbtt_int32 cx, stbtt_int32 cy, stbtt_int32 cx1, stbtt_int32 cy1) +{ + if (c->bounds) { + stbtt__track_vertex(c, x, y); + if (type == STBTT_vcubic) { + stbtt__track_vertex(c, cx, cy); + stbtt__track_vertex(c, cx1, cy1); + } + } else { + stbtt_setvertex(&c->pvertices[c->num_vertices], type, x, y, cx, cy); + c->pvertices[c->num_vertices].cx1 = (stbtt_int16) cx1; + c->pvertices[c->num_vertices].cy1 = (stbtt_int16) cy1; + } + c->num_vertices++; +} + +static void stbtt__csctx_close_shape(stbtt__csctx *ctx) +{ + if (ctx->first_x != ctx->x || ctx->first_y != ctx->y) + stbtt__csctx_v(ctx, STBTT_vline, (int)ctx->first_x, (int)ctx->first_y, 0, 0, 0, 0); +} + +static void stbtt__csctx_rmove_to(stbtt__csctx *ctx, float dx, float dy) +{ + stbtt__csctx_close_shape(ctx); + ctx->first_x = ctx->x = ctx->x + dx; + ctx->first_y = ctx->y = ctx->y + dy; + stbtt__csctx_v(ctx, STBTT_vmove, (int)ctx->x, (int)ctx->y, 0, 0, 0, 0); +} + +static void stbtt__csctx_rline_to(stbtt__csctx *ctx, float dx, float dy) +{ + ctx->x += dx; + ctx->y += dy; + stbtt__csctx_v(ctx, STBTT_vline, (int)ctx->x, (int)ctx->y, 0, 0, 0, 0); +} + +static void stbtt__csctx_rccurve_to(stbtt__csctx *ctx, float dx1, float dy1, float dx2, float dy2, float dx3, float dy3) +{ + float cx1 = ctx->x + dx1; + float cy1 = ctx->y + dy1; + float cx2 = cx1 + dx2; + float cy2 = cy1 + dy2; + ctx->x = cx2 + dx3; + ctx->y = cy2 + dy3; + stbtt__csctx_v(ctx, STBTT_vcubic, (int)ctx->x, (int)ctx->y, (int)cx1, (int)cy1, (int)cx2, (int)cy2); +} + +static stbtt__buf stbtt__get_subr(stbtt__buf idx, int n) +{ + int count = stbtt__cff_index_count(&idx); + int bias = 107; + if (count >= 33900) + bias = 32768; + else if (count >= 1240) + bias = 1131; + n += bias; + if (n < 0 || n >= count) + return stbtt__new_buf(NULL, 0); + return stbtt__cff_index_get(idx, n); +} + +static stbtt__buf stbtt__cid_get_glyph_subrs(const stbtt_fontinfo *info, int glyph_index) +{ + stbtt__buf fdselect = info->fdselect; + int nranges, start, end, v, fmt, fdselector = -1, i; + + stbtt__buf_seek(&fdselect, 0); + fmt = stbtt__buf_get8(&fdselect); + if (fmt == 0) { + // untested + stbtt__buf_skip(&fdselect, glyph_index); + fdselector = stbtt__buf_get8(&fdselect); + } else if (fmt == 3) { + nranges = stbtt__buf_get16(&fdselect); + start = stbtt__buf_get16(&fdselect); + for (i = 0; i < nranges; i++) { + v = stbtt__buf_get8(&fdselect); + end = stbtt__buf_get16(&fdselect); + if (glyph_index >= start && glyph_index < end) { + fdselector = v; + break; + } + start = end; + } + } + if (fdselector == -1) stbtt__new_buf(NULL, 0); + return stbtt__get_subrs(info->cff, stbtt__cff_index_get(info->fontdicts, fdselector)); +} + +static int stbtt__run_charstring(const stbtt_fontinfo *info, int glyph_index, stbtt__csctx *c) +{ + int in_header = 1, maskbits = 0, subr_stack_height = 0, sp = 0, v, i, b0; + int has_subrs = 0, clear_stack; + float s[48]; + stbtt__buf subr_stack[10], subrs = info->subrs, b; + float f; + +#define STBTT__CSERR(s) (0) + + // this currently ignores the initial width value, which isn't needed if we have hmtx + b = stbtt__cff_index_get(info->charstrings, glyph_index); + while (b.cursor < b.size) { + i = 0; + clear_stack = 1; + b0 = stbtt__buf_get8(&b); + switch (b0) { + // @TODO implement hinting + case 0x13: // hintmask + case 0x14: // cntrmask + if (in_header) + maskbits += (sp / 2); // implicit "vstem" + in_header = 0; + stbtt__buf_skip(&b, (maskbits + 7) / 8); + break; + + case 0x01: // hstem + case 0x03: // vstem + case 0x12: // hstemhm + case 0x17: // vstemhm + maskbits += (sp / 2); + break; + + case 0x15: // rmoveto + in_header = 0; + if (sp < 2) return STBTT__CSERR("rmoveto stack"); + stbtt__csctx_rmove_to(c, s[sp-2], s[sp-1]); + break; + case 0x04: // vmoveto + in_header = 0; + if (sp < 1) return STBTT__CSERR("vmoveto stack"); + stbtt__csctx_rmove_to(c, 0, s[sp-1]); + break; + case 0x16: // hmoveto + in_header = 0; + if (sp < 1) return STBTT__CSERR("hmoveto stack"); + stbtt__csctx_rmove_to(c, s[sp-1], 0); + break; + + case 0x05: // rlineto + if (sp < 2) return STBTT__CSERR("rlineto stack"); + for (; i + 1 < sp; i += 2) + stbtt__csctx_rline_to(c, s[i], s[i+1]); + break; + + // hlineto/vlineto and vhcurveto/hvcurveto alternate horizontal and vertical + // starting from a different place. + + case 0x07: // vlineto + if (sp < 1) return STBTT__CSERR("vlineto stack"); + goto vlineto; + case 0x06: // hlineto + if (sp < 1) return STBTT__CSERR("hlineto stack"); + for (;;) { + if (i >= sp) break; + stbtt__csctx_rline_to(c, s[i], 0); + i++; + vlineto: + if (i >= sp) break; + stbtt__csctx_rline_to(c, 0, s[i]); + i++; + } + break; + + case 0x1F: // hvcurveto + if (sp < 4) return STBTT__CSERR("hvcurveto stack"); + goto hvcurveto; + case 0x1E: // vhcurveto + if (sp < 4) return STBTT__CSERR("vhcurveto stack"); + for (;;) { + if (i + 3 >= sp) break; + stbtt__csctx_rccurve_to(c, 0, s[i], s[i+1], s[i+2], s[i+3], (sp - i == 5) ? s[i + 4] : 0.0f); + i += 4; + hvcurveto: + if (i + 3 >= sp) break; + stbtt__csctx_rccurve_to(c, s[i], 0, s[i+1], s[i+2], (sp - i == 5) ? s[i+4] : 0.0f, s[i+3]); + i += 4; + } + break; + + case 0x08: // rrcurveto + if (sp < 6) return STBTT__CSERR("rcurveline stack"); + for (; i + 5 < sp; i += 6) + stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); + break; + + case 0x18: // rcurveline + if (sp < 8) return STBTT__CSERR("rcurveline stack"); + for (; i + 5 < sp - 2; i += 6) + stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); + if (i + 1 >= sp) return STBTT__CSERR("rcurveline stack"); + stbtt__csctx_rline_to(c, s[i], s[i+1]); + break; + + case 0x19: // rlinecurve + if (sp < 8) return STBTT__CSERR("rlinecurve stack"); + for (; i + 1 < sp - 6; i += 2) + stbtt__csctx_rline_to(c, s[i], s[i+1]); + if (i + 5 >= sp) return STBTT__CSERR("rlinecurve stack"); + stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); + break; + + case 0x1A: // vvcurveto + case 0x1B: // hhcurveto + if (sp < 4) return STBTT__CSERR("(vv|hh)curveto stack"); + f = 0.0; + if (sp & 1) { f = s[i]; i++; } + for (; i + 3 < sp; i += 4) { + if (b0 == 0x1B) + stbtt__csctx_rccurve_to(c, s[i], f, s[i+1], s[i+2], s[i+3], 0.0); + else + stbtt__csctx_rccurve_to(c, f, s[i], s[i+1], s[i+2], 0.0, s[i+3]); + f = 0.0; + } + break; + + case 0x0A: // callsubr + if (!has_subrs) { + if (info->fdselect.size) + subrs = stbtt__cid_get_glyph_subrs(info, glyph_index); + has_subrs = 1; + } + // fallthrough + case 0x1D: // callgsubr + if (sp < 1) return STBTT__CSERR("call(g|)subr stack"); + v = (int) s[--sp]; + if (subr_stack_height >= 10) return STBTT__CSERR("recursion limit"); + subr_stack[subr_stack_height++] = b; + b = stbtt__get_subr(b0 == 0x0A ? subrs : info->gsubrs, v); + if (b.size == 0) return STBTT__CSERR("subr not found"); + b.cursor = 0; + clear_stack = 0; + break; + + case 0x0B: // return + if (subr_stack_height <= 0) return STBTT__CSERR("return outside subr"); + b = subr_stack[--subr_stack_height]; + clear_stack = 0; + break; + + case 0x0E: // endchar + stbtt__csctx_close_shape(c); + return 1; + + case 0x0C: { // two-byte escape + float dx1, dx2, dx3, dx4, dx5, dx6, dy1, dy2, dy3, dy4, dy5, dy6; + float dx, dy; + int b1 = stbtt__buf_get8(&b); + switch (b1) { + // @TODO These "flex" implementations ignore the flex-depth and resolution, + // and always draw beziers. + case 0x22: // hflex + if (sp < 7) return STBTT__CSERR("hflex stack"); + dx1 = s[0]; + dx2 = s[1]; + dy2 = s[2]; + dx3 = s[3]; + dx4 = s[4]; + dx5 = s[5]; + dx6 = s[6]; + stbtt__csctx_rccurve_to(c, dx1, 0, dx2, dy2, dx3, 0); + stbtt__csctx_rccurve_to(c, dx4, 0, dx5, -dy2, dx6, 0); + break; + + case 0x23: // flex + if (sp < 13) return STBTT__CSERR("flex stack"); + dx1 = s[0]; + dy1 = s[1]; + dx2 = s[2]; + dy2 = s[3]; + dx3 = s[4]; + dy3 = s[5]; + dx4 = s[6]; + dy4 = s[7]; + dx5 = s[8]; + dy5 = s[9]; + dx6 = s[10]; + dy6 = s[11]; + //fd is s[12] + stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, dy3); + stbtt__csctx_rccurve_to(c, dx4, dy4, dx5, dy5, dx6, dy6); + break; + + case 0x24: // hflex1 + if (sp < 9) return STBTT__CSERR("hflex1 stack"); + dx1 = s[0]; + dy1 = s[1]; + dx2 = s[2]; + dy2 = s[3]; + dx3 = s[4]; + dx4 = s[5]; + dx5 = s[6]; + dy5 = s[7]; + dx6 = s[8]; + stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, 0); + stbtt__csctx_rccurve_to(c, dx4, 0, dx5, dy5, dx6, -(dy1+dy2+dy5)); + break; + + case 0x25: // flex1 + if (sp < 11) return STBTT__CSERR("flex1 stack"); + dx1 = s[0]; + dy1 = s[1]; + dx2 = s[2]; + dy2 = s[3]; + dx3 = s[4]; + dy3 = s[5]; + dx4 = s[6]; + dy4 = s[7]; + dx5 = s[8]; + dy5 = s[9]; + dx6 = dy6 = s[10]; + dx = dx1+dx2+dx3+dx4+dx5; + dy = dy1+dy2+dy3+dy4+dy5; + if (STBTT_fabs(dx) > STBTT_fabs(dy)) + dy6 = -dy; + else + dx6 = -dx; + stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, dy3); + stbtt__csctx_rccurve_to(c, dx4, dy4, dx5, dy5, dx6, dy6); + break; + + default: + return STBTT__CSERR("unimplemented"); + } + } break; + + default: + if (b0 != 255 && b0 != 28 && (b0 < 32 || b0 > 254)) + return STBTT__CSERR("reserved operator"); + + // push immediate + if (b0 == 255) { + f = (float)stbtt__buf_get32(&b) / 0x10000; + } else { + stbtt__buf_skip(&b, -1); + f = (float)(stbtt_int16)stbtt__cff_int(&b); + } + if (sp >= 48) return STBTT__CSERR("push stack overflow"); + s[sp++] = f; + clear_stack = 0; + break; + } + if (clear_stack) sp = 0; + } + return STBTT__CSERR("no endchar"); + +#undef STBTT__CSERR +} + +static int stbtt__GetGlyphShapeT2(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) +{ + // runs the charstring twice, once to count and once to output (to avoid realloc) + stbtt__csctx count_ctx = STBTT__CSCTX_INIT(1); + stbtt__csctx output_ctx = STBTT__CSCTX_INIT(0); + if (stbtt__run_charstring(info, glyph_index, &count_ctx)) { + *pvertices = (stbtt_vertex*)STBTT_malloc(count_ctx.num_vertices*sizeof(stbtt_vertex), info->userdata); + output_ctx.pvertices = *pvertices; + if (stbtt__run_charstring(info, glyph_index, &output_ctx)) { + STBTT_assert(output_ctx.num_vertices == count_ctx.num_vertices); + return output_ctx.num_vertices; + } + } + *pvertices = NULL; + return 0; +} + +static int stbtt__GetGlyphInfoT2(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1) +{ + stbtt__csctx c = STBTT__CSCTX_INIT(1); + int r = stbtt__run_charstring(info, glyph_index, &c); + if (x0) { + *x0 = r ? c.min_x : 0; + *y0 = r ? c.min_y : 0; + *x1 = r ? c.max_x : 0; + *y1 = r ? c.max_y : 0; + } + return r ? c.num_vertices : 0; +} + +STBTT_DEF int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) +{ + if (!info->cff.size) + return stbtt__GetGlyphShapeTT(info, glyph_index, pvertices); + else + return stbtt__GetGlyphShapeT2(info, glyph_index, pvertices); +} + +STBTT_DEF void stbtt_GetGlyphHMetrics(const stbtt_fontinfo *info, int glyph_index, int *advanceWidth, int *leftSideBearing) +{ + stbtt_uint16 numOfLongHorMetrics = ttUSHORT(info->data+info->hhea + 34); + if (glyph_index < numOfLongHorMetrics) { + if (advanceWidth) *advanceWidth = ttSHORT(info->data + info->hmtx + 4*glyph_index); + if (leftSideBearing) *leftSideBearing = ttSHORT(info->data + info->hmtx + 4*glyph_index + 2); + } else { + if (advanceWidth) *advanceWidth = ttSHORT(info->data + info->hmtx + 4*(numOfLongHorMetrics-1)); + if (leftSideBearing) *leftSideBearing = ttSHORT(info->data + info->hmtx + 4*numOfLongHorMetrics + 2*(glyph_index - numOfLongHorMetrics)); + } +} + +STBTT_DEF int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2) +{ + stbtt_uint8 *data = info->data + info->kern; + stbtt_uint32 needle, straw; + int l, r, m; + + // we only look at the first table. it must be 'horizontal' and format 0. + if (!info->kern) + return 0; + if (ttUSHORT(data+2) < 1) // number of tables, need at least 1 + return 0; + if (ttUSHORT(data+8) != 1) // horizontal flag must be set in format + return 0; + + l = 0; + r = ttUSHORT(data+10) - 1; + needle = glyph1 << 16 | glyph2; + while (l <= r) { + m = (l + r) >> 1; + straw = ttULONG(data+18+(m*6)); // note: unaligned read + if (needle < straw) + r = m - 1; + else if (needle > straw) + l = m + 1; + else + return ttSHORT(data+22+(m*6)); + } + return 0; +} + +STBTT_DEF int stbtt_GetCodepointKernAdvance(const stbtt_fontinfo *info, int ch1, int ch2) +{ + if (!info->kern) // if no kerning table, don't waste time looking up both codepoint->glyphs + return 0; + return stbtt_GetGlyphKernAdvance(info, stbtt_FindGlyphIndex(info,ch1), stbtt_FindGlyphIndex(info,ch2)); +} + +STBTT_DEF void stbtt_GetCodepointHMetrics(const stbtt_fontinfo *info, int codepoint, int *advanceWidth, int *leftSideBearing) +{ + stbtt_GetGlyphHMetrics(info, stbtt_FindGlyphIndex(info,codepoint), advanceWidth, leftSideBearing); +} + +STBTT_DEF void stbtt_GetFontVMetrics(const stbtt_fontinfo *info, int *ascent, int *descent, int *lineGap) +{ + if (ascent ) *ascent = ttSHORT(info->data+info->hhea + 4); + if (descent) *descent = ttSHORT(info->data+info->hhea + 6); + if (lineGap) *lineGap = ttSHORT(info->data+info->hhea + 8); +} + +STBTT_DEF void stbtt_GetFontBoundingBox(const stbtt_fontinfo *info, int *x0, int *y0, int *x1, int *y1) +{ + *x0 = ttSHORT(info->data + info->head + 36); + *y0 = ttSHORT(info->data + info->head + 38); + *x1 = ttSHORT(info->data + info->head + 40); + *y1 = ttSHORT(info->data + info->head + 42); +} + +STBTT_DEF float stbtt_ScaleForPixelHeight(const stbtt_fontinfo *info, float height) +{ + int fheight = ttSHORT(info->data + info->hhea + 4) - ttSHORT(info->data + info->hhea + 6); + return (float) height / fheight; +} + +STBTT_DEF float stbtt_ScaleForMappingEmToPixels(const stbtt_fontinfo *info, float pixels) +{ + int unitsPerEm = ttUSHORT(info->data + info->head + 18); + return pixels / unitsPerEm; +} + +STBTT_DEF void stbtt_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *v) +{ + STBTT_free(v, info->userdata); +} + +////////////////////////////////////////////////////////////////////////////// +// +// antialiasing software rasterizer +// + +STBTT_DEF void stbtt_GetGlyphBitmapBoxSubpixel(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y,float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + int x0=0,y0=0,x1,y1; // =0 suppresses compiler warning + if (!stbtt_GetGlyphBox(font, glyph, &x0,&y0,&x1,&y1)) { + // e.g. space character + if (ix0) *ix0 = 0; + if (iy0) *iy0 = 0; + if (ix1) *ix1 = 0; + if (iy1) *iy1 = 0; + } else { + // move to integral bboxes (treating pixels as little squares, what pixels get touched)? + if (ix0) *ix0 = STBTT_ifloor( x0 * scale_x + shift_x); + if (iy0) *iy0 = STBTT_ifloor(-y1 * scale_y + shift_y); + if (ix1) *ix1 = STBTT_iceil ( x1 * scale_x + shift_x); + if (iy1) *iy1 = STBTT_iceil (-y0 * scale_y + shift_y); + } +} + +STBTT_DEF void stbtt_GetGlyphBitmapBox(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetGlyphBitmapBoxSubpixel(font, glyph, scale_x, scale_y,0.0f,0.0f, ix0, iy0, ix1, iy1); +} + +STBTT_DEF void stbtt_GetCodepointBitmapBoxSubpixel(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetGlyphBitmapBoxSubpixel(font, stbtt_FindGlyphIndex(font,codepoint), scale_x, scale_y,shift_x,shift_y, ix0,iy0,ix1,iy1); +} + +STBTT_DEF void stbtt_GetCodepointBitmapBox(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetCodepointBitmapBoxSubpixel(font, codepoint, scale_x, scale_y,0.0f,0.0f, ix0,iy0,ix1,iy1); +} + +////////////////////////////////////////////////////////////////////////////// +// +// Rasterizer + +typedef struct stbtt__hheap_chunk +{ + struct stbtt__hheap_chunk *next; +} stbtt__hheap_chunk; + +typedef struct stbtt__hheap +{ + struct stbtt__hheap_chunk *head; + void *first_free; + int num_remaining_in_head_chunk; +} stbtt__hheap; + +static void *stbtt__hheap_alloc(stbtt__hheap *hh, size_t size, void *userdata) +{ + if (hh->first_free) { + void *p = hh->first_free; + hh->first_free = * (void **) p; + return p; + } else { + if (hh->num_remaining_in_head_chunk == 0) { + int count = (size < 32 ? 2000 : size < 128 ? 800 : 100); + stbtt__hheap_chunk *c = (stbtt__hheap_chunk *) STBTT_malloc(sizeof(stbtt__hheap_chunk) + size * count, userdata); + if (c == NULL) + return NULL; + c->next = hh->head; + hh->head = c; + hh->num_remaining_in_head_chunk = count; + } + --hh->num_remaining_in_head_chunk; + return (char *) (hh->head) + size * hh->num_remaining_in_head_chunk; + } +} + +static void stbtt__hheap_free(stbtt__hheap *hh, void *p) +{ + *(void **) p = hh->first_free; + hh->first_free = p; +} + +static void stbtt__hheap_cleanup(stbtt__hheap *hh, void *userdata) +{ + stbtt__hheap_chunk *c = hh->head; + while (c) { + stbtt__hheap_chunk *n = c->next; + STBTT_free(c, userdata); + c = n; + } +} + +typedef struct stbtt__edge { + float x0,y0, x1,y1; + int invert; +} stbtt__edge; + + +typedef struct stbtt__active_edge +{ + struct stbtt__active_edge *next; + #if STBTT_RASTERIZER_VERSION==1 + int x,dx; + float ey; + int direction; + #elif STBTT_RASTERIZER_VERSION==2 + float fx,fdx,fdy; + float direction; + float sy; + float ey; + #else + #error "Unrecognized value of STBTT_RASTERIZER_VERSION" + #endif +} stbtt__active_edge; + +#if STBTT_RASTERIZER_VERSION == 1 +#define STBTT_FIXSHIFT 10 +#define STBTT_FIX (1 << STBTT_FIXSHIFT) +#define STBTT_FIXMASK (STBTT_FIX-1) + +static stbtt__active_edge *stbtt__new_active(stbtt__hheap *hh, stbtt__edge *e, int off_x, float start_point, void *userdata) +{ + stbtt__active_edge *z = (stbtt__active_edge *) stbtt__hheap_alloc(hh, sizeof(*z), userdata); + float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0); + STBTT_assert(z != NULL); + if (!z) return z; + + // round dx down to avoid overshooting + if (dxdy < 0) + z->dx = -STBTT_ifloor(STBTT_FIX * -dxdy); + else + z->dx = STBTT_ifloor(STBTT_FIX * dxdy); + + z->x = STBTT_ifloor(STBTT_FIX * e->x0 + z->dx * (start_point - e->y0)); // use z->dx so when we offset later it's by the same amount + z->x -= off_x * STBTT_FIX; + + z->ey = e->y1; + z->next = 0; + z->direction = e->invert ? 1 : -1; + return z; +} +#elif STBTT_RASTERIZER_VERSION == 2 +static stbtt__active_edge *stbtt__new_active(stbtt__hheap *hh, stbtt__edge *e, int off_x, float start_point, void *userdata) +{ + stbtt__active_edge *z = (stbtt__active_edge *) stbtt__hheap_alloc(hh, sizeof(*z), userdata); + float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0); + STBTT_assert(z != NULL); + //STBTT_assert(e->y0 <= start_point); + if (!z) return z; + z->fdx = dxdy; + z->fdy = dxdy != 0.0f ? (1.0f/dxdy) : 0.0f; + z->fx = e->x0 + dxdy * (start_point - e->y0); + z->fx -= off_x; + z->direction = e->invert ? 1.0f : -1.0f; + z->sy = e->y0; + z->ey = e->y1; + z->next = 0; + return z; +} +#else +#error "Unrecognized value of STBTT_RASTERIZER_VERSION" +#endif + +#if STBTT_RASTERIZER_VERSION == 1 +// note: this routine clips fills that extend off the edges... ideally this +// wouldn't happen, but it could happen if the truetype glyph bounding boxes +// are wrong, or if the user supplies a too-small bitmap +static void stbtt__fill_active_edges(unsigned char *scanline, int len, stbtt__active_edge *e, int max_weight) +{ + // non-zero winding fill + int x0=0, w=0; + + while (e) { + if (w == 0) { + // if we're currently at zero, we need to record the edge start point + x0 = e->x; w += e->direction; + } else { + int x1 = e->x; w += e->direction; + // if we went to zero, we need to draw + if (w == 0) { + int i = x0 >> STBTT_FIXSHIFT; + int j = x1 >> STBTT_FIXSHIFT; + + if (i < len && j >= 0) { + if (i == j) { + // x0,x1 are the same pixel, so compute combined coverage + scanline[i] = scanline[i] + (stbtt_uint8) ((x1 - x0) * max_weight >> STBTT_FIXSHIFT); + } else { + if (i >= 0) // add antialiasing for x0 + scanline[i] = scanline[i] + (stbtt_uint8) (((STBTT_FIX - (x0 & STBTT_FIXMASK)) * max_weight) >> STBTT_FIXSHIFT); + else + i = -1; // clip + + if (j < len) // add antialiasing for x1 + scanline[j] = scanline[j] + (stbtt_uint8) (((x1 & STBTT_FIXMASK) * max_weight) >> STBTT_FIXSHIFT); + else + j = len; // clip + + for (++i; i < j; ++i) // fill pixels between x0 and x1 + scanline[i] = scanline[i] + (stbtt_uint8) max_weight; + } + } + } + } + + e = e->next; + } +} + +static void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e, int n, int vsubsample, int off_x, int off_y, void *userdata) +{ + stbtt__hheap hh = { 0, 0, 0 }; + stbtt__active_edge *active = NULL; + int y,j=0; + int max_weight = (255 / vsubsample); // weight per vertical scanline + int s; // vertical subsample index + unsigned char scanline_data[512], *scanline; + + if (result->w > 512) + scanline = (unsigned char *) STBTT_malloc(result->w, userdata); + else + scanline = scanline_data; + + y = off_y * vsubsample; + e[n].y0 = (off_y + result->h) * (float) vsubsample + 1; + + while (j < result->h) { + STBTT_memset(scanline, 0, result->w); + for (s=0; s < vsubsample; ++s) { + // find center of pixel for this scanline + float scan_y = y + 0.5f; + stbtt__active_edge **step = &active; + + // update all active edges; + // remove all active edges that terminate before the center of this scanline + while (*step) { + stbtt__active_edge * z = *step; + if (z->ey <= scan_y) { + *step = z->next; // delete from list + STBTT_assert(z->direction); + z->direction = 0; + stbtt__hheap_free(&hh, z); + } else { + z->x += z->dx; // advance to position for current scanline + step = &((*step)->next); // advance through list + } + } + + // resort the list if needed + for(;;) { + int changed=0; + step = &active; + while (*step && (*step)->next) { + if ((*step)->x > (*step)->next->x) { + stbtt__active_edge *t = *step; + stbtt__active_edge *q = t->next; + + t->next = q->next; + q->next = t; + *step = q; + changed = 1; + } + step = &(*step)->next; + } + if (!changed) break; + } + + // insert all edges that start before the center of this scanline -- omit ones that also end on this scanline + while (e->y0 <= scan_y) { + if (e->y1 > scan_y) { + stbtt__active_edge *z = stbtt__new_active(&hh, e, off_x, scan_y, userdata); + if (z != NULL) { + // find insertion point + if (active == NULL) + active = z; + else if (z->x < active->x) { + // insert at front + z->next = active; + active = z; + } else { + // find thing to insert AFTER + stbtt__active_edge *p = active; + while (p->next && p->next->x < z->x) + p = p->next; + // at this point, p->next->x is NOT < z->x + z->next = p->next; + p->next = z; + } + } + } + ++e; + } + + // now process all active edges in XOR fashion + if (active) + stbtt__fill_active_edges(scanline, result->w, active, max_weight); + + ++y; + } + STBTT_memcpy(result->pixels + j * result->stride, scanline, result->w); + ++j; + } + + stbtt__hheap_cleanup(&hh, userdata); + + if (scanline != scanline_data) + STBTT_free(scanline, userdata); +} + +#elif STBTT_RASTERIZER_VERSION == 2 + +// the edge passed in here does not cross the vertical line at x or the vertical line at x+1 +// (i.e. it has already been clipped to those) +static void stbtt__handle_clipped_edge(float *scanline, int x, stbtt__active_edge *e, float x0, float y0, float x1, float y1) +{ + if (y0 == y1) return; + STBTT_assert(y0 < y1); + STBTT_assert(e->sy <= e->ey); + if (y0 > e->ey) return; + if (y1 < e->sy) return; + if (y0 < e->sy) { + x0 += (x1-x0) * (e->sy - y0) / (y1-y0); + y0 = e->sy; + } + if (y1 > e->ey) { + x1 += (x1-x0) * (e->ey - y1) / (y1-y0); + y1 = e->ey; + } + + if (x0 == x) + STBTT_assert(x1 <= x+1); + else if (x0 == x+1) + STBTT_assert(x1 >= x); + else if (x0 <= x) + STBTT_assert(x1 <= x); + else if (x0 >= x+1) + STBTT_assert(x1 >= x+1); + else + STBTT_assert(x1 >= x && x1 <= x+1); + + if (x0 <= x && x1 <= x) + scanline[x] += e->direction * (y1-y0); + else if (x0 >= x+1 && x1 >= x+1) + ; + else { + STBTT_assert(x0 >= x && x0 <= x+1 && x1 >= x && x1 <= x+1); + scanline[x] += e->direction * (y1-y0) * (1-((x0-x)+(x1-x))/2); // coverage = 1 - average x position + } +} + +static void stbtt__fill_active_edges_new(float *scanline, float *scanline_fill, int len, stbtt__active_edge *e, float y_top) +{ + float y_bottom = y_top+1; + + while (e) { + // brute force every pixel + + // compute intersection points with top & bottom + STBTT_assert(e->ey >= y_top); + + if (e->fdx == 0) { + float x0 = e->fx; + if (x0 < len) { + if (x0 >= 0) { + stbtt__handle_clipped_edge(scanline,(int) x0,e, x0,y_top, x0,y_bottom); + stbtt__handle_clipped_edge(scanline_fill-1,(int) x0+1,e, x0,y_top, x0,y_bottom); + } else { + stbtt__handle_clipped_edge(scanline_fill-1,0,e, x0,y_top, x0,y_bottom); + } + } + } else { + float x0 = e->fx; + float dx = e->fdx; + float xb = x0 + dx; + float x_top, x_bottom; + float sy0,sy1; + float dy = e->fdy; + STBTT_assert(e->sy <= y_bottom && e->ey >= y_top); + + // compute endpoints of line segment clipped to this scanline (if the + // line segment starts on this scanline. x0 is the intersection of the + // line with y_top, but that may be off the line segment. + if (e->sy > y_top) { + x_top = x0 + dx * (e->sy - y_top); + sy0 = e->sy; + } else { + x_top = x0; + sy0 = y_top; + } + if (e->ey < y_bottom) { + x_bottom = x0 + dx * (e->ey - y_top); + sy1 = e->ey; + } else { + x_bottom = xb; + sy1 = y_bottom; + } + + if (x_top >= 0 && x_bottom >= 0 && x_top < len && x_bottom < len) { + // from here on, we don't have to range check x values + + if ((int) x_top == (int) x_bottom) { + float height; + // simple case, only spans one pixel + int x = (int) x_top; + height = sy1 - sy0; + STBTT_assert(x >= 0 && x < len); + scanline[x] += e->direction * (1-((x_top - x) + (x_bottom-x))/2) * height; + scanline_fill[x] += e->direction * height; // everything right of this pixel is filled + } else { + int x,x1,x2; + float y_crossing, step, sign, area; + // covers 2+ pixels + if (x_top > x_bottom) { + // flip scanline vertically; signed area is the same + float t; + sy0 = y_bottom - (sy0 - y_top); + sy1 = y_bottom - (sy1 - y_top); + t = sy0, sy0 = sy1, sy1 = t; + t = x_bottom, x_bottom = x_top, x_top = t; + dx = -dx; + dy = -dy; + t = x0, x0 = xb, xb = t; + } + + x1 = (int) x_top; + x2 = (int) x_bottom; + // compute intersection with y axis at x1+1 + y_crossing = (x1+1 - x0) * dy + y_top; + + sign = e->direction; + // area of the rectangle covered from y0..y_crossing + area = sign * (y_crossing-sy0); + // area of the triangle (x_top,y0), (x+1,y0), (x+1,y_crossing) + scanline[x1] += area * (1-((x_top - x1)+(x1+1-x1))/2); + + step = sign * dy; + for (x = x1+1; x < x2; ++x) { + scanline[x] += area + step/2; + area += step; + } + y_crossing += dy * (x2 - (x1+1)); + + STBTT_assert(STBTT_fabs(area) <= 1.01f); + + scanline[x2] += area + sign * (1-((x2-x2)+(x_bottom-x2))/2) * (sy1-y_crossing); + + scanline_fill[x2] += sign * (sy1-sy0); + } + } else { + // if edge goes outside of box we're drawing, we require + // clipping logic. since this does not match the intended use + // of this library, we use a different, very slow brute + // force implementation + int x; + for (x=0; x < len; ++x) { + // cases: + // + // there can be up to two intersections with the pixel. any intersection + // with left or right edges can be handled by splitting into two (or three) + // regions. intersections with top & bottom do not necessitate case-wise logic. + // + // the old way of doing this found the intersections with the left & right edges, + // then used some simple logic to produce up to three segments in sorted order + // from top-to-bottom. however, this had a problem: if an x edge was epsilon + // across the x border, then the corresponding y position might not be distinct + // from the other y segment, and it might ignored as an empty segment. to avoid + // that, we need to explicitly produce segments based on x positions. + + // rename variables to clearly-defined pairs + float y0 = y_top; + float x1 = (float) (x); + float x2 = (float) (x+1); + float x3 = xb; + float y3 = y_bottom; + + // x = e->x + e->dx * (y-y_top) + // (y-y_top) = (x - e->x) / e->dx + // y = (x - e->x) / e->dx + y_top + float y1 = (x - x0) / dx + y_top; + float y2 = (x+1 - x0) / dx + y_top; + + if (x0 < x1 && x3 > x2) { // three segments descending down-right + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); + } else if (x3 < x1 && x0 > x2) { // three segments descending down-left + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); + } else if (x0 < x1 && x3 > x1) { // two segments across x, down-right + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); + } else if (x3 < x1 && x0 > x1) { // two segments across x, down-left + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); + } else if (x0 < x2 && x3 > x2) { // two segments across x+1, down-right + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); + } else if (x3 < x2 && x0 > x2) { // two segments across x+1, down-left + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); + } else { // one segment + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x3,y3); + } + } + } + } + e = e->next; + } +} + +// directly AA rasterize edges w/o supersampling +static void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e, int n, int vsubsample, int off_x, int off_y, void *userdata) +{ + stbtt__hheap hh = { 0, 0, 0 }; + stbtt__active_edge *active = NULL; + int y,j=0, i; + float scanline_data[129], *scanline, *scanline2; + + STBTT__NOTUSED(vsubsample); + + if (result->w > 64) + scanline = (float *) STBTT_malloc((result->w*2+1) * sizeof(float), userdata); + else + scanline = scanline_data; + + scanline2 = scanline + result->w; + + y = off_y; + e[n].y0 = (float) (off_y + result->h) + 1; + + while (j < result->h) { + // find center of pixel for this scanline + float scan_y_top = y + 0.0f; + float scan_y_bottom = y + 1.0f; + stbtt__active_edge **step = &active; + + STBTT_memset(scanline , 0, result->w*sizeof(scanline[0])); + STBTT_memset(scanline2, 0, (result->w+1)*sizeof(scanline[0])); + + // update all active edges; + // remove all active edges that terminate before the top of this scanline + while (*step) { + stbtt__active_edge * z = *step; + if (z->ey <= scan_y_top) { + *step = z->next; // delete from list + STBTT_assert(z->direction); + z->direction = 0; + stbtt__hheap_free(&hh, z); + } else { + step = &((*step)->next); // advance through list + } + } + + // insert all edges that start before the bottom of this scanline + while (e->y0 <= scan_y_bottom) { + if (e->y0 != e->y1) { + stbtt__active_edge *z = stbtt__new_active(&hh, e, off_x, scan_y_top, userdata); + if (z != NULL) { + STBTT_assert(z->ey >= scan_y_top); + // insert at front + z->next = active; + active = z; + } + } + ++e; + } + + // now process all active edges + if (active) + stbtt__fill_active_edges_new(scanline, scanline2+1, result->w, active, scan_y_top); + + { + float sum = 0; + for (i=0; i < result->w; ++i) { + float k; + int m; + sum += scanline2[i]; + k = scanline[i] + sum; + k = (float) STBTT_fabs(k)*255 + 0.5f; + m = (int) k; + if (m > 255) m = 255; + result->pixels[j*result->stride + i] = (unsigned char) m; + } + } + // advance all the edges + step = &active; + while (*step) { + stbtt__active_edge *z = *step; + z->fx += z->fdx; // advance to position for current scanline + step = &((*step)->next); // advance through list + } + + ++y; + ++j; + } + + stbtt__hheap_cleanup(&hh, userdata); + + if (scanline != scanline_data) + STBTT_free(scanline, userdata); +} +#else +#error "Unrecognized value of STBTT_RASTERIZER_VERSION" +#endif + +#define STBTT__COMPARE(a,b) ((a)->y0 < (b)->y0) + +static void stbtt__sort_edges_ins_sort(stbtt__edge *p, int n) +{ + int i,j; + for (i=1; i < n; ++i) { + stbtt__edge t = p[i], *a = &t; + j = i; + while (j > 0) { + stbtt__edge *b = &p[j-1]; + int c = STBTT__COMPARE(a,b); + if (!c) break; + p[j] = p[j-1]; + --j; + } + if (i != j) + p[j] = t; + } +} + +static void stbtt__sort_edges_quicksort(stbtt__edge *p, int n) +{ + /* threshhold for transitioning to insertion sort */ + while (n > 12) { + stbtt__edge t; + int c01,c12,c,m,i,j; + + /* compute median of three */ + m = n >> 1; + c01 = STBTT__COMPARE(&p[0],&p[m]); + c12 = STBTT__COMPARE(&p[m],&p[n-1]); + /* if 0 >= mid >= end, or 0 < mid < end, then use mid */ + if (c01 != c12) { + /* otherwise, we'll need to swap something else to middle */ + int z; + c = STBTT__COMPARE(&p[0],&p[n-1]); + /* 0>mid && midn => n; 0 0 */ + /* 0n: 0>n => 0; 0 n */ + z = (c == c12) ? 0 : n-1; + t = p[z]; + p[z] = p[m]; + p[m] = t; + } + /* now p[m] is the median-of-three */ + /* swap it to the beginning so it won't move around */ + t = p[0]; + p[0] = p[m]; + p[m] = t; + + /* partition loop */ + i=1; + j=n-1; + for(;;) { + /* handling of equality is crucial here */ + /* for sentinels & efficiency with duplicates */ + for (;;++i) { + if (!STBTT__COMPARE(&p[i], &p[0])) break; + } + for (;;--j) { + if (!STBTT__COMPARE(&p[0], &p[j])) break; + } + /* make sure we haven't crossed */ + if (i >= j) break; + t = p[i]; + p[i] = p[j]; + p[j] = t; + + ++i; + --j; + } + /* recurse on smaller side, iterate on larger */ + if (j < (n-i)) { + stbtt__sort_edges_quicksort(p,j); + p = p+i; + n = n-i; + } else { + stbtt__sort_edges_quicksort(p+i, n-i); + n = j; + } + } +} + +static void stbtt__sort_edges(stbtt__edge *p, int n) +{ + stbtt__sort_edges_quicksort(p, n); + stbtt__sort_edges_ins_sort(p, n); +} + +typedef struct +{ + float x,y; +} stbtt__point; + +static void stbtt__rasterize(stbtt__bitmap *result, stbtt__point *pts, int *wcount, int windings, float scale_x, float scale_y, float shift_x, float shift_y, int off_x, int off_y, int invert, void *userdata) +{ + float y_scale_inv = invert ? -scale_y : scale_y; + stbtt__edge *e; + int n,i,j,k,m; +#if STBTT_RASTERIZER_VERSION == 1 + int vsubsample = result->h < 8 ? 15 : 5; +#elif STBTT_RASTERIZER_VERSION == 2 + int vsubsample = 1; +#else + #error "Unrecognized value of STBTT_RASTERIZER_VERSION" +#endif + // vsubsample should divide 255 evenly; otherwise we won't reach full opacity + + // now we have to blow out the windings into explicit edge lists + n = 0; + for (i=0; i < windings; ++i) + n += wcount[i]; + + e = (stbtt__edge *) STBTT_malloc(sizeof(*e) * (n+1), userdata); // add an extra one as a sentinel + if (e == 0) return; + n = 0; + + m=0; + for (i=0; i < windings; ++i) { + stbtt__point *p = pts + m; + m += wcount[i]; + j = wcount[i]-1; + for (k=0; k < wcount[i]; j=k++) { + int a=k,b=j; + // skip the edge if horizontal + if (p[j].y == p[k].y) + continue; + // add edge from j to k to the list + e[n].invert = 0; + if (invert ? p[j].y > p[k].y : p[j].y < p[k].y) { + e[n].invert = 1; + a=j,b=k; + } + e[n].x0 = p[a].x * scale_x + shift_x; + e[n].y0 = (p[a].y * y_scale_inv + shift_y) * vsubsample; + e[n].x1 = p[b].x * scale_x + shift_x; + e[n].y1 = (p[b].y * y_scale_inv + shift_y) * vsubsample; + ++n; + } + } + + // now sort the edges by their highest point (should snap to integer, and then by x) + //STBTT_sort(e, n, sizeof(e[0]), stbtt__edge_compare); + stbtt__sort_edges(e, n); + + // now, traverse the scanlines and find the intersections on each scanline, use xor winding rule + stbtt__rasterize_sorted_edges(result, e, n, vsubsample, off_x, off_y, userdata); + + STBTT_free(e, userdata); +} + +static void stbtt__add_point(stbtt__point *points, int n, float x, float y) +{ + if (!points) return; // during first pass, it's unallocated + points[n].x = x; + points[n].y = y; +} + +// tesselate until threshhold p is happy... @TODO warped to compensate for non-linear stretching +static int stbtt__tesselate_curve(stbtt__point *points, int *num_points, float x0, float y0, float x1, float y1, float x2, float y2, float objspace_flatness_squared, int n) +{ + // midpoint + float mx = (x0 + 2*x1 + x2)/4; + float my = (y0 + 2*y1 + y2)/4; + // versus directly drawn line + float dx = (x0+x2)/2 - mx; + float dy = (y0+y2)/2 - my; + if (n > 16) // 65536 segments on one curve better be enough! + return 1; + if (dx*dx+dy*dy > objspace_flatness_squared) { // half-pixel error allowed... need to be smaller if AA + stbtt__tesselate_curve(points, num_points, x0,y0, (x0+x1)/2.0f,(y0+y1)/2.0f, mx,my, objspace_flatness_squared,n+1); + stbtt__tesselate_curve(points, num_points, mx,my, (x1+x2)/2.0f,(y1+y2)/2.0f, x2,y2, objspace_flatness_squared,n+1); + } else { + stbtt__add_point(points, *num_points,x2,y2); + *num_points = *num_points+1; + } + return 1; +} + +static void stbtt__tesselate_cubic(stbtt__point *points, int *num_points, float x0, float y0, float x1, float y1, float x2, float y2, float x3, float y3, float objspace_flatness_squared, int n) +{ + // @TODO this "flatness" calculation is just made-up nonsense that seems to work well enough + float dx0 = x1-x0; + float dy0 = y1-y0; + float dx1 = x2-x1; + float dy1 = y2-y1; + float dx2 = x3-x2; + float dy2 = y3-y2; + float dx = x3-x0; + float dy = y3-y0; + float longlen = (float) (STBTT_sqrt(dx0*dx0+dy0*dy0)+STBTT_sqrt(dx1*dx1+dy1*dy1)+STBTT_sqrt(dx2*dx2+dy2*dy2)); + float shortlen = (float) STBTT_sqrt(dx*dx+dy*dy); + float flatness_squared = longlen*longlen-shortlen*shortlen; + + if (n > 16) // 65536 segments on one curve better be enough! + return; + + if (flatness_squared > objspace_flatness_squared) { + float x01 = (x0+x1)/2; + float y01 = (y0+y1)/2; + float x12 = (x1+x2)/2; + float y12 = (y1+y2)/2; + float x23 = (x2+x3)/2; + float y23 = (y2+y3)/2; + + float xa = (x01+x12)/2; + float ya = (y01+y12)/2; + float xb = (x12+x23)/2; + float yb = (y12+y23)/2; + + float mx = (xa+xb)/2; + float my = (ya+yb)/2; + + stbtt__tesselate_cubic(points, num_points, x0,y0, x01,y01, xa,ya, mx,my, objspace_flatness_squared,n+1); + stbtt__tesselate_cubic(points, num_points, mx,my, xb,yb, x23,y23, x3,y3, objspace_flatness_squared,n+1); + } else { + stbtt__add_point(points, *num_points,x3,y3); + *num_points = *num_points+1; + } +} + +// returns number of contours +static stbtt__point *stbtt_FlattenCurves(stbtt_vertex *vertices, int num_verts, float objspace_flatness, int **contour_lengths, int *num_contours, void *userdata) +{ + stbtt__point *points=0; + int num_points=0; + + float objspace_flatness_squared = objspace_flatness * objspace_flatness; + int i,n=0,start=0, pass; + + // count how many "moves" there are to get the contour count + for (i=0; i < num_verts; ++i) + if (vertices[i].type == STBTT_vmove) + ++n; + + *num_contours = n; + if (n == 0) return 0; + + *contour_lengths = (int *) STBTT_malloc(sizeof(**contour_lengths) * n, userdata); + + if (*contour_lengths == 0) { + *num_contours = 0; + return 0; + } + + // make two passes through the points so we don't need to realloc + for (pass=0; pass < 2; ++pass) { + float x=0,y=0; + if (pass == 1) { + points = (stbtt__point *) STBTT_malloc(num_points * sizeof(points[0]), userdata); + if (points == NULL) goto error; + } + num_points = 0; + n= -1; + for (i=0; i < num_verts; ++i) { + switch (vertices[i].type) { + case STBTT_vmove: + // start the next contour + if (n >= 0) + (*contour_lengths)[n] = num_points - start; + ++n; + start = num_points; + + x = vertices[i].x, y = vertices[i].y; + stbtt__add_point(points, num_points++, x,y); + break; + case STBTT_vline: + x = vertices[i].x, y = vertices[i].y; + stbtt__add_point(points, num_points++, x, y); + break; + case STBTT_vcurve: + stbtt__tesselate_curve(points, &num_points, x,y, + vertices[i].cx, vertices[i].cy, + vertices[i].x, vertices[i].y, + objspace_flatness_squared, 0); + x = vertices[i].x, y = vertices[i].y; + break; + case STBTT_vcubic: + stbtt__tesselate_cubic(points, &num_points, x,y, + vertices[i].cx, vertices[i].cy, + vertices[i].cx1, vertices[i].cy1, + vertices[i].x, vertices[i].y, + objspace_flatness_squared, 0); + x = vertices[i].x, y = vertices[i].y; + break; + } + } + (*contour_lengths)[n] = num_points - start; + } + + return points; +error: + STBTT_free(points, userdata); + STBTT_free(*contour_lengths, userdata); + *contour_lengths = 0; + *num_contours = 0; + return NULL; +} + +STBTT_DEF void stbtt_Rasterize(stbtt__bitmap *result, float flatness_in_pixels, stbtt_vertex *vertices, int num_verts, float scale_x, float scale_y, float shift_x, float shift_y, int x_off, int y_off, int invert, void *userdata) +{ + float scale = scale_x > scale_y ? scale_y : scale_x; + int winding_count, *winding_lengths; + stbtt__point *windings = stbtt_FlattenCurves(vertices, num_verts, flatness_in_pixels / scale, &winding_lengths, &winding_count, userdata); + if (windings) { + stbtt__rasterize(result, windings, winding_lengths, winding_count, scale_x, scale_y, shift_x, shift_y, x_off, y_off, invert, userdata); + STBTT_free(winding_lengths, userdata); + STBTT_free(windings, userdata); + } +} + +STBTT_DEF void stbtt_FreeBitmap(unsigned char *bitmap, void *userdata) +{ + STBTT_free(bitmap, userdata); +} + +STBTT_DEF unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int glyph, int *width, int *height, int *xoff, int *yoff) +{ + int ix0,iy0,ix1,iy1; + stbtt__bitmap gbm; + stbtt_vertex *vertices; + int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices); + + if (scale_x == 0) scale_x = scale_y; + if (scale_y == 0) { + if (scale_x == 0) { + STBTT_free(vertices, info->userdata); + return NULL; + } + scale_y = scale_x; + } + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0,&iy0,&ix1,&iy1); + + // now we get the size + gbm.w = (ix1 - ix0); + gbm.h = (iy1 - iy0); + gbm.pixels = NULL; // in case we error + + if (width ) *width = gbm.w; + if (height) *height = gbm.h; + if (xoff ) *xoff = ix0; + if (yoff ) *yoff = iy0; + + if (gbm.w && gbm.h) { + gbm.pixels = (unsigned char *) STBTT_malloc(gbm.w * gbm.h, info->userdata); + if (gbm.pixels) { + gbm.stride = gbm.w; + + stbtt_Rasterize(&gbm, 0.35f, vertices, num_verts, scale_x, scale_y, shift_x, shift_y, ix0, iy0, 1, info->userdata); + } + } + STBTT_free(vertices, info->userdata); + return gbm.pixels; +} + +STBTT_DEF unsigned char *stbtt_GetGlyphBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int glyph, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y, 0.0f, 0.0f, glyph, width, height, xoff, yoff); +} + +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int glyph) +{ + int ix0,iy0; + stbtt_vertex *vertices; + int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices); + stbtt__bitmap gbm; + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0,&iy0,0,0); + gbm.pixels = output; + gbm.w = out_w; + gbm.h = out_h; + gbm.stride = out_stride; + + if (gbm.w && gbm.h) + stbtt_Rasterize(&gbm, 0.35f, vertices, num_verts, scale_x, scale_y, shift_x, shift_y, ix0,iy0, 1, info->userdata); + + STBTT_free(vertices, info->userdata); +} + +STBTT_DEF void stbtt_MakeGlyphBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int glyph) +{ + stbtt_MakeGlyphBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, 0.0f,0.0f, glyph); +} + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y,shift_x,shift_y, stbtt_FindGlyphIndex(info,codepoint), width,height,xoff,yoff); +} + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint) +{ + stbtt_MakeGlyphBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, shift_x, shift_y, stbtt_FindGlyphIndex(info,codepoint)); +} + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int codepoint, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetCodepointBitmapSubpixel(info, scale_x, scale_y, 0.0f,0.0f, codepoint, width,height,xoff,yoff); +} + +STBTT_DEF void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint) +{ + stbtt_MakeCodepointBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, 0.0f,0.0f, codepoint); +} + +////////////////////////////////////////////////////////////////////////////// +// +// bitmap baking +// +// This is SUPER-CRAPPY packing to keep source code small + +static int stbtt_BakeFontBitmap_internal(unsigned char *data, int offset, // font location (use offset=0 for plain .ttf) + float pixel_height, // height of font in pixels + unsigned char *pixels, int pw, int ph, // bitmap to be filled in + int first_char, int num_chars, // characters to bake + stbtt_bakedchar *chardata) +{ + float scale; + int x,y,bottom_y, i; + stbtt_fontinfo f; + f.userdata = NULL; + if (!stbtt_InitFont(&f, data, offset)) + return -1; + STBTT_memset(pixels, 0, pw*ph); // background of 0 around pixels + x=y=1; + bottom_y = 1; + + scale = stbtt_ScaleForPixelHeight(&f, pixel_height); + + for (i=0; i < num_chars; ++i) { + int advance, lsb, x0,y0,x1,y1,gw,gh; + int g = stbtt_FindGlyphIndex(&f, first_char + i); + stbtt_GetGlyphHMetrics(&f, g, &advance, &lsb); + stbtt_GetGlyphBitmapBox(&f, g, scale,scale, &x0,&y0,&x1,&y1); + gw = x1-x0; + gh = y1-y0; + if (x + gw + 1 >= pw) + y = bottom_y, x = 1; // advance to next row + if (y + gh + 1 >= ph) // check if it fits vertically AFTER potentially moving to next row + return -i; + STBTT_assert(x+gw < pw); + STBTT_assert(y+gh < ph); + stbtt_MakeGlyphBitmap(&f, pixels+x+y*pw, gw,gh,pw, scale,scale, g); + chardata[i].x0 = (stbtt_int16) x; + chardata[i].y0 = (stbtt_int16) y; + chardata[i].x1 = (stbtt_int16) (x + gw); + chardata[i].y1 = (stbtt_int16) (y + gh); + chardata[i].xadvance = scale * advance; + chardata[i].xoff = (float) x0; + chardata[i].yoff = (float) y0; + x = x + gw + 1; + if (y+gh+1 > bottom_y) + bottom_y = y+gh+1; + } + return bottom_y; +} + +STBTT_DEF void stbtt_GetBakedQuad(const stbtt_bakedchar *chardata, int pw, int ph, int char_index, float *xpos, float *ypos, stbtt_aligned_quad *q, int opengl_fillrule) +{ + float d3d_bias = opengl_fillrule ? 0 : -0.5f; + float ipw = 1.0f / pw, iph = 1.0f / ph; + const stbtt_bakedchar *b = chardata + char_index; + int round_x = STBTT_ifloor((*xpos + b->xoff) + 0.5f); + int round_y = STBTT_ifloor((*ypos + b->yoff) + 0.5f); + + q->x0 = round_x + d3d_bias; + q->y0 = round_y + d3d_bias; + q->x1 = round_x + b->x1 - b->x0 + d3d_bias; + q->y1 = round_y + b->y1 - b->y0 + d3d_bias; + + q->s0 = b->x0 * ipw; + q->t0 = b->y0 * iph; + q->s1 = b->x1 * ipw; + q->t1 = b->y1 * iph; + + *xpos += b->xadvance; +} + +////////////////////////////////////////////////////////////////////////////// +// +// rectangle packing replacement routines if you don't have stb_rect_pack.h +// + +#ifndef STB_RECT_PACK_VERSION + +typedef int stbrp_coord; + +//////////////////////////////////////////////////////////////////////////////////// +// // +// // +// COMPILER WARNING ?!?!? // +// // +// // +// if you get a compile warning due to these symbols being defined more than // +// once, move #include "stb_rect_pack.h" before #include "stb_truetype.h" // +// // +//////////////////////////////////////////////////////////////////////////////////// + +typedef struct +{ + int width,height; + int x,y,bottom_y; +} stbrp_context; + +typedef struct +{ + unsigned char x; +} stbrp_node; + +struct stbrp_rect +{ + stbrp_coord x,y; + int id,w,h,was_packed; +}; + +static void stbrp_init_target(stbrp_context *con, int pw, int ph, stbrp_node *nodes, int num_nodes) +{ + con->width = pw; + con->height = ph; + con->x = 0; + con->y = 0; + con->bottom_y = 0; + STBTT__NOTUSED(nodes); + STBTT__NOTUSED(num_nodes); +} + +static void stbrp_pack_rects(stbrp_context *con, stbrp_rect *rects, int num_rects) +{ + int i; + for (i=0; i < num_rects; ++i) { + if (con->x + rects[i].w > con->width) { + con->x = 0; + con->y = con->bottom_y; + } + if (con->y + rects[i].h > con->height) + break; + rects[i].x = con->x; + rects[i].y = con->y; + rects[i].was_packed = 1; + con->x += rects[i].w; + if (con->y + rects[i].h > con->bottom_y) + con->bottom_y = con->y + rects[i].h; + } + for ( ; i < num_rects; ++i) + rects[i].was_packed = 0; +} +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// bitmap baking +// +// This is SUPER-AWESOME (tm Ryan Gordon) packing using stb_rect_pack.h. If +// stb_rect_pack.h isn't available, it uses the BakeFontBitmap strategy. + +STBTT_DEF int stbtt_PackBegin(stbtt_pack_context *spc, unsigned char *pixels, int pw, int ph, int stride_in_bytes, int padding, void *alloc_context) +{ + stbrp_context *context = (stbrp_context *) STBTT_malloc(sizeof(*context) ,alloc_context); + int num_nodes = pw - padding; + stbrp_node *nodes = (stbrp_node *) STBTT_malloc(sizeof(*nodes ) * num_nodes,alloc_context); + + if (context == NULL || nodes == NULL) { + if (context != NULL) STBTT_free(context, alloc_context); + if (nodes != NULL) STBTT_free(nodes , alloc_context); + return 0; + } + + spc->user_allocator_context = alloc_context; + spc->width = pw; + spc->height = ph; + spc->pixels = pixels; + spc->pack_info = context; + spc->nodes = nodes; + spc->padding = padding; + spc->stride_in_bytes = stride_in_bytes != 0 ? stride_in_bytes : pw; + spc->h_oversample = 1; + spc->v_oversample = 1; + + stbrp_init_target(context, pw-padding, ph-padding, nodes, num_nodes); + + if (pixels) + STBTT_memset(pixels, 0, pw*ph); // background of 0 around pixels + + return 1; +} + +STBTT_DEF void stbtt_PackEnd (stbtt_pack_context *spc) +{ + STBTT_free(spc->nodes , spc->user_allocator_context); + STBTT_free(spc->pack_info, spc->user_allocator_context); +} + +STBTT_DEF void stbtt_PackSetOversampling(stbtt_pack_context *spc, unsigned int h_oversample, unsigned int v_oversample) +{ + STBTT_assert(h_oversample <= STBTT_MAX_OVERSAMPLE); + STBTT_assert(v_oversample <= STBTT_MAX_OVERSAMPLE); + if (h_oversample <= STBTT_MAX_OVERSAMPLE) + spc->h_oversample = h_oversample; + if (v_oversample <= STBTT_MAX_OVERSAMPLE) + spc->v_oversample = v_oversample; +} + +#define STBTT__OVER_MASK (STBTT_MAX_OVERSAMPLE-1) + +static void stbtt__h_prefilter(unsigned char *pixels, int w, int h, int stride_in_bytes, unsigned int kernel_width) +{ + unsigned char buffer[STBTT_MAX_OVERSAMPLE]; + int safe_w = w - kernel_width; + int j; + STBTT_memset(buffer, 0, STBTT_MAX_OVERSAMPLE); // suppress bogus warning from VS2013 -analyze + for (j=0; j < h; ++j) { + int i; + unsigned int total; + STBTT_memset(buffer, 0, kernel_width); + + total = 0; + + // make kernel_width a constant in common cases so compiler can optimize out the divide + switch (kernel_width) { + case 2: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 2); + } + break; + case 3: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 3); + } + break; + case 4: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 4); + } + break; + case 5: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 5); + } + break; + default: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / kernel_width); + } + break; + } + + for (; i < w; ++i) { + STBTT_assert(pixels[i] == 0); + total -= buffer[i & STBTT__OVER_MASK]; + pixels[i] = (unsigned char) (total / kernel_width); + } + + pixels += stride_in_bytes; + } +} + +static void stbtt__v_prefilter(unsigned char *pixels, int w, int h, int stride_in_bytes, unsigned int kernel_width) +{ + unsigned char buffer[STBTT_MAX_OVERSAMPLE]; + int safe_h = h - kernel_width; + int j; + STBTT_memset(buffer, 0, STBTT_MAX_OVERSAMPLE); // suppress bogus warning from VS2013 -analyze + for (j=0; j < w; ++j) { + int i; + unsigned int total; + STBTT_memset(buffer, 0, kernel_width); + + total = 0; + + // make kernel_width a constant in common cases so compiler can optimize out the divide + switch (kernel_width) { + case 2: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 2); + } + break; + case 3: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 3); + } + break; + case 4: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 4); + } + break; + case 5: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 5); + } + break; + default: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / kernel_width); + } + break; + } + + for (; i < h; ++i) { + STBTT_assert(pixels[i*stride_in_bytes] == 0); + total -= buffer[i & STBTT__OVER_MASK]; + pixels[i*stride_in_bytes] = (unsigned char) (total / kernel_width); + } + + pixels += 1; + } +} + +static float stbtt__oversample_shift(int oversample) +{ + if (!oversample) + return 0.0f; + + // The prefilter is a box filter of width "oversample", + // which shifts phase by (oversample - 1)/2 pixels in + // oversampled space. We want to shift in the opposite + // direction to counter this. + return (float)-(oversample - 1) / (2.0f * (float)oversample); +} + +// rects array must be big enough to accommodate all characters in the given ranges +STBTT_DEF int stbtt_PackFontRangesGatherRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects) +{ + int i,j,k; + + k=0; + for (i=0; i < num_ranges; ++i) { + float fh = ranges[i].font_size; + float scale = fh > 0 ? stbtt_ScaleForPixelHeight(info, fh) : stbtt_ScaleForMappingEmToPixels(info, -fh); + ranges[i].h_oversample = (unsigned char) spc->h_oversample; + ranges[i].v_oversample = (unsigned char) spc->v_oversample; + for (j=0; j < ranges[i].num_chars; ++j) { + int x0,y0,x1,y1; + int codepoint = ranges[i].array_of_unicode_codepoints == NULL ? ranges[i].first_unicode_codepoint_in_range + j : ranges[i].array_of_unicode_codepoints[j]; + int glyph = stbtt_FindGlyphIndex(info, codepoint); + stbtt_GetGlyphBitmapBoxSubpixel(info,glyph, + scale * spc->h_oversample, + scale * spc->v_oversample, + 0,0, + &x0,&y0,&x1,&y1); + rects[k].w = (stbrp_coord) (x1-x0 + spc->padding + spc->h_oversample-1); + rects[k].h = (stbrp_coord) (y1-y0 + spc->padding + spc->v_oversample-1); + ++k; + } + } + + return k; +} + +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int prefilter_x, int prefilter_y, float *sub_x, float *sub_y, int glyph) +{ + stbtt_MakeGlyphBitmapSubpixel(info, + output, + out_w - (prefilter_x - 1), + out_h - (prefilter_y - 1), + out_stride, + scale_x, + scale_y, + shift_x, + shift_y, + glyph); + + if (prefilter_x > 1) + stbtt__h_prefilter(output, out_w, out_h, out_stride, prefilter_x); + + if (prefilter_y > 1) + stbtt__v_prefilter(output, out_w, out_h, out_stride, prefilter_y); + + *sub_x = stbtt__oversample_shift(prefilter_x); + *sub_y = stbtt__oversample_shift(prefilter_y); +} + +// rects array must be big enough to accommodate all characters in the given ranges +STBTT_DEF int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects) +{ + int i,j,k, return_value = 1; + + // save current values + int old_h_over = spc->h_oversample; + int old_v_over = spc->v_oversample; + + k = 0; + for (i=0; i < num_ranges; ++i) { + float fh = ranges[i].font_size; + float scale = fh > 0 ? stbtt_ScaleForPixelHeight(info, fh) : stbtt_ScaleForMappingEmToPixels(info, -fh); + float recip_h,recip_v,sub_x,sub_y; + spc->h_oversample = ranges[i].h_oversample; + spc->v_oversample = ranges[i].v_oversample; + recip_h = 1.0f / spc->h_oversample; + recip_v = 1.0f / spc->v_oversample; + sub_x = stbtt__oversample_shift(spc->h_oversample); + sub_y = stbtt__oversample_shift(spc->v_oversample); + for (j=0; j < ranges[i].num_chars; ++j) { + stbrp_rect *r = &rects[k]; + if (r->was_packed) { + stbtt_packedchar *bc = &ranges[i].chardata_for_range[j]; + int advance, lsb, x0,y0,x1,y1; + int codepoint = ranges[i].array_of_unicode_codepoints == NULL ? ranges[i].first_unicode_codepoint_in_range + j : ranges[i].array_of_unicode_codepoints[j]; + int glyph = stbtt_FindGlyphIndex(info, codepoint); + stbrp_coord pad = (stbrp_coord) spc->padding; + + // pad on left and top + r->x += pad; + r->y += pad; + r->w -= pad; + r->h -= pad; + stbtt_GetGlyphHMetrics(info, glyph, &advance, &lsb); + stbtt_GetGlyphBitmapBox(info, glyph, + scale * spc->h_oversample, + scale * spc->v_oversample, + &x0,&y0,&x1,&y1); + stbtt_MakeGlyphBitmapSubpixel(info, + spc->pixels + r->x + r->y*spc->stride_in_bytes, + r->w - spc->h_oversample+1, + r->h - spc->v_oversample+1, + spc->stride_in_bytes, + scale * spc->h_oversample, + scale * spc->v_oversample, + 0,0, + glyph); + + if (spc->h_oversample > 1) + stbtt__h_prefilter(spc->pixels + r->x + r->y*spc->stride_in_bytes, + r->w, r->h, spc->stride_in_bytes, + spc->h_oversample); + + if (spc->v_oversample > 1) + stbtt__v_prefilter(spc->pixels + r->x + r->y*spc->stride_in_bytes, + r->w, r->h, spc->stride_in_bytes, + spc->v_oversample); + + bc->x0 = (stbtt_int16) r->x; + bc->y0 = (stbtt_int16) r->y; + bc->x1 = (stbtt_int16) (r->x + r->w); + bc->y1 = (stbtt_int16) (r->y + r->h); + bc->xadvance = scale * advance; + bc->xoff = (float) x0 * recip_h + sub_x; + bc->yoff = (float) y0 * recip_v + sub_y; + bc->xoff2 = (x0 + r->w) * recip_h + sub_x; + bc->yoff2 = (y0 + r->h) * recip_v + sub_y; + } else { + return_value = 0; // if any fail, report failure + } + + ++k; + } + } + + // restore original values + spc->h_oversample = old_h_over; + spc->v_oversample = old_v_over; + + return return_value; +} + +STBTT_DEF void stbtt_PackFontRangesPackRects(stbtt_pack_context *spc, stbrp_rect *rects, int num_rects) +{ + stbrp_pack_rects((stbrp_context *) spc->pack_info, rects, num_rects); +} + +STBTT_DEF int stbtt_PackFontRanges(stbtt_pack_context *spc, unsigned char *fontdata, int font_index, stbtt_pack_range *ranges, int num_ranges) +{ + stbtt_fontinfo info; + int i,j,n, return_value = 1; + //stbrp_context *context = (stbrp_context *) spc->pack_info; + stbrp_rect *rects; + + // flag all characters as NOT packed + for (i=0; i < num_ranges; ++i) + for (j=0; j < ranges[i].num_chars; ++j) + ranges[i].chardata_for_range[j].x0 = + ranges[i].chardata_for_range[j].y0 = + ranges[i].chardata_for_range[j].x1 = + ranges[i].chardata_for_range[j].y1 = 0; + + n = 0; + for (i=0; i < num_ranges; ++i) + n += ranges[i].num_chars; + + rects = (stbrp_rect *) STBTT_malloc(sizeof(*rects) * n, spc->user_allocator_context); + if (rects == NULL) + return 0; + + info.userdata = spc->user_allocator_context; + stbtt_InitFont(&info, fontdata, stbtt_GetFontOffsetForIndex(fontdata,font_index)); + + n = stbtt_PackFontRangesGatherRects(spc, &info, ranges, num_ranges, rects); + + stbtt_PackFontRangesPackRects(spc, rects, n); + + return_value = stbtt_PackFontRangesRenderIntoRects(spc, &info, ranges, num_ranges, rects); + + STBTT_free(rects, spc->user_allocator_context); + return return_value; +} + +STBTT_DEF int stbtt_PackFontRange(stbtt_pack_context *spc, unsigned char *fontdata, int font_index, float font_size, + int first_unicode_codepoint_in_range, int num_chars_in_range, stbtt_packedchar *chardata_for_range) +{ + stbtt_pack_range range; + range.first_unicode_codepoint_in_range = first_unicode_codepoint_in_range; + range.array_of_unicode_codepoints = NULL; + range.num_chars = num_chars_in_range; + range.chardata_for_range = chardata_for_range; + range.font_size = font_size; + return stbtt_PackFontRanges(spc, fontdata, font_index, &range, 1); +} + +STBTT_DEF void stbtt_GetPackedQuad(const stbtt_packedchar *chardata, int pw, int ph, int char_index, float *xpos, float *ypos, stbtt_aligned_quad *q, int align_to_integer) +{ + float ipw = 1.0f / pw, iph = 1.0f / ph; + const stbtt_packedchar *b = chardata + char_index; + + if (align_to_integer) { + float x = (float) STBTT_ifloor((*xpos + b->xoff) + 0.5f); + float y = (float) STBTT_ifloor((*ypos + b->yoff) + 0.5f); + q->x0 = x; + q->y0 = y; + q->x1 = x + b->xoff2 - b->xoff; + q->y1 = y + b->yoff2 - b->yoff; + } else { + q->x0 = *xpos + b->xoff; + q->y0 = *ypos + b->yoff; + q->x1 = *xpos + b->xoff2; + q->y1 = *ypos + b->yoff2; + } + + q->s0 = b->x0 * ipw; + q->t0 = b->y0 * iph; + q->s1 = b->x1 * ipw; + q->t1 = b->y1 * iph; + + *xpos += b->xadvance; +} + +////////////////////////////////////////////////////////////////////////////// +// +// sdf computation +// + +#define STBTT_min(a,b) ((a) < (b) ? (a) : (b)) +#define STBTT_max(a,b) ((a) < (b) ? (b) : (a)) + +static int stbtt__ray_intersect_bezier(float orig[2], float ray[2], float q0[2], float q1[2], float q2[2], float hits[2][2]) +{ + float q0perp = q0[1]*ray[0] - q0[0]*ray[1]; + float q1perp = q1[1]*ray[0] - q1[0]*ray[1]; + float q2perp = q2[1]*ray[0] - q2[0]*ray[1]; + float roperp = orig[1]*ray[0] - orig[0]*ray[1]; + + float a = q0perp - 2*q1perp + q2perp; + float b = q1perp - q0perp; + float c = q0perp - roperp; + + float s0 = 0., s1 = 0.; + int num_s = 0; + + if (a != 0.0) { + float discr = b*b - a*c; + if (discr > 0.0) { + float rcpna = -1 / a; + float d = (float) sqrt(discr); + s0 = (b+d) * rcpna; + s1 = (b-d) * rcpna; + if (s0 >= 0.0 && s0 <= 1.0) + num_s = 1; + if (d > 0.0 && s1 >= 0.0 && s1 <= 1.0) { + if (num_s == 0) s0 = s1; + ++num_s; + } + } + } else { + // 2*b*s + c = 0 + // s = -c / (2*b) + s0 = c / (-2 * b); + if (s0 >= 0.0 && s0 <= 1.0) + num_s = 1; + } + + if (num_s == 0) + return 0; + else { + float rcp_len2 = 1 / (ray[0]*ray[0] + ray[1]*ray[1]); + float rayn_x = ray[0] * rcp_len2, rayn_y = ray[1] * rcp_len2; + + float q0d = q0[0]*rayn_x + q0[1]*rayn_y; + float q1d = q1[0]*rayn_x + q1[1]*rayn_y; + float q2d = q2[0]*rayn_x + q2[1]*rayn_y; + float rod = orig[0]*rayn_x + orig[1]*rayn_y; + + float q10d = q1d - q0d; + float q20d = q2d - q0d; + float q0rd = q0d - rod; + + hits[0][0] = q0rd + s0*(2.0f - 2.0f*s0)*q10d + s0*s0*q20d; + hits[0][1] = a*s0+b; + + if (num_s > 1) { + hits[1][0] = q0rd + s1*(2.0f - 2.0f*s1)*q10d + s1*s1*q20d; + hits[1][1] = a*s1+b; + return 2; + } else { + return 1; + } + } +} + +static int equal(float *a, float *b) +{ + return (a[0] == b[0] && a[1] == b[1]); +} + +static int stbtt__compute_crossings_x(float x, float y, int nverts, stbtt_vertex *verts) +{ + int i; + float orig[2], ray[2] = { 1, 0 }; + float y_frac; + int winding = 0; + + orig[0] = x; + orig[1] = y; + + // make sure y never passes through a vertex of the shape + y_frac = (float) fmod(y, 1.0f); + if (y_frac < 0.01f) + y += 0.01f; + else if (y_frac > 0.99f) + y -= 0.01f; + orig[1] = y; + + // test a ray from (-infinity,y) to (x,y) + for (i=0; i < nverts; ++i) { + if (verts[i].type == STBTT_vline) { + int x0 = (int) verts[i-1].x, y0 = (int) verts[i-1].y; + int x1 = (int) verts[i ].x, y1 = (int) verts[i ].y; + if (y > STBTT_min(y0,y1) && y < STBTT_max(y0,y1) && x > STBTT_min(x0,x1)) { + float x_inter = (y - y0) / (y1 - y0) * (x1-x0) + x0; + if (x_inter < x) + winding += (y0 < y1) ? 1 : -1; + } + } + if (verts[i].type == STBTT_vcurve) { + int x0 = (int) verts[i-1].x , y0 = (int) verts[i-1].y ; + int x1 = (int) verts[i ].cx, y1 = (int) verts[i ].cy; + int x2 = (int) verts[i ].x , y2 = (int) verts[i ].y ; + int ax = STBTT_min(x0,STBTT_min(x1,x2)), ay = STBTT_min(y0,STBTT_min(y1,y2)); + int by = STBTT_max(y0,STBTT_max(y1,y2)); + if (y > ay && y < by && x > ax) { + float q0[2],q1[2],q2[2]; + float hits[2][2]; + q0[0] = (float)x0; + q0[1] = (float)y0; + q1[0] = (float)x1; + q1[1] = (float)y1; + q2[0] = (float)x2; + q2[1] = (float)y2; + if (equal(q0,q1) || equal(q1,q2)) { + x0 = (int)verts[i-1].x; + y0 = (int)verts[i-1].y; + x1 = (int)verts[i ].x; + y1 = (int)verts[i ].y; + if (y > STBTT_min(y0,y1) && y < STBTT_max(y0,y1) && x > STBTT_min(x0,x1)) { + float x_inter = (y - y0) / (y1 - y0) * (x1-x0) + x0; + if (x_inter < x) + winding += (y0 < y1) ? 1 : -1; + } + } else { + int num_hits = stbtt__ray_intersect_bezier(orig, ray, q0, q1, q2, hits); + if (num_hits >= 1) + if (hits[0][0] < 0) + winding += (hits[0][1] < 0 ? -1 : 1); + if (num_hits >= 2) + if (hits[1][0] < 0) + winding += (hits[1][1] < 0 ? -1 : 1); + } + } + } + } + return winding; +} + +static float stbtt__cuberoot( float x ) +{ + if (x<0) + return -(float) STBTT_pow(-x,1.0f/3.0f); + else + return (float) STBTT_pow( x,1.0f/3.0f); +} + +// x^3 + c*x^2 + b*x + a = 0 +static int stbtt__solve_cubic(float a, float b, float c, float* r) +{ + float s = -a / 3; + float p = b - a*a / 3; + float q = a * (2*a*a - 9*b) / 27 + c; + float p3 = p*p*p; + float d = q*q + 4*p3 / 27; + if (d >= 0) { + float z = (float) STBTT_sqrt(d); + float u = (-q + z) / 2; + float v = (-q - z) / 2; + u = stbtt__cuberoot(u); + v = stbtt__cuberoot(v); + r[0] = s + u + v; + return 1; + } else { + float u = (float) STBTT_sqrt(-p/3); + float v = (float) STBTT_acos(-STBTT_sqrt(-27/p3) * q / 2) / 3; // p3 must be negative, since d is negative + float m = (float) STBTT_cos(v); + float n = (float) STBTT_cos(v-3.141592/2)*1.732050808f; + r[0] = s + u * 2 * m; + r[1] = s - u * (m + n); + r[2] = s - u * (m - n); + + //STBTT_assert( STBTT_fabs(((r[0]+a)*r[0]+b)*r[0]+c) < 0.05f); // these asserts may not be safe at all scales, though they're in bezier t parameter units so maybe? + //STBTT_assert( STBTT_fabs(((r[1]+a)*r[1]+b)*r[1]+c) < 0.05f); + //STBTT_assert( STBTT_fabs(((r[2]+a)*r[2]+b)*r[2]+c) < 0.05f); + return 3; + } +} + +STBTT_DEF unsigned char * stbtt_GetGlyphSDF(const stbtt_fontinfo *info, float scale, int glyph, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff) +{ + float scale_x = scale, scale_y = scale; + int ix0,iy0,ix1,iy1; + int w,h; + unsigned char *data; + + // if one scale is 0, use same scale for both + if (scale_x == 0) scale_x = scale_y; + if (scale_y == 0) { + if (scale_x == 0) return NULL; // if both scales are 0, return NULL + scale_y = scale_x; + } + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale, scale, 0.0f,0.0f, &ix0,&iy0,&ix1,&iy1); + + // if empty, return NULL + if (ix0 == ix1 || iy0 == iy1) + return NULL; + + ix0 -= padding; + iy0 -= padding; + ix1 += padding; + iy1 += padding; + + w = (ix1 - ix0); + h = (iy1 - iy0); + + if (width ) *width = w; + if (height) *height = h; + if (xoff ) *xoff = ix0; + if (yoff ) *yoff = iy0; + + // invert for y-downwards bitmaps + scale_y = -scale_y; + + { + int x,y,i,j; + float *precompute; + stbtt_vertex *verts; + int num_verts = stbtt_GetGlyphShape(info, glyph, &verts); + data = (unsigned char *) STBTT_malloc(w * h, info->userdata); + precompute = (float *) STBTT_malloc(num_verts * sizeof(float), info->userdata); + + for (i=0,j=num_verts-1; i < num_verts; j=i++) { + if (verts[i].type == STBTT_vline) { + float x0 = verts[i].x*scale_x, y0 = verts[i].y*scale_y; + float x1 = verts[j].x*scale_x, y1 = verts[j].y*scale_y; + float dist = (float) STBTT_sqrt((x1-x0)*(x1-x0) + (y1-y0)*(y1-y0)); + precompute[i] = (dist == 0) ? 0.0f : 1.0f / dist; + } else if (verts[i].type == STBTT_vcurve) { + float x2 = verts[j].x *scale_x, y2 = verts[j].y *scale_y; + float x1 = verts[i].cx*scale_x, y1 = verts[i].cy*scale_y; + float x0 = verts[i].x *scale_x, y0 = verts[i].y *scale_y; + float bx = x0 - 2*x1 + x2, by = y0 - 2*y1 + y2; + float len2 = bx*bx + by*by; + if (len2 != 0.0f) + precompute[i] = 1.0f / (bx*bx + by*by); + else + precompute[i] = 0.0f; + } else + precompute[i] = 0.0f; + } + + for (y=iy0; y < iy1; ++y) { + for (x=ix0; x < ix1; ++x) { + float val; + float min_dist = 999999.0f; + float sx = (float) x + 0.5f; + float sy = (float) y + 0.5f; + float x_gspace = (sx / scale_x); + float y_gspace = (sy / scale_y); + + int winding = stbtt__compute_crossings_x(x_gspace, y_gspace, num_verts, verts); // @OPTIMIZE: this could just be a rasterization, but needs to be line vs. non-tesselated curves so a new path + + for (i=0; i < num_verts; ++i) { + float x0 = verts[i].x*scale_x, y0 = verts[i].y*scale_y; + + // check against every point here rather than inside line/curve primitives -- @TODO: wrong if multiple 'moves' in a row produce a garbage point, and given culling, probably more efficient to do within line/curve + float dist2 = (x0-sx)*(x0-sx) + (y0-sy)*(y0-sy); + if (dist2 < min_dist*min_dist) + min_dist = (float) STBTT_sqrt(dist2); + + if (verts[i].type == STBTT_vline) { + float x1 = verts[i-1].x*scale_x, y1 = verts[i-1].y*scale_y; + + // coarse culling against bbox + //if (sx > STBTT_min(x0,x1)-min_dist && sx < STBTT_max(x0,x1)+min_dist && + // sy > STBTT_min(y0,y1)-min_dist && sy < STBTT_max(y0,y1)+min_dist) + float dist = (float) STBTT_fabs((x1-x0)*(y0-sy) - (y1-y0)*(x0-sx)) * precompute[i]; + STBTT_assert(i != 0); + if (dist < min_dist) { + // check position along line + // x' = x0 + t*(x1-x0), y' = y0 + t*(y1-y0) + // minimize (x'-sx)*(x'-sx)+(y'-sy)*(y'-sy) + float dx = x1-x0, dy = y1-y0; + float px = x0-sx, py = y0-sy; + // minimize (px+t*dx)^2 + (py+t*dy)^2 = px*px + 2*px*dx*t + t^2*dx*dx + py*py + 2*py*dy*t + t^2*dy*dy + // derivative: 2*px*dx + 2*py*dy + (2*dx*dx+2*dy*dy)*t, set to 0 and solve + float t = -(px*dx + py*dy) / (dx*dx + dy*dy); + if (t >= 0.0f && t <= 1.0f) + min_dist = dist; + } + } else if (verts[i].type == STBTT_vcurve) { + float x2 = verts[i-1].x *scale_x, y2 = verts[i-1].y *scale_y; + float x1 = verts[i ].cx*scale_x, y1 = verts[i ].cy*scale_y; + float box_x0 = STBTT_min(STBTT_min(x0,x1),x2); + float box_y0 = STBTT_min(STBTT_min(y0,y1),y2); + float box_x1 = STBTT_max(STBTT_max(x0,x1),x2); + float box_y1 = STBTT_max(STBTT_max(y0,y1),y2); + // coarse culling against bbox to avoid computing cubic unnecessarily + if (sx > box_x0-min_dist && sx < box_x1+min_dist && sy > box_y0-min_dist && sy < box_y1+min_dist) { + int num=0; + float ax = x1-x0, ay = y1-y0; + float bx = x0 - 2*x1 + x2, by = y0 - 2*y1 + y2; + float mx = x0 - sx, my = y0 - sy; + float res[3],px,py,t,it; + float a_inv = precompute[i]; + if (a_inv == 0.0) { // if a_inv is 0, it's 2nd degree so use quadratic formula + float a = 3*(ax*bx + ay*by); + float b = 2*(ax*ax + ay*ay) + (mx*bx+my*by); + float c = mx*ax+my*ay; + if (a == 0.0) { // if a is 0, it's linear + if (b != 0.0) { + res[num++] = -c/b; + } + } else { + float discriminant = b*b - 4*a*c; + if (discriminant < 0) + num = 0; + else { + float root = (float) STBTT_sqrt(discriminant); + res[0] = (-b - root)/(2*a); + res[1] = (-b + root)/(2*a); + num = 2; // don't bother distinguishing 1-solution case, as code below will still work + } + } + } else { + float b = 3*(ax*bx + ay*by) * a_inv; // could precompute this as it doesn't depend on sample point + float c = (2*(ax*ax + ay*ay) + (mx*bx+my*by)) * a_inv; + float d = (mx*ax+my*ay) * a_inv; + num = stbtt__solve_cubic(b, c, d, res); + } + if (num >= 1 && res[0] >= 0.0f && res[0] <= 1.0f) { + t = res[0], it = 1.0f - t; + px = it*it*x0 + 2*t*it*x1 + t*t*x2; + py = it*it*y0 + 2*t*it*y1 + t*t*y2; + dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy); + if (dist2 < min_dist * min_dist) + min_dist = (float) STBTT_sqrt(dist2); + } + if (num >= 2 && res[1] >= 0.0f && res[1] <= 1.0f) { + t = res[1], it = 1.0f - t; + px = it*it*x0 + 2*t*it*x1 + t*t*x2; + py = it*it*y0 + 2*t*it*y1 + t*t*y2; + dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy); + if (dist2 < min_dist * min_dist) + min_dist = (float) STBTT_sqrt(dist2); + } + if (num >= 3 && res[2] >= 0.0f && res[2] <= 1.0f) { + t = res[2], it = 1.0f - t; + px = it*it*x0 + 2*t*it*x1 + t*t*x2; + py = it*it*y0 + 2*t*it*y1 + t*t*y2; + dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy); + if (dist2 < min_dist * min_dist) + min_dist = (float) STBTT_sqrt(dist2); + } + } + } + } + if (winding == 0) + min_dist = -min_dist; // if outside the shape, value is negative + val = onedge_value + pixel_dist_scale * min_dist; + if (val < 0) + val = 0; + else if (val > 255) + val = 255; + data[(y-iy0)*w+(x-ix0)] = (unsigned char) val; + } + } + STBTT_free(precompute, info->userdata); + STBTT_free(verts, info->userdata); + } + return data; +} + +STBTT_DEF unsigned char * stbtt_GetCodepointSDF(const stbtt_fontinfo *info, float scale, int codepoint, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetGlyphSDF(info, scale, stbtt_FindGlyphIndex(info, codepoint), padding, onedge_value, pixel_dist_scale, width, height, xoff, yoff); +} + +STBTT_DEF void stbtt_FreeSDF(unsigned char *bitmap, void *userdata) +{ + STBTT_free(bitmap, userdata); +} + +////////////////////////////////////////////////////////////////////////////// +// +// font name matching -- recommended not to use this +// + +// check if a utf8 string contains a prefix which is the utf16 string; if so return length of matching utf8 string +static stbtt_int32 stbtt__CompareUTF8toUTF16_bigendian_prefix(stbtt_uint8 *s1, stbtt_int32 len1, stbtt_uint8 *s2, stbtt_int32 len2) +{ + stbtt_int32 i=0; + + // convert utf16 to utf8 and compare the results while converting + while (len2) { + stbtt_uint16 ch = s2[0]*256 + s2[1]; + if (ch < 0x80) { + if (i >= len1) return -1; + if (s1[i++] != ch) return -1; + } else if (ch < 0x800) { + if (i+1 >= len1) return -1; + if (s1[i++] != 0xc0 + (ch >> 6)) return -1; + if (s1[i++] != 0x80 + (ch & 0x3f)) return -1; + } else if (ch >= 0xd800 && ch < 0xdc00) { + stbtt_uint32 c; + stbtt_uint16 ch2 = s2[2]*256 + s2[3]; + if (i+3 >= len1) return -1; + c = ((ch - 0xd800) << 10) + (ch2 - 0xdc00) + 0x10000; + if (s1[i++] != 0xf0 + (c >> 18)) return -1; + if (s1[i++] != 0x80 + ((c >> 12) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((c >> 6) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((c ) & 0x3f)) return -1; + s2 += 2; // plus another 2 below + len2 -= 2; + } else if (ch >= 0xdc00 && ch < 0xe000) { + return -1; + } else { + if (i+2 >= len1) return -1; + if (s1[i++] != 0xe0 + (ch >> 12)) return -1; + if (s1[i++] != 0x80 + ((ch >> 6) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((ch ) & 0x3f)) return -1; + } + s2 += 2; + len2 -= 2; + } + return i; +} + +static int stbtt_CompareUTF8toUTF16_bigendian_internal(char *s1, int len1, char *s2, int len2) +{ + return len1 == stbtt__CompareUTF8toUTF16_bigendian_prefix((stbtt_uint8*) s1, len1, (stbtt_uint8*) s2, len2); +} + +// returns results in whatever encoding you request... but note that 2-byte encodings +// will be BIG-ENDIAN... use stbtt_CompareUTF8toUTF16_bigendian() to compare +STBTT_DEF const char *stbtt_GetFontNameString(const stbtt_fontinfo *font, int *length, int platformID, int encodingID, int languageID, int nameID) +{ + stbtt_int32 i,count,stringOffset; + stbtt_uint8 *fc = font->data; + stbtt_uint32 offset = font->fontstart; + stbtt_uint32 nm = stbtt__find_table(fc, offset, "name"); + if (!nm) return NULL; + + count = ttUSHORT(fc+nm+2); + stringOffset = nm + ttUSHORT(fc+nm+4); + for (i=0; i < count; ++i) { + stbtt_uint32 loc = nm + 6 + 12 * i; + if (platformID == ttUSHORT(fc+loc+0) && encodingID == ttUSHORT(fc+loc+2) + && languageID == ttUSHORT(fc+loc+4) && nameID == ttUSHORT(fc+loc+6)) { + *length = ttUSHORT(fc+loc+8); + return (const char *) (fc+stringOffset+ttUSHORT(fc+loc+10)); + } + } + return NULL; +} + +static int stbtt__matchpair(stbtt_uint8 *fc, stbtt_uint32 nm, stbtt_uint8 *name, stbtt_int32 nlen, stbtt_int32 target_id, stbtt_int32 next_id) +{ + stbtt_int32 i; + stbtt_int32 count = ttUSHORT(fc+nm+2); + stbtt_int32 stringOffset = nm + ttUSHORT(fc+nm+4); + + for (i=0; i < count; ++i) { + stbtt_uint32 loc = nm + 6 + 12 * i; + stbtt_int32 id = ttUSHORT(fc+loc+6); + if (id == target_id) { + // find the encoding + stbtt_int32 platform = ttUSHORT(fc+loc+0), encoding = ttUSHORT(fc+loc+2), language = ttUSHORT(fc+loc+4); + + // is this a Unicode encoding? + if (platform == 0 || (platform == 3 && encoding == 1) || (platform == 3 && encoding == 10)) { + stbtt_int32 slen = ttUSHORT(fc+loc+8); + stbtt_int32 off = ttUSHORT(fc+loc+10); + + // check if there's a prefix match + stbtt_int32 matchlen = stbtt__CompareUTF8toUTF16_bigendian_prefix(name, nlen, fc+stringOffset+off,slen); + if (matchlen >= 0) { + // check for target_id+1 immediately following, with same encoding & language + if (i+1 < count && ttUSHORT(fc+loc+12+6) == next_id && ttUSHORT(fc+loc+12) == platform && ttUSHORT(fc+loc+12+2) == encoding && ttUSHORT(fc+loc+12+4) == language) { + slen = ttUSHORT(fc+loc+12+8); + off = ttUSHORT(fc+loc+12+10); + if (slen == 0) { + if (matchlen == nlen) + return 1; + } else if (matchlen < nlen && name[matchlen] == ' ') { + ++matchlen; + if (stbtt_CompareUTF8toUTF16_bigendian_internal((char*) (name+matchlen), nlen-matchlen, (char*)(fc+stringOffset+off),slen)) + return 1; + } + } else { + // if nothing immediately following + if (matchlen == nlen) + return 1; + } + } + } + + // @TODO handle other encodings + } + } + return 0; +} + +static int stbtt__matches(stbtt_uint8 *fc, stbtt_uint32 offset, stbtt_uint8 *name, stbtt_int32 flags) +{ + stbtt_int32 nlen = (stbtt_int32) STBTT_strlen((char *) name); + stbtt_uint32 nm,hd; + if (!stbtt__isfont(fc+offset)) return 0; + + // check italics/bold/underline flags in macStyle... + if (flags) { + hd = stbtt__find_table(fc, offset, "head"); + if ((ttUSHORT(fc+hd+44) & 7) != (flags & 7)) return 0; + } + + nm = stbtt__find_table(fc, offset, "name"); + if (!nm) return 0; + + if (flags) { + // if we checked the macStyle flags, then just check the family and ignore the subfamily + if (stbtt__matchpair(fc, nm, name, nlen, 16, -1)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 1, -1)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 3, -1)) return 1; + } else { + if (stbtt__matchpair(fc, nm, name, nlen, 16, 17)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 1, 2)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 3, -1)) return 1; + } + + return 0; +} + +static int stbtt_FindMatchingFont_internal(unsigned char *font_collection, char *name_utf8, stbtt_int32 flags) +{ + stbtt_int32 i; + for (i=0;;++i) { + stbtt_int32 off = stbtt_GetFontOffsetForIndex(font_collection, i); + if (off < 0) return off; + if (stbtt__matches((stbtt_uint8 *) font_collection, off, (stbtt_uint8*) name_utf8, flags)) + return off; + } +} + +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wcast-qual" +#endif + +STBTT_DEF int stbtt_BakeFontBitmap(const unsigned char *data, int offset, + float pixel_height, unsigned char *pixels, int pw, int ph, + int first_char, int num_chars, stbtt_bakedchar *chardata) +{ + return stbtt_BakeFontBitmap_internal((unsigned char *) data, offset, pixel_height, pixels, pw, ph, first_char, num_chars, chardata); +} + +STBTT_DEF int stbtt_GetFontOffsetForIndex(const unsigned char *data, int index) +{ + return stbtt_GetFontOffsetForIndex_internal((unsigned char *) data, index); +} + +STBTT_DEF int stbtt_GetNumberOfFonts(const unsigned char *data) +{ + return stbtt_GetNumberOfFonts_internal((unsigned char *) data); +} + +STBTT_DEF int stbtt_InitFont(stbtt_fontinfo *info, const unsigned char *data, int offset) +{ + return stbtt_InitFont_internal(info, (unsigned char *) data, offset); +} + +STBTT_DEF int stbtt_FindMatchingFont(const unsigned char *fontdata, const char *name, int flags) +{ + return stbtt_FindMatchingFont_internal((unsigned char *) fontdata, (char *) name, flags); +} + +STBTT_DEF int stbtt_CompareUTF8toUTF16_bigendian(const char *s1, int len1, const char *s2, int len2) +{ + return stbtt_CompareUTF8toUTF16_bigendian_internal((char *) s1, len1, (char *) s2, len2); +} + +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic pop +#endif + +#endif // STB_TRUETYPE_IMPLEMENTATION + + +// FULL VERSION HISTORY +// +// 1.16 (2017-07-12) SDF support +// 1.15 (2017-03-03) make more arguments const +// 1.14 (2017-01-16) num-fonts-in-TTC function +// 1.13 (2017-01-02) support OpenType fonts, certain Apple fonts +// 1.12 (2016-10-25) suppress warnings about casting away const with -Wcast-qual +// 1.11 (2016-04-02) fix unused-variable warning +// 1.10 (2016-04-02) allow user-defined fabs() replacement +// fix memory leak if fontsize=0.0 +// fix warning from duplicate typedef +// 1.09 (2016-01-16) warning fix; avoid crash on outofmem; use alloc userdata for PackFontRanges +// 1.08 (2015-09-13) document stbtt_Rasterize(); fixes for vertical & horizontal edges +// 1.07 (2015-08-01) allow PackFontRanges to accept arrays of sparse codepoints; +// allow PackFontRanges to pack and render in separate phases; +// fix stbtt_GetFontOFfsetForIndex (never worked for non-0 input?); +// fixed an assert() bug in the new rasterizer +// replace assert() with STBTT_assert() in new rasterizer +// 1.06 (2015-07-14) performance improvements (~35% faster on x86 and x64 on test machine) +// also more precise AA rasterizer, except if shapes overlap +// remove need for STBTT_sort +// 1.05 (2015-04-15) fix misplaced definitions for STBTT_STATIC +// 1.04 (2015-04-15) typo in example +// 1.03 (2015-04-12) STBTT_STATIC, fix memory leak in new packing, various fixes +// 1.02 (2014-12-10) fix various warnings & compile issues w/ stb_rect_pack, C++ +// 1.01 (2014-12-08) fix subpixel position when oversampling to exactly match +// non-oversampled; STBTT_POINT_SIZE for packed case only +// 1.00 (2014-12-06) add new PackBegin etc. API, w/ support for oversampling +// 0.99 (2014-09-18) fix multiple bugs with subpixel rendering (ryg) +// 0.9 (2014-08-07) support certain mac/iOS fonts without an MS platformID +// 0.8b (2014-07-07) fix a warning +// 0.8 (2014-05-25) fix a few more warnings +// 0.7 (2013-09-25) bugfix: subpixel glyph bug fixed in 0.5 had come back +// 0.6c (2012-07-24) improve documentation +// 0.6b (2012-07-20) fix a few more warnings +// 0.6 (2012-07-17) fix warnings; added stbtt_ScaleForMappingEmToPixels, +// stbtt_GetFontBoundingBox, stbtt_IsGlyphEmpty +// 0.5 (2011-12-09) bugfixes: +// subpixel glyph renderer computed wrong bounding box +// first vertex of shape can be off-curve (FreeSans) +// 0.4b (2011-12-03) fixed an error in the font baking example +// 0.4 (2011-12-01) kerning, subpixel rendering (tor) +// bugfixes for: +// codepoint-to-glyph conversion using table fmt=12 +// codepoint-to-glyph conversion using table fmt=4 +// stbtt_GetBakedQuad with non-square texture (Zer) +// updated Hello World! sample to use kerning and subpixel +// fixed some warnings +// 0.3 (2009-06-24) cmap fmt=12, compound shapes (MM) +// userdata, malloc-from-userdata, non-zero fill (stb) +// 0.2 (2009-03-11) Fix unsigned/signed char warnings +// 0.1 (2009-03-09) First public release +// + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ diff --git a/third-party/sokol/sokol_app.h b/third-party/sokol/sokol_app.h new file mode 100644 index 0000000..2df4f05 --- /dev/null +++ b/third-party/sokol/sokol_app.h @@ -0,0 +1,13242 @@ +#if defined(SOKOL_IMPL) && !defined(SOKOL_APP_IMPL) +#define SOKOL_APP_IMPL +#endif +#ifndef SOKOL_APP_INCLUDED +/* + sokol_app.h -- cross-platform application wrapper + + Project URL: https://github.com/floooh/sokol + + Do this: + #define SOKOL_IMPL or + #define SOKOL_APP_IMPL + before you include this file in *one* C or C++ file to create the + implementation. + + In the same place define one of the following to select the 3D-API + which should be initialized by sokol_app.h (this must also match + the backend selected for sokol_gfx.h if both are used in the same + project): + + #define SOKOL_GLCORE + #define SOKOL_GLES3 + #define SOKOL_D3D11 + #define SOKOL_METAL + #define SOKOL_WGPU + #define SOKOL_NOAPI + + Optionally provide the following defines with your own implementations: + + SOKOL_ASSERT(c) - your own assert macro (default: assert(c)) + SOKOL_UNREACHABLE() - a guard macro for unreachable code (default: assert(false)) + SOKOL_WIN32_FORCE_MAIN - define this on Win32 to add a main() entry point + SOKOL_WIN32_FORCE_WINMAIN - define this on Win32 to add a WinMain() entry point (enabled by default unless + SOKOL_WIN32_FORCE_MAIN or SOKOL_NO_ENTRY is defined) + SOKOL_NO_ENTRY - define this if sokol_app.h shouldn't "hijack" the main() function + SOKOL_APP_API_DECL - public function declaration prefix (default: extern) + SOKOL_API_DECL - same as SOKOL_APP_API_DECL + SOKOL_API_IMPL - public function implementation prefix (default: -) + + Optionally define the following to force debug checks and validations + even in release mode: + + SOKOL_DEBUG - by default this is defined if NDEBUG is not defined + + If sokol_app.h is compiled as a DLL, define the following before + including the declaration or implementation: + + SOKOL_DLL + + On Windows, SOKOL_DLL will define SOKOL_APP_API_DECL as __declspec(dllexport) + or __declspec(dllimport) as needed. + + if SOKOL_WIN32_FORCE_MAIN and SOKOL_WIN32_FORCE_WINMAIN are both defined, + it is up to the developer to define the desired subsystem. + + On Linux, SOKOL_GLCORE can use either GLX or EGL. + GLX is default, set SOKOL_FORCE_EGL to override. + + For example code, see https://github.com/floooh/sokol-samples/tree/master/sapp + + Portions of the Windows and Linux GL initialization, event-, icon- etc... code + have been taken from GLFW (http://www.glfw.org/). + + iOS onscreen keyboard support 'inspired' by libgdx. + + Link with the following system libraries: + + - on macOS: + - all backends: Foundation, Cocoa, QuartzCore + - with SOKOL_METAL: Metal, MetalKit + - with SOKOL_GLCORE: OpenGL + - with SOKOL_WGPU: a WebGPU implementation library (tested with webgpu_dawn) + - on iOS: + - all backends: Foundation, UIKit + - with SOKOL_METAL: Metal, MetalKit + - with SOKOL_GLES3: OpenGLES, GLKit + - on Linux: + - all backends: X11, Xi, Xcursor, dl, pthread, m + - with SOKOL_GLCORE: GL + - with SOKOL_GLES3: GLESv2 + - with SOKOL_WGPU: a WebGPU implementation library (tested with webgpu_dawn) + - with EGL: EGL + - on Android: GLESv3, EGL, log, android + - on Windows: + - with MSVC or Clang: library dependencies are defined via `#pragma comment` + - with SOKOL_WGPU: a WebGPU implementation library (tested with webgpu_dawn) + - with MINGW/MSYS2 gcc: + - compile with '-mwin32' so that _WIN32 is defined + - link with the following libs: -lkernel32 -luser32 -lshell32 + - additionally with the GL backend: -lgdi32 + - additionally with the D3D11 backend: -ld3d11 -ldxgi + + On Linux, you also need to use the -pthread compiler and linker option, otherwise weird + things will happen, see here for details: https://github.com/floooh/sokol/issues/376 + + On macOS and iOS, the implementation must be compiled as Objective-C. + + On Emscripten: + - for WebGL2: add the linker option `-s USE_WEBGL2=1` + - for WebGPU: compile and link with `--use-port=emdawnwebgpu` + (for more exotic situations read: https://dawn.googlesource.com/dawn/+/refs/heads/main/src/emdawnwebgpu/pkg/README.md) + + FEATURE OVERVIEW + ================ + sokol_app.h provides a minimalistic cross-platform API which + implements the 'application-wrapper' parts of a 3D application: + + - a common application entry function + - creates a window and 3D-API context/device with a swapchain + surface, depth-stencil-buffer surface and optionally MSAA surface + - makes the rendered frame visible + - provides keyboard-, mouse- and low-level touch-events + - platforms: MacOS, iOS, HTML5, Win32, Linux/RaspberryPi, Android + - 3D-APIs: Metal, D3D11, GL4.1, GL4.3, GLES3, WebGL2, WebGPU, NOAPI + + FEATURE/PLATFORM MATRIX + ======================= + | Windows | macOS | Linux | iOS | Android | HTML5 + --------------------+---------+-------+-------+-------+---------+-------- + gl 4.x | YES | YES | YES | --- | --- | --- + gles3/webgl2 | --- | --- | YES(2)| YES | YES | YES + metal | --- | YES | --- | YES | --- | --- + d3d11 | YES | --- | --- | --- | --- | --- + webgpu | YES(4) | YES(4)| YES(4)| NO | NO | YES + noapi | YES | TODO | TODO | --- | TODO | --- + KEY_DOWN | YES | YES | YES | SOME | TODO | YES + KEY_UP | YES | YES | YES | SOME | TODO | YES + CHAR | YES | YES | YES | YES | TODO | YES + MOUSE_DOWN | YES | YES | YES | --- | --- | YES + MOUSE_UP | YES | YES | YES | --- | --- | YES + MOUSE_SCROLL | YES | YES | YES | --- | --- | YES + MOUSE_MOVE | YES | YES | YES | --- | --- | YES + MOUSE_ENTER | YES | YES | YES | --- | --- | YES + MOUSE_LEAVE | YES | YES | YES | --- | --- | YES + TOUCHES_BEGAN | --- | --- | --- | YES | YES | YES + TOUCHES_MOVED | --- | --- | --- | YES | YES | YES + TOUCHES_ENDED | --- | --- | --- | YES | YES | YES + TOUCHES_CANCELLED | --- | --- | --- | YES | YES | YES + RESIZED | YES | YES | YES | YES | YES | YES + ICONIFIED | YES | YES | YES | --- | --- | --- + RESTORED | YES | YES | YES | --- | --- | --- + FOCUSED | YES | YES | YES | --- | --- | YES + UNFOCUSED | YES | YES | YES | --- | --- | YES + SUSPENDED | --- | --- | --- | YES | YES | TODO + RESUMED | --- | --- | --- | YES | YES | TODO + QUIT_REQUESTED | YES | YES | YES | --- | --- | YES + IME | TODO | TODO? | TODO | ??? | TODO | ??? + key repeat flag | YES | YES | YES | --- | --- | YES + windowed | YES | YES | YES | --- | --- | YES + fullscreen | YES | YES | YES | YES | YES | YES(3) + mouse hide | YES | YES | YES | --- | --- | YES + mouse lock | YES | YES | YES | --- | --- | YES + set cursor type | YES | YES | YES | --- | --- | YES + screen keyboard | --- | --- | --- | YES | TODO | YES + swap interval | YES | YES | YES | YES | TODO | YES + high-dpi | YES | YES | TODO | YES | YES | YES + clipboard | YES | YES | YES | --- | --- | YES + MSAA | YES | YES | YES | YES | YES | YES + drag'n'drop | YES | YES | YES | --- | --- | YES + window icon | YES | YES(1)| YES | --- | --- | YES + + (1) macOS has no regular window icons, instead the dock icon is changed + (2) supported with EGL only (not GLX) + (3) fullscreen in the browser not supported on iphones + (4) WebGPU on native desktop platforms should be considered experimental + and mainly useful for debugging and benchmarking + + STEP BY STEP + ============ + --- Add a sokol_main() function to your code which returns a sapp_desc structure + with initialization parameters and callback function pointers. This + function is called very early, usually at the start of the + platform's entry function (e.g. main or WinMain). You should do as + little as possible here, since the rest of your code might be called + from another thread (this depends on the platform): + + sapp_desc sokol_main(int argc, char* argv[]) { + return (sapp_desc) { + .width = 640, + .height = 480, + .init_cb = my_init_func, + .frame_cb = my_frame_func, + .cleanup_cb = my_cleanup_func, + .event_cb = my_event_func, + ... + }; + } + + To get any logging output in case of errors you need to provide a log + callback. The easiest way is via sokol_log.h: + + #include "sokol_log.h" + + sapp_desc sokol_main(int argc, char* argv[]) { + return (sapp_desc) { + ... + .logger.func = slog_func, + }; + } + + There are many more setup parameters, but these are the most important. + For a complete list search for the sapp_desc structure declaration + below. + + DO NOT call any sokol-app function from inside sokol_main(), since + sokol-app will not be initialized at this point. + + The .width and .height parameters are the preferred size of the 3D + rendering canvas. The actual size may differ from this depending on + platform and other circumstances. Also the canvas size may change at + any time (for instance when the user resizes the application window, + or rotates the mobile device). You can just keep .width and .height + zero-initialized to open a default-sized window (what "default-size" + exactly means is platform-specific, but usually it's a size that covers + most of, but not all, of the display). + + All provided function callbacks will be called from the same thread, + but this may be different from the thread where sokol_main() was called. + + .init_cb (void (*)(void)) + This function is called once after the application window, + 3D rendering context and swap chain have been created. The + function takes no arguments and has no return value. + .frame_cb (void (*)(void)) + This is the per-frame callback, which is usually called 60 + times per second. This is where your application would update + most of its state and perform all rendering. + .cleanup_cb (void (*)(void)) + The cleanup callback is called once right before the application + quits. + .event_cb (void (*)(const sapp_event* event)) + The event callback is mainly for input handling, but is also + used to communicate other types of events to the application. Keep the + event_cb struct member zero-initialized if your application doesn't require + event handling. + + As you can see, those 'standard callbacks' don't have a user_data + argument, so any data that needs to be preserved between callbacks + must live in global variables. If keeping state in global variables + is not an option, there's an alternative set of callbacks with + an additional user_data pointer argument: + + .user_data (void*) + The user-data argument for the callbacks below + .init_userdata_cb (void (*)(void* user_data)) + .frame_userdata_cb (void (*)(void* user_data)) + .cleanup_userdata_cb (void (*)(void* user_data)) + .event_userdata_cb (void(*)(const sapp_event* event, void* user_data)) + + The function sapp_userdata() can be used to query the user_data + pointer provided in the sapp_desc struct. + + You can also call sapp_query_desc() to get a copy of the + original sapp_desc structure. + + NOTE that there's also an alternative compile mode where sokol_app.h + doesn't "hijack" the main() function. Search below for SOKOL_NO_ENTRY. + + --- Implement the initialization callback function (init_cb), this is called + once after the rendering surface, 3D API and swap chain have been + initialized by sokol_app. All sokol-app functions can be called + from inside the initialization callback, the most useful functions + at this point are: + + int sapp_width(void) + int sapp_height(void) + Returns the current width and height of the default framebuffer in pixels, + this may change from one frame to the next, and it may be different + from the initial size provided in the sapp_desc struct. + + float sapp_widthf(void) + float sapp_heightf(void) + These are alternatives to sapp_width() and sapp_height() which return + the default framebuffer size as float values instead of integer. This + may help to prevent casting back and forth between int and float + in more strongly typed languages than C and C++. + + double sapp_frame_duration(void) + Returns the frame duration in seconds averaged over a number of + frames to smooth out any jittering spikes. + + int sapp_color_format(void) + int sapp_depth_format(void) + The color and depth-stencil pixelformats of the default framebuffer, + as integer values which are compatible with sokol-gfx's + sg_pixel_format enum (so that they can be plugged directly in places + where sg_pixel_format is expected). Possible values are: + + 23 == SG_PIXELFORMAT_RGBA8 + 28 == SG_PIXELFORMAT_BGRA8 + 42 == SG_PIXELFORMAT_DEPTH + 43 == SG_PIXELFORMAT_DEPTH_STENCIL + + int sapp_sample_count(void) + Return the MSAA sample count of the default framebuffer. + + const void* sapp_metal_get_device(void) + const void* sapp_metal_get_current_drawable(void) + const void* sapp_metal_get_depth_stencil_texture(void) + const void* sapp_metal_get_msaa_color_texture(void) + If the Metal backend has been selected, these functions return pointers + to various Metal API objects required for rendering, otherwise + they return a null pointer. These void pointers are actually + Objective-C ids converted with a (ARC) __bridge cast so that + the ids can be tunneled through C code. Also note that the returned + pointers may change from one frame to the next, only the Metal device + object is guaranteed to stay the same. + + const void* sapp_macos_get_window(void) + On macOS, get the NSWindow object pointer, otherwise a null pointer. + Before being used as Objective-C object, the void* must be converted + back with a (ARC) __bridge cast. + + const void* sapp_ios_get_window(void) + On iOS, get the UIWindow object pointer, otherwise a null pointer. + Before being used as Objective-C object, the void* must be converted + back with a (ARC) __bridge cast. + + const void* sapp_d3d11_get_device(void) + const void* sapp_d3d11_get_device_context(void) + const void* sapp_d3d11_get_render_view(void) + const void* sapp_d3d11_get_resolve_view(void); + const void* sapp_d3d11_get_depth_stencil_view(void) + Similar to the sapp_metal_* functions, the sapp_d3d11_* functions + return pointers to D3D11 API objects required for rendering, + only if the D3D11 backend has been selected. Otherwise they + return a null pointer. Note that the returned pointers to the + render-target-view and depth-stencil-view may change from one + frame to the next! + + const void* sapp_win32_get_hwnd(void) + On Windows, get the window's HWND, otherwise a null pointer. The + HWND has been cast to a void pointer in order to be tunneled + through code which doesn't include Windows.h. + + const void* sapp_x11_get_window(void) + On Linux, get the X11 Window, otherwise a null pointer. The + Window has been cast to a void pointer in order to be tunneled + through code which doesn't include X11/Xlib.h. + + const void* sapp_x11_get_display(void) + On Linux, get the X11 Display, otherwise a null pointer. The + Display has been cast to a void pointer in order to be tunneled + through code which doesn't include X11/Xlib.h. + + const void* sapp_wgpu_get_device(void) + const void* sapp_wgpu_get_render_view(void) + const void* sapp_wgpu_get_resolve_view(void) + const void* sapp_wgpu_get_depth_stencil_view(void) + These are the WebGPU-specific functions to get the WebGPU + objects and values required for rendering. If sokol_app.h + is not compiled with SOKOL_WGPU, these functions return null. + + uint32_t sapp_gl_get_framebuffer(void) + This returns the 'default framebuffer' of the GL context. + Typically this will be zero. + + int sapp_gl_get_major_version(void) + int sapp_gl_get_minor_version(void) + bool sapp_gl_is_gles(void) + Returns the major and minor version of the GL context and + whether the GL context is a GLES context + + const void* sapp_android_get_native_activity(void); + On Android, get the native activity ANativeActivity pointer, otherwise + a null pointer. + + --- Implement the frame-callback function, this function will be called + on the same thread as the init callback, but might be on a different + thread than the sokol_main() function. Note that the size of + the rendering framebuffer might have changed since the frame callback + was called last. Call the functions sapp_width() and sapp_height() + each frame to get the current size. + + --- Optionally implement the event-callback to handle input events. + sokol-app provides the following type of input events: + - a 'virtual key' was pressed down or released + - a single text character was entered (provided as UTF-32 encoded + UNICODE code point) + - a mouse button was pressed down or released (left, right, middle) + - mouse-wheel or 2D scrolling events + - the mouse was moved + - the mouse has entered or left the application window boundaries + - low-level, portable multi-touch events (began, moved, ended, cancelled) + - the application window was resized, iconified or restored + - the application was suspended or restored (on mobile platforms) + - the user or application code has asked to quit the application + - a string was pasted to the system clipboard + - one or more files have been dropped onto the application window + + To explicitly 'consume' an event and prevent that the event is + forwarded for further handling to the operating system, call + sapp_consume_event() from inside the event handler (NOTE that + this behaviour is currently only implemented for some HTML5 + events, support for other platforms and event types will + be added as needed, please open a GitHub ticket and/or provide + a PR if needed). + + NOTE: Do *not* call any 3D API rendering functions in the event + callback function, since the 3D API context may not be active when the + event callback is called (it may work on some platforms and 3D APIs, + but not others, and the exact behaviour may change between + sokol-app versions). + + --- Implement the cleanup-callback function, this is called once + after the user quits the application (see the section + "APPLICATION QUIT" for detailed information on quitting + behaviour, and how to intercept a pending quit - for instance to show a + "Really Quit?" dialog box). Note that the cleanup-callback isn't + guaranteed to be called on the web and mobile platforms. + + MOUSE CURSOR TYPE AND VISIBILITY + ================================ + You can show and hide the mouse cursor with + + void sapp_show_mouse(bool show) + + And to get the current shown status: + + bool sapp_mouse_shown(void) + + NOTE that hiding the mouse cursor is different and independent from + the MOUSE/POINTER LOCK feature which will also hide the mouse pointer when + active (MOUSE LOCK is described below). + + To change the mouse cursor to one of several predefined types, call + the function: + + void sapp_set_mouse_cursor(sapp_mouse_cursor cursor) + + Setting the default mouse cursor SAPP_MOUSECURSOR_DEFAULT will restore + the standard look. + + To get the currently active mouse cursor type, call: + + sapp_mouse_cursor sapp_get_mouse_cursor(void) + + MOUSE LOCK (AKA POINTER LOCK, AKA MOUSE CAPTURE) + ================================================ + In normal mouse mode, no mouse movement events are reported when the + mouse leaves the windows client area or hits the screen border (whether + it's one or the other depends on the platform), and the mouse move events + (SAPP_EVENTTYPE_MOUSE_MOVE) contain absolute mouse positions in + framebuffer pixels in the sapp_event items mouse_x and mouse_y, and + relative movement in framebuffer pixels in the sapp_event items mouse_dx + and mouse_dy. + + To get continuous mouse movement (also when the mouse leaves the window + client area or hits the screen border), activate mouse-lock mode + by calling: + + sapp_lock_mouse(true) + + When mouse lock is activated, the mouse pointer is hidden, the + reported absolute mouse position (sapp_event.mouse_x/y) appears + frozen, and the relative mouse movement in sapp_event.mouse_dx/dy + no longer has a direct relation to framebuffer pixels but instead + uses "raw mouse input" (what "raw mouse input" exactly means also + differs by platform). + + To deactivate mouse lock and return to normal mouse mode, call + + sapp_lock_mouse(false) + + And finally, to check if mouse lock is currently active, call + + if (sapp_mouse_locked()) { ... } + + Note that mouse-lock state may not change immediately after sapp_lock_mouse(true/false) + is called, instead on some platforms the actual state switch may be delayed + to the end of the current frame or even to a later frame. + + The mouse may also be unlocked automatically without calling sapp_lock_mouse(false), + most notably when the application window becomes inactive. + + On the web platform there are further restrictions to be aware of, caused + by the limitations of the HTML5 Pointer Lock API: + + - sapp_lock_mouse(true) can be called at any time, but it will + only take effect in a 'short-lived input event handler of a specific + type', meaning when one of the following events happens: + - SAPP_EVENTTYPE_MOUSE_DOWN + - SAPP_EVENTTYPE_MOUSE_UP + - SAPP_EVENTTYPE_MOUSE_SCROLL + - SAPP_EVENTTYPE_KEY_UP + - SAPP_EVENTTYPE_KEY_DOWN + - The mouse lock/unlock action on the web platform is asynchronous, + this means that sapp_mouse_locked() won't immediately return + the new status after calling sapp_lock_mouse(), instead the + reported status will only change when the pointer lock has actually + been activated or deactivated in the browser. + - On the web, mouse lock can be deactivated by the user at any time + by pressing the Esc key. When this happens, sokol_app.h behaves + the same as if sapp_lock_mouse(false) is called. + + For things like camera manipulation it's most straightforward to lock + and unlock the mouse right from the sokol_app.h event handler, for + instance the following code enters and leaves mouse lock when the + left mouse button is pressed and released, and then uses the relative + movement information to manipulate a camera (taken from the + cgltf-sapp.c sample in the sokol-samples repository + at https://github.com/floooh/sokol-samples): + + static void input(const sapp_event* ev) { + switch (ev->type) { + case SAPP_EVENTTYPE_MOUSE_DOWN: + if (ev->mouse_button == SAPP_MOUSEBUTTON_LEFT) { + sapp_lock_mouse(true); + } + break; + + case SAPP_EVENTTYPE_MOUSE_UP: + if (ev->mouse_button == SAPP_MOUSEBUTTON_LEFT) { + sapp_lock_mouse(false); + } + break; + + case SAPP_EVENTTYPE_MOUSE_MOVE: + if (sapp_mouse_locked()) { + cam_orbit(&state.camera, ev->mouse_dx * 0.25f, ev->mouse_dy * 0.25f); + } + break; + + default: + break; + } + } + + For a 'first person shooter mouse' the following code inside the sokol-app event handler + is recommended somewhere in your frame callback: + + if (!sapp_mouse_locked()) { + sapp_lock_mouse(true); + } + + CLIPBOARD SUPPORT + ================= + Applications can send and receive UTF-8 encoded text data from and to the + system clipboard. By default, clipboard support is disabled and + must be enabled at startup via the following sapp_desc struct + members: + + sapp_desc.enable_clipboard - set to true to enable clipboard support + sapp_desc.clipboard_size - size of the internal clipboard buffer in bytes + + Enabling the clipboard will dynamically allocate a clipboard buffer + for UTF-8 encoded text data of the requested size in bytes, the default + size is 8 KBytes. Strings that don't fit into the clipboard buffer + (including the terminating zero) will be silently clipped, so it's + important that you provide a big enough clipboard size for your + use case. + + To send data to the clipboard, call sapp_set_clipboard_string() with + a pointer to an UTF-8 encoded, null-terminated C-string. + + NOTE that on the HTML5 platform, sapp_set_clipboard_string() must be + called from inside a 'short-lived event handler', and there are a few + other HTML5-specific caveats to workaround. You'll basically have to + tinker until it works in all browsers :/ (maybe the situation will + improve when all browsers agree on and implement the new + HTML5 navigator.clipboard API). + + To get data from the clipboard, check for the SAPP_EVENTTYPE_CLIPBOARD_PASTED + event in your event handler function, and then call sapp_get_clipboard_string() + to obtain the pasted UTF-8 encoded text. + + NOTE that behaviour of sapp_get_clipboard_string() is slightly different + depending on platform: + + - on the HTML5 platform, the internal clipboard buffer will only be updated + right before the SAPP_EVENTTYPE_CLIPBOARD_PASTED event is sent, + and sapp_get_clipboard_string() will simply return the current content + of the clipboard buffer + - on 'native' platforms, the call to sapp_get_clipboard_string() will + update the internal clipboard buffer with the most recent data + from the system clipboard + + Portable code should check for the SAPP_EVENTTYPE_CLIPBOARD_PASTED event, + and then call sapp_get_clipboard_string() right in the event handler. + + The SAPP_EVENTTYPE_CLIPBOARD_PASTED event will be generated by sokol-app + as follows: + + - on macOS: when the Cmd+V key is pressed down + - on HTML5: when the browser sends a 'paste' event to the global 'window' object + - on all other platforms: when the Ctrl+V key is pressed down + + DRAG AND DROP SUPPORT + ===================== + PLEASE NOTE: the drag'n'drop feature works differently on WASM/HTML5 + and on the native desktop platforms (Win32, Linux and macOS) because + of security-related restrictions in the HTML5 drag'n'drop API. The + WASM/HTML5 specifics are described at the end of this documentation + section: + + Like clipboard support, drag'n'drop support must be explicitly enabled + at startup in the sapp_desc struct. + + sapp_desc sokol_main(void) { + return (sapp_desc) { + .enable_dragndrop = true, // default is false + ... + }; + } + + You can also adjust the maximum number of files that are accepted + in a drop operation, and the maximum path length in bytes if needed: + + sapp_desc sokol_main(void) { + return (sapp_desc) { + .enable_dragndrop = true, // default is false + .max_dropped_files = 8, // default is 1 + .max_dropped_file_path_length = 8192, // in bytes, default is 2048 + ... + }; + } + + When drag'n'drop is enabled, the event callback will be invoked with an + event of type SAPP_EVENTTYPE_FILES_DROPPED whenever the user drops files on + the application window. + + After the SAPP_EVENTTYPE_FILES_DROPPED is received, you can query the + number of dropped files, and their absolute paths by calling separate + functions: + + void on_event(const sapp_event* ev) { + if (ev->type == SAPP_EVENTTYPE_FILES_DROPPED) { + + // the mouse position where the drop happened + float x = ev->mouse_x; + float y = ev->mouse_y; + + // get the number of files and their paths like this: + const int num_dropped_files = sapp_get_num_dropped_files(); + for (int i = 0; i < num_dropped_files; i++) { + const char* path = sapp_get_dropped_file_path(i); + ... + } + } + } + + The returned file paths are UTF-8 encoded strings. + + You can call sapp_get_num_dropped_files() and sapp_get_dropped_file_path() + anywhere, also outside the event handler callback, but be aware that the + file path strings will be overwritten with the next drop operation. + + In any case, sapp_get_dropped_file_path() will never return a null pointer, + instead an empty string "" will be returned if the drag'n'drop feature + hasn't been enabled, the last drop-operation failed, or the file path index + is out of range. + + Drag'n'drop caveats: + + - if more files are dropped in a single drop-action + than sapp_desc.max_dropped_files, the additional + files will be silently ignored + - if any of the file paths is longer than + sapp_desc.max_dropped_file_path_length (in number of bytes, after UTF-8 + encoding) the entire drop operation will be silently ignored (this + needs some sort of error feedback in the future) + - no mouse positions are reported while the drag is in + process, this may change in the future + + Drag'n'drop on HTML5/WASM: + + The HTML5 drag'n'drop API doesn't return file paths, but instead + black-box 'file objects' which must be used to load the content + of dropped files. This is the reason why sokol_app.h adds two + HTML5-specific functions to the drag'n'drop API: + + uint32_t sapp_html5_get_dropped_file_size(int index) + Returns the size in bytes of a dropped file. + + void sapp_html5_fetch_dropped_file(const sapp_html5_fetch_request* request) + Asynchronously loads the content of a dropped file into a + provided memory buffer (which must be big enough to hold + the file content) + + To start loading the first dropped file after an SAPP_EVENTTYPE_FILES_DROPPED + event is received: + + sapp_html5_fetch_dropped_file(&(sapp_html5_fetch_request){ + .dropped_file_index = 0, + .callback = fetch_cb + .buffer = { + .ptr = buf, + .size = sizeof(buf) + }, + .user_data = ... + }); + + Make sure that the memory pointed to by 'buf' stays valid until the + callback function is called! + + As result of the asynchronous loading operation (no matter if succeeded or + failed) the 'fetch_cb' function will be called: + + void fetch_cb(const sapp_html5_fetch_response* response) { + // IMPORTANT: check if the loading operation actually succeeded: + if (response->succeeded) { + // the size of the loaded file: + const size_t num_bytes = response->data.size; + // and the pointer to the data (same as 'buf' in the fetch-call): + const void* ptr = response->data.ptr; + } else { + // on error check the error code: + switch (response->error_code) { + case SAPP_HTML5_FETCH_ERROR_BUFFER_TOO_SMALL: + ... + break; + case SAPP_HTML5_FETCH_ERROR_OTHER: + ... + break; + } + } + } + + Check the droptest-sapp example for a real-world example which works + both on native platforms and the web: + + https://github.com/floooh/sokol-samples/blob/master/sapp/droptest-sapp.c + + HIGH-DPI RENDERING + ================== + You can set the sapp_desc.high_dpi flag during initialization to request + a full-resolution framebuffer on HighDPI displays. The default behaviour + is sapp_desc.high_dpi=false, this means that the application will + render to a lower-resolution framebuffer on HighDPI displays and the + rendered content will be upscaled by the window system composer. + + In a HighDPI scenario, you still request the same window size during + sokol_main(), but the framebuffer sizes returned by sapp_width() + and sapp_height() will be scaled up according to the DPI scaling + ratio. + + Note that on some platforms the DPI scaling factor may change at any + time (for instance when a window is moved from a high-dpi display + to a low-dpi display). + + To query the current DPI scaling factor, call the function: + + float sapp_dpi_scale(void); + + For instance on a Retina Mac, returning the following sapp_desc + struct from sokol_main(): + + sapp_desc sokol_main(void) { + return (sapp_desc) { + .width = 640, + .height = 480, + .high_dpi = true, + ... + }; + } + + ...the functions the functions sapp_width(), sapp_height() + and sapp_dpi_scale() will return the following values: + + sapp_width: 1280 + sapp_height: 960 + sapp_dpi_scale: 2.0 + + If the high_dpi flag is false, or you're not running on a Retina display, + the values would be: + + sapp_width: 640 + sapp_height: 480 + sapp_dpi_scale: 1.0 + + If the window is moved from the Retina display to a low-dpi external display, + the values would change as follows: + + sapp_width: 1280 => 640 + sapp_height: 960 => 480 + sapp_dpi_scale: 2.0 => 1.0 + + Currently there is no event associated with a DPI change, but an + SAPP_EVENTTYPE_RESIZED will be sent as a side effect of the + framebuffer size changing. + + Per-monitor DPI is currently supported on macOS and Windows. + + APPLICATION QUIT + ================ + Without special quit handling, a sokol_app.h application will quit + 'gracefully' when the user clicks the window close-button unless a + platform's application model prevents this (e.g. on web or mobile). + 'Graceful exit' means that the application-provided cleanup callback will + be called before the application quits. + + On native desktop platforms sokol_app.h provides more control over the + application-quit-process. It's possible to initiate a 'programmatic quit' + from the application code, and a quit initiated by the application user can + be intercepted (for instance to show a custom dialog box). + + This 'programmatic quit protocol' is implemented through 3 functions + and 1 event: + + - sapp_quit(): This function simply quits the application without + giving the user a chance to intervene. Usually this might + be called when the user clicks the 'Ok' button in a 'Really Quit?' + dialog box + - sapp_request_quit(): Calling sapp_request_quit() will send the + event SAPP_EVENTTYPE_QUIT_REQUESTED to the applications event handler + callback, giving the user code a chance to intervene and cancel the + pending quit process (for instance to show a 'Really Quit?' dialog + box). If the event handler callback does nothing, the application + will be quit as usual. To prevent this, call the function + sapp_cancel_quit() from inside the event handler. + - sapp_cancel_quit(): Cancels a pending quit request, either initiated + by the user clicking the window close button, or programmatically + by calling sapp_request_quit(). The only place where calling this + function makes sense is from inside the event handler callback when + the SAPP_EVENTTYPE_QUIT_REQUESTED event has been received. + - SAPP_EVENTTYPE_QUIT_REQUESTED: this event is sent when the user + clicks the window's close button or application code calls the + sapp_request_quit() function. The event handler callback code can handle + this event by calling sapp_cancel_quit() to cancel the quit. + If the event is ignored, the application will quit as usual. + + On the web platform, the quit behaviour differs from native platforms, + because of web-specific restrictions: + + A `programmatic quit` initiated by calling sapp_quit() or + sapp_request_quit() will work as described above: the cleanup callback is + called, platform-specific cleanup is performed (on the web + this means that JS event handlers are unregistered), and then + the request-animation-loop will be exited. However that's all. The + web page itself will continue to exist (e.g. it's not possible to + programmatically close the browser tab). + + On the web it's also not possible to run custom code when the user + closes a browser tab, so it's not possible to prevent this with a + fancy custom dialog box. + + Instead the standard "Leave Site?" dialog box can be activated (or + deactivated) with the following function: + + sapp_html5_ask_leave_site(bool ask); + + The initial state of the associated internal flag can be provided + at startup via sapp_desc.html5_ask_leave_site. + + This feature should only be used sparingly in critical situations - for + instance when the user would loose data - since popping up modal dialog + boxes is considered quite rude in the web world. Note that there's no way + to customize the content of this dialog box or run any code as a result + of the user's decision. Also note that the user must have interacted with + the site before the dialog box will appear. These are all security measures + to prevent fishing. + + The Dear ImGui HighDPI sample contains example code of how to + implement a 'Really Quit?' dialog box with Dear ImGui (native desktop + platforms only), and for showing the hardwired "Leave Site?" dialog box + when running on the web platform: + + https://floooh.github.io/sokol-html5/wasm/imgui-highdpi-sapp.html + + FULLSCREEN + ========== + If the sapp_desc.fullscreen flag is true, sokol-app will try to create + a fullscreen window on platforms with a 'proper' window system + (mobile devices will always use fullscreen). The implementation details + depend on the target platform, in general sokol-app will use a + 'soft approach' which doesn't interfere too much with the platform's + window system (for instance borderless fullscreen window instead of + a 'real' fullscreen mode). Such details might change over time + as sokol-app is adapted for different needs. + + The most important effect of fullscreen mode to keep in mind is that + the requested canvas width and height will be ignored for the initial + window size, calling sapp_width() and sapp_height() will instead return + the resolution of the fullscreen canvas (however the provided size + might still be used for the non-fullscreen window, in case the user can + switch back from fullscreen- to windowed-mode). + + To toggle fullscreen mode programmatically, call sapp_toggle_fullscreen(). + + To check if the application window is currently in fullscreen mode, + call sapp_is_fullscreen(). + + On the web, sapp_desc.fullscreen will have no effect, and the application + will always start in non-fullscreen mode. Call sapp_toggle_fullscreen() + from within or 'near' an input event to switch to fullscreen programatically. + Note that on the web, the fullscreen state may change back to windowed at + any time (either because the browser had rejected switching into fullscreen, + or the user leaves fullscreen via Esc), this means that the result + of sapp_is_fullscreen() may change also without calling sapp_toggle_fullscreen()! + + + WINDOW ICON SUPPORT + =================== + Some sokol_app.h backends allow to change the window icon programmatically: + + - on Win32: the small icon in the window's title bar, and the + bigger icon in the task bar + - on Linux: highly dependent on the used window manager, but usually + the window's title bar icon and/or the task bar icon + - on HTML5: the favicon shown in the page's browser tab + - on macOS: the application icon shown in the dock, but only + for currently running applications + + NOTE that it is not possible to set the actual application icon which is + displayed by the operating system on the desktop or 'home screen'. Those + icons must be provided 'traditionally' through operating-system-specific + resources which are associated with the application (sokol_app.h might + later support setting the window icon from platform specific resource data + though). + + There are two ways to set the window icon: + + - at application start in the sokol_main() function by initializing + the sapp_desc.icon nested struct + - or later by calling the function sapp_set_icon() + + As a convenient shortcut, sokol_app.h comes with a builtin default-icon + (a rainbow-colored 'S', which at least looks a bit better than the Windows + default icon for applications), which can be activated like this: + + At startup in sokol_main(): + + sapp_desc sokol_main(...) { + return (sapp_desc){ + ... + icon.sokol_default = true + }; + } + + Or later by calling: + + sapp_set_icon(&(sapp_icon_desc){ .sokol_default = true }); + + NOTE that a completely zero-initialized sapp_icon_desc struct will not + update the window icon in any way. This is an 'escape hatch' so that you + can handle the window icon update yourself (or if you do this already, + sokol_app.h won't get in your way, in this case just leave the + sapp_desc.icon struct zero-initialized). + + Providing your own icon images works exactly like in GLFW (down to the + data format): + + You provide one or more 'candidate images' in different sizes, and the + sokol_app.h platform backends pick the best match for the specific backend + and icon type. + + For each candidate image, you need to provide: + + - the width in pixels + - the height in pixels + - and the actual pixel data in RGBA8 pixel format (e.g. 0xFFCC8844 + on a little-endian CPU means: alpha=0xFF, blue=0xCC, green=0x88, red=0x44) + + For instance, if you have 3 candidate images (small, medium, big) of + sizes 16x16, 32x32 and 64x64 the corresponding sapp_icon_desc struct is setup + like this: + + // the actual pixel data (RGBA8, origin top-left) + const uint32_t small[16][16] = { ... }; + const uint32_t medium[32][32] = { ... }; + const uint32_t big[64][64] = { ... }; + + const sapp_icon_desc icon_desc = { + .images = { + { .width = 16, .height = 16, .pixels = SAPP_RANGE(small) }, + { .width = 32, .height = 32, .pixels = SAPP_RANGE(medium) }, + // ...or without the SAPP_RANGE helper macro: + { .width = 64, .height = 64, .pixels = { .ptr=big, .size=sizeof(big) } } + } + }; + + An sapp_icon_desc struct initialized like this can then either be applied + at application start in sokol_main: + + sapp_desc sokol_main(...) { + return (sapp_desc){ + ... + icon = icon_desc + }; + } + + ...or later by calling sapp_set_icon(): + + sapp_set_icon(&icon_desc); + + Some window icon caveats: + + - once the window icon has been updated, there's no way to go back to + the platform's default icon, this is because some platforms (Linux + and HTML5) don't switch the icon visual back to the default even if + the custom icon is deleted or removed + - on HTML5, if the sokol_app.h icon doesn't show up in the browser + tab, check that there's no traditional favicon 'link' element + is defined in the page's index.html, sokol_app.h will only + append a new favicon link element, but not delete any manually + defined favicon in the page + + For an example and test of the window icon feature, check out the + 'icon-sapp' sample on the sokol-samples git repository. + + ONSCREEN KEYBOARD + ================= + On some platforms which don't provide a physical keyboard, sokol-app + can display the platform's integrated onscreen keyboard for text + input. To request that the onscreen keyboard is shown, call + + sapp_show_keyboard(true); + + Likewise, to hide the keyboard call: + + sapp_show_keyboard(false); + + Note that onscreen keyboard functionality is no longer supported + on the browser platform (the previous hacks and workarounds to make browser + keyboards work for on web applications that don't use HTML UIs + never really worked across browsers). + + INPUT EVENT BUBBLING ON THE WEB PLATFORM + ======================================== + By default, input event bubbling on the web platform is configured in + a way that makes the most sense for 'full-canvas' apps that cover the + entire browser client window area: + + - mouse, touch and wheel events do not bubble up, this prevents various + ugly side events, like: + - HTML text overlays being selected on double- or triple-click into + the canvas + - 'scroll bumping' even when the canvas covers the entire client area + - key_up/down events for 'character keys' *do* bubble up (otherwise + the browser will not generate UNICODE character events) + - all other key events *do not* bubble up by default (this prevents side effects + like F1 opening help, or F7 starting 'caret browsing') + - character events do not bubble up (although I haven't noticed any side effects + otherwise) + + Event bubbling can be enabled for input event categories during initialization + in the sapp_desc struct: + + sapp_desc sokol_main(int argc, char* argv[]) { + return (sapp_desc){ + //... + .html5_bubble_mouse_events = true, + .html5_bubble_touch_events = true, + .html5_bubble_wheel_events = true, + .html5_bubble_key_events = true, + .html5_bubble_char_events = true, + }; + } + + This basically opens the floodgates and lets *all* input events bubble up to the browser. + + To prevent individual events from bubbling, call sapp_consume_event() from within + the sokol_app.h event callback when that specific event is reported. + + + SETTING THE CANVAS OBJECT ON THE WEB PLATFORM + ============================================= + On the web, sokol_app.h and the Emscripten SDK functions need to find + the WebGL/WebGPU canvas intended for rendering and attaching event + handlers. This can happen in four ways: + + 1. do nothing and just set the id of the canvas object to 'canvas' (preferred) + 2. via a CSS Selector string (preferred) + 3. by setting the `Module.canvas` property to the canvas object + 4. by adding the canvas object to the global variable `specialHTMLTargets[]` + (this is a special variable used by the Emscripten runtime to lookup + event target objects for which document.querySelector() cannot be used) + + The easiest way is to just name your canvas object 'canvas': + + + + This works because the default css selector string used by sokol_app.h + is '#canvas'. + + If you name your canvas differently, you need to communicate that name to + sokol_app.h via `sapp_desc.html5_canvas_selector` as a regular css selector + string that's compatible with `document.querySelector()`. E.g. if your canvas + object looks like this: + + + + The `sapp_desc.html5_canvas_selector` string must be set to '#bla': + + .html5_canvas_selector = "#bla" + + If the canvas object cannot be looked up via `document.querySelector()` you + need to use one of the alternative methods, both involve the special + Emscripten runtime `Module` object which is usually setup in the index.html + like this before the WASM blob is loaded and instantiated: + + + + The first option is to set the `Module.canvas` property to your canvas object: + + + + When sokol_app.h initializes, it will check the global Module object whether + a `Module.canvas` property exists and is an object. This method will add + a new entry to the `specialHTMLTargets[]` object + + The other option is to add the canvas under a name chosen by you to the + special `specialHTMLTargets[]` map, which is used by the Emscripten runtime + to lookup 'event target objects' which are not visible to `document.querySelector()`. + Note that `specialHTMLTargets[]` must be updated after the Emscripten runtime + has started but before the WASM code is running. A good place for this is + the special `Module.preRun` array in index.html: + + + + In that case, pass the same string to sokol_app.h which is used as key + in the specialHTMLTargets[] map: + + .html5_canvas_selector = "my_canvas" + + If sokol_app.h can't find your canvas for some reason check for warning + messages on the browser console. + + + OPTIONAL: DON'T HIJACK main() (#define SOKOL_NO_ENTRY) + ====================================================== + NOTE: SOKOL_NO_ENTRY and sapp_run() is currently not supported on Android. + + In its default configuration, sokol_app.h "hijacks" the platform's + standard main() function. This was done because different platforms + have different entry point conventions which are not compatible with + C's main() (for instance WinMain on Windows has completely different + arguments). However, this "main hijacking" posed a problem for + usage scenarios like integrating sokol_app.h with other languages than + C or C++, so an alternative SOKOL_NO_ENTRY mode has been added + in which the user code provides the platform's main function: + + - define SOKOL_NO_ENTRY before including the sokol_app.h implementation + - do *not* provide a sokol_main() function + - instead provide the standard main() function of the platform + - from the main function, call the function ```sapp_run()``` which + takes a pointer to an ```sapp_desc``` structure. + - from here on```sapp_run()``` takes over control and calls the provided + init-, frame-, event- and cleanup-callbacks just like in the default model. + + sapp_run() behaves differently across platforms: + + - on some platforms, sapp_run() will return when the application quits + - on other platforms, sapp_run() will never return, even when the + application quits (the operating system is free to simply terminate + the application at any time) + - on Emscripten specifically, sapp_run() will return immediately while + the frame callback keeps being called + + This different behaviour of sapp_run() essentially means that there shouldn't + be any code *after* sapp_run(), because that may either never be called, or in + case of Emscripten will be called at an unexpected time (at application start). + + An application also should not depend on the cleanup-callback being called + when cross-platform compatibility is required. + + Since sapp_run() returns immediately on Emscripten you shouldn't activate + the 'EXIT_RUNTIME' linker option (this is disabled by default when compiling + for the browser target), since the C/C++ exit runtime would be called immediately at + application start, causing any global objects to be destroyed and global + variables to be zeroed. + + WINDOWS CONSOLE OUTPUT + ====================== + On Windows, regular windowed applications don't show any stdout/stderr text + output, which can be a bit of a hassle for printf() debugging or generally + logging text to the console. Also, console output by default uses a local + codepage setting and thus international UTF-8 encoded text is printed + as garbage. + + To help with these issues, sokol_app.h can be configured at startup + via the following Windows-specific sapp_desc flags: + + sapp_desc.win32_console_utf8 (default: false) + When set to true, the output console codepage will be switched + to UTF-8 (and restored to the original codepage on exit) + + sapp_desc.win32_console_attach (default: false) + When set to true, stdout and stderr will be attached to the + console of the parent process (if the parent process actually + has a console). This means that if the application was started + in a command line window, stdout and stderr output will be printed + to the terminal, just like a regular command line program. But if + the application is started via double-click, it will behave like + a regular UI application, and stdout/stderr will not be visible. + + sapp_desc.win32_console_create (default: false) + When set to true, a new console window will be created and + stdout/stderr will be redirected to that console window. It + doesn't matter if the application is started from the command + line or via double-click. + + MEMORY ALLOCATION OVERRIDE + ========================== + You can override the memory allocation functions at initialization time + like this: + + void* my_alloc(size_t size, void* user_data) { + return malloc(size); + } + + void my_free(void* ptr, void* user_data) { + free(ptr); + } + + sapp_desc sokol_main(int argc, char* argv[]) { + return (sapp_desc){ + // ... + .allocator = { + .alloc_fn = my_alloc, + .free_fn = my_free, + .user_data = ..., + } + }; + } + + If no overrides are provided, malloc and free will be used. + + This only affects memory allocation calls done by sokol_app.h + itself though, not any allocations in OS libraries. + + + ERROR REPORTING AND LOGGING + =========================== + To get any logging information at all you need to provide a logging callback in the setup call + the easiest way is to use sokol_log.h: + + #include "sokol_log.h" + + sapp_desc sokol_main(int argc, char* argv[]) { + return (sapp_desc) { + ... + .logger.func = slog_func, + }; + } + + To override logging with your own callback, first write a logging function like this: + + void my_log(const char* tag, // e.g. 'sapp' + uint32_t log_level, // 0=panic, 1=error, 2=warn, 3=info + uint32_t log_item_id, // SAPP_LOGITEM_* + const char* message_or_null, // a message string, may be nullptr in release mode + uint32_t line_nr, // line number in sokol_app.h + const char* filename_or_null, // source filename, may be nullptr in release mode + void* user_data) + { + ... + } + + ...and then setup sokol-app like this: + + sapp_desc sokol_main(int argc, char* argv[]) { + return (sapp_desc) { + ... + .logger = { + .func = my_log, + .user_data = my_user_data, + } + }; + } + + The provided logging function must be reentrant (e.g. be callable from + different threads). + + If you don't want to provide your own custom logger it is highly recommended to use + the standard logger in sokol_log.h instead, otherwise you won't see any warnings or + errors. + + TEMP NOTE DUMP + ============== + - sapp_desc needs a bool whether to initialize depth-stencil surface + - the Android implementation calls cleanup_cb() and destroys the egl context in onDestroy + at the latest but should do it earlier, in onStop, as an app is "killable" after onStop + on Android Honeycomb and later (it can't be done at the moment as the app may be started + again after onStop and the sokol lifecycle does not yet handle context teardown/bringup) + + + LICENSE + ======= + zlib/libpng license + + Copyright (c) 2018 Andre Weissflog + + This software is provided 'as-is', without any express or implied warranty. + In no event will the authors be held liable for any damages arising from the + use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software in a + product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not + be misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ +#define SOKOL_APP_INCLUDED (1) +#include // size_t +#include +#include + +#if defined(SOKOL_API_DECL) && !defined(SOKOL_APP_API_DECL) +#define SOKOL_APP_API_DECL SOKOL_API_DECL +#endif +#ifndef SOKOL_APP_API_DECL +#if defined(_WIN32) && defined(SOKOL_DLL) && defined(SOKOL_APP_IMPL) +#define SOKOL_APP_API_DECL __declspec(dllexport) +#elif defined(_WIN32) && defined(SOKOL_DLL) +#define SOKOL_APP_API_DECL __declspec(dllimport) +#else +#define SOKOL_APP_API_DECL extern +#endif +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/* misc constants */ +enum { + SAPP_MAX_TOUCHPOINTS = 8, + SAPP_MAX_MOUSEBUTTONS = 3, + SAPP_MAX_KEYCODES = 512, + SAPP_MAX_ICONIMAGES = 8, +}; + +/* + sapp_event_type + + The type of event that's passed to the event handler callback + in the sapp_event.type field. These are not just "traditional" + input events, but also notify the application about state changes + or other user-invoked actions. +*/ +typedef enum sapp_event_type { + SAPP_EVENTTYPE_INVALID, + SAPP_EVENTTYPE_KEY_DOWN, + SAPP_EVENTTYPE_KEY_UP, + SAPP_EVENTTYPE_CHAR, + SAPP_EVENTTYPE_MOUSE_DOWN, + SAPP_EVENTTYPE_MOUSE_UP, + SAPP_EVENTTYPE_MOUSE_SCROLL, + SAPP_EVENTTYPE_MOUSE_MOVE, + SAPP_EVENTTYPE_MOUSE_ENTER, + SAPP_EVENTTYPE_MOUSE_LEAVE, + SAPP_EVENTTYPE_TOUCHES_BEGAN, + SAPP_EVENTTYPE_TOUCHES_MOVED, + SAPP_EVENTTYPE_TOUCHES_ENDED, + SAPP_EVENTTYPE_TOUCHES_CANCELLED, + SAPP_EVENTTYPE_RESIZED, + SAPP_EVENTTYPE_ICONIFIED, + SAPP_EVENTTYPE_RESTORED, + SAPP_EVENTTYPE_FOCUSED, + SAPP_EVENTTYPE_UNFOCUSED, + SAPP_EVENTTYPE_SUSPENDED, + SAPP_EVENTTYPE_RESUMED, + SAPP_EVENTTYPE_QUIT_REQUESTED, + SAPP_EVENTTYPE_CLIPBOARD_PASTED, + SAPP_EVENTTYPE_FILES_DROPPED, + _SAPP_EVENTTYPE_NUM, + _SAPP_EVENTTYPE_FORCE_U32 = 0x7FFFFFFF +} sapp_event_type; + +/* + sapp_keycode + + The 'virtual keycode' of a KEY_DOWN or KEY_UP event in the + struct field sapp_event.key_code. + + Note that the keycode values are identical with GLFW. +*/ +typedef enum sapp_keycode { + SAPP_KEYCODE_INVALID = 0, + SAPP_KEYCODE_SPACE = 32, + SAPP_KEYCODE_APOSTROPHE = 39, /* ' */ + SAPP_KEYCODE_COMMA = 44, /* , */ + SAPP_KEYCODE_MINUS = 45, /* - */ + SAPP_KEYCODE_PERIOD = 46, /* . */ + SAPP_KEYCODE_SLASH = 47, /* / */ + SAPP_KEYCODE_0 = 48, + SAPP_KEYCODE_1 = 49, + SAPP_KEYCODE_2 = 50, + SAPP_KEYCODE_3 = 51, + SAPP_KEYCODE_4 = 52, + SAPP_KEYCODE_5 = 53, + SAPP_KEYCODE_6 = 54, + SAPP_KEYCODE_7 = 55, + SAPP_KEYCODE_8 = 56, + SAPP_KEYCODE_9 = 57, + SAPP_KEYCODE_SEMICOLON = 59, /* ; */ + SAPP_KEYCODE_EQUAL = 61, /* = */ + SAPP_KEYCODE_A = 65, + SAPP_KEYCODE_B = 66, + SAPP_KEYCODE_C = 67, + SAPP_KEYCODE_D = 68, + SAPP_KEYCODE_E = 69, + SAPP_KEYCODE_F = 70, + SAPP_KEYCODE_G = 71, + SAPP_KEYCODE_H = 72, + SAPP_KEYCODE_I = 73, + SAPP_KEYCODE_J = 74, + SAPP_KEYCODE_K = 75, + SAPP_KEYCODE_L = 76, + SAPP_KEYCODE_M = 77, + SAPP_KEYCODE_N = 78, + SAPP_KEYCODE_O = 79, + SAPP_KEYCODE_P = 80, + SAPP_KEYCODE_Q = 81, + SAPP_KEYCODE_R = 82, + SAPP_KEYCODE_S = 83, + SAPP_KEYCODE_T = 84, + SAPP_KEYCODE_U = 85, + SAPP_KEYCODE_V = 86, + SAPP_KEYCODE_W = 87, + SAPP_KEYCODE_X = 88, + SAPP_KEYCODE_Y = 89, + SAPP_KEYCODE_Z = 90, + SAPP_KEYCODE_LEFT_BRACKET = 91, /* [ */ + SAPP_KEYCODE_BACKSLASH = 92, /* \ */ + SAPP_KEYCODE_RIGHT_BRACKET = 93, /* ] */ + SAPP_KEYCODE_GRAVE_ACCENT = 96, /* ` */ + SAPP_KEYCODE_WORLD_1 = 161, /* non-US #1 */ + SAPP_KEYCODE_WORLD_2 = 162, /* non-US #2 */ + SAPP_KEYCODE_ESCAPE = 256, + SAPP_KEYCODE_ENTER = 257, + SAPP_KEYCODE_TAB = 258, + SAPP_KEYCODE_BACKSPACE = 259, + SAPP_KEYCODE_INSERT = 260, + SAPP_KEYCODE_DELETE = 261, + SAPP_KEYCODE_RIGHT = 262, + SAPP_KEYCODE_LEFT = 263, + SAPP_KEYCODE_DOWN = 264, + SAPP_KEYCODE_UP = 265, + SAPP_KEYCODE_PAGE_UP = 266, + SAPP_KEYCODE_PAGE_DOWN = 267, + SAPP_KEYCODE_HOME = 268, + SAPP_KEYCODE_END = 269, + SAPP_KEYCODE_CAPS_LOCK = 280, + SAPP_KEYCODE_SCROLL_LOCK = 281, + SAPP_KEYCODE_NUM_LOCK = 282, + SAPP_KEYCODE_PRINT_SCREEN = 283, + SAPP_KEYCODE_PAUSE = 284, + SAPP_KEYCODE_F1 = 290, + SAPP_KEYCODE_F2 = 291, + SAPP_KEYCODE_F3 = 292, + SAPP_KEYCODE_F4 = 293, + SAPP_KEYCODE_F5 = 294, + SAPP_KEYCODE_F6 = 295, + SAPP_KEYCODE_F7 = 296, + SAPP_KEYCODE_F8 = 297, + SAPP_KEYCODE_F9 = 298, + SAPP_KEYCODE_F10 = 299, + SAPP_KEYCODE_F11 = 300, + SAPP_KEYCODE_F12 = 301, + SAPP_KEYCODE_F13 = 302, + SAPP_KEYCODE_F14 = 303, + SAPP_KEYCODE_F15 = 304, + SAPP_KEYCODE_F16 = 305, + SAPP_KEYCODE_F17 = 306, + SAPP_KEYCODE_F18 = 307, + SAPP_KEYCODE_F19 = 308, + SAPP_KEYCODE_F20 = 309, + SAPP_KEYCODE_F21 = 310, + SAPP_KEYCODE_F22 = 311, + SAPP_KEYCODE_F23 = 312, + SAPP_KEYCODE_F24 = 313, + SAPP_KEYCODE_F25 = 314, + SAPP_KEYCODE_KP_0 = 320, + SAPP_KEYCODE_KP_1 = 321, + SAPP_KEYCODE_KP_2 = 322, + SAPP_KEYCODE_KP_3 = 323, + SAPP_KEYCODE_KP_4 = 324, + SAPP_KEYCODE_KP_5 = 325, + SAPP_KEYCODE_KP_6 = 326, + SAPP_KEYCODE_KP_7 = 327, + SAPP_KEYCODE_KP_8 = 328, + SAPP_KEYCODE_KP_9 = 329, + SAPP_KEYCODE_KP_DECIMAL = 330, + SAPP_KEYCODE_KP_DIVIDE = 331, + SAPP_KEYCODE_KP_MULTIPLY = 332, + SAPP_KEYCODE_KP_SUBTRACT = 333, + SAPP_KEYCODE_KP_ADD = 334, + SAPP_KEYCODE_KP_ENTER = 335, + SAPP_KEYCODE_KP_EQUAL = 336, + SAPP_KEYCODE_LEFT_SHIFT = 340, + SAPP_KEYCODE_LEFT_CONTROL = 341, + SAPP_KEYCODE_LEFT_ALT = 342, + SAPP_KEYCODE_LEFT_SUPER = 343, + SAPP_KEYCODE_RIGHT_SHIFT = 344, + SAPP_KEYCODE_RIGHT_CONTROL = 345, + SAPP_KEYCODE_RIGHT_ALT = 346, + SAPP_KEYCODE_RIGHT_SUPER = 347, + SAPP_KEYCODE_MENU = 348, +} sapp_keycode; + +/* + Android specific 'tool type' enum for touch events. This lets the + application check what type of input device was used for + touch events. + + NOTE: the values must remain in sync with the corresponding + Android SDK type, so don't change those. + + See https://developer.android.com/reference/android/view/MotionEvent#TOOL_TYPE_UNKNOWN +*/ +typedef enum sapp_android_tooltype { + SAPP_ANDROIDTOOLTYPE_UNKNOWN = 0, // TOOL_TYPE_UNKNOWN + SAPP_ANDROIDTOOLTYPE_FINGER = 1, // TOOL_TYPE_FINGER + SAPP_ANDROIDTOOLTYPE_STYLUS = 2, // TOOL_TYPE_STYLUS + SAPP_ANDROIDTOOLTYPE_MOUSE = 3, // TOOL_TYPE_MOUSE +} sapp_android_tooltype; + +/* + sapp_touchpoint + + Describes a single touchpoint in a multitouch event (TOUCHES_BEGAN, + TOUCHES_MOVED, TOUCHES_ENDED). + + Touch points are stored in the nested array sapp_event.touches[], + and the number of touches is stored in sapp_event.num_touches. +*/ +typedef struct sapp_touchpoint { + uintptr_t identifier; + float pos_x; + float pos_y; + sapp_android_tooltype android_tooltype; // only valid on Android + bool changed; +} sapp_touchpoint; + +/* + sapp_mousebutton + + The currently pressed mouse button in the events MOUSE_DOWN + and MOUSE_UP, stored in the struct field sapp_event.mouse_button. +*/ +typedef enum sapp_mousebutton { + SAPP_MOUSEBUTTON_LEFT = 0x0, + SAPP_MOUSEBUTTON_RIGHT = 0x1, + SAPP_MOUSEBUTTON_MIDDLE = 0x2, + SAPP_MOUSEBUTTON_INVALID = 0x100, +} sapp_mousebutton; + +/* + These are currently pressed modifier keys (and mouse buttons) which are + passed in the event struct field sapp_event.modifiers. +*/ +enum { + SAPP_MODIFIER_SHIFT = 0x1, // left or right shift key + SAPP_MODIFIER_CTRL = 0x2, // left or right control key + SAPP_MODIFIER_ALT = 0x4, // left or right alt key + SAPP_MODIFIER_SUPER = 0x8, // left or right 'super' key + SAPP_MODIFIER_LMB = 0x100, // left mouse button + SAPP_MODIFIER_RMB = 0x200, // right mouse button + SAPP_MODIFIER_MMB = 0x400, // middle mouse button +}; + +/* + sapp_event + + This is an all-in-one event struct passed to the event handler + user callback function. Note that it depends on the event + type what struct fields actually contain useful values, so you + should first check the event type before reading other struct + fields. +*/ +typedef struct sapp_event { + uint64_t frame_count; // current frame counter, always valid, useful for checking if two events were issued in the same frame + sapp_event_type type; // the event type, always valid + sapp_keycode key_code; // the virtual key code, only valid in KEY_UP, KEY_DOWN + uint32_t char_code; // the UTF-32 character code, only valid in CHAR events + bool key_repeat; // true if this is a key-repeat event, valid in KEY_UP, KEY_DOWN and CHAR + uint32_t modifiers; // current modifier keys, valid in all key-, char- and mouse-events + sapp_mousebutton mouse_button; // mouse button that was pressed or released, valid in MOUSE_DOWN, MOUSE_UP + float mouse_x; // current horizontal mouse position in pixels, always valid except during mouse lock + float mouse_y; // current vertical mouse position in pixels, always valid except during mouse lock + float mouse_dx; // relative horizontal mouse movement since last frame, always valid + float mouse_dy; // relative vertical mouse movement since last frame, always valid + float scroll_x; // horizontal mouse wheel scroll distance, valid in MOUSE_SCROLL events + float scroll_y; // vertical mouse wheel scroll distance, valid in MOUSE_SCROLL events + int num_touches; // number of valid items in the touches[] array + sapp_touchpoint touches[SAPP_MAX_TOUCHPOINTS]; // current touch points, valid in TOUCHES_BEGIN, TOUCHES_MOVED, TOUCHES_ENDED + int window_width; // current window- and framebuffer sizes in pixels, always valid + int window_height; + int framebuffer_width; // = window_width * dpi_scale + int framebuffer_height; // = window_height * dpi_scale +} sapp_event; + +/* + sg_range + + A general pointer/size-pair struct and constructor macros for passing binary blobs + into sokol_app.h. +*/ +typedef struct sapp_range { + const void* ptr; + size_t size; +} sapp_range; +// disabling this for every includer isn't great, but the warnings are also quite pointless +#if defined(_MSC_VER) +#pragma warning(disable:4221) /* /W4 only: nonstandard extension used: 'x': cannot be initialized using address of automatic variable 'y' */ +#pragma warning(disable:4204) /* VS2015: nonstandard extension used: non-constant aggregate initializer */ +#endif +#if defined(__cplusplus) +#define SAPP_RANGE(x) sapp_range{ &x, sizeof(x) } +#else +#define SAPP_RANGE(x) (sapp_range){ &x, sizeof(x) } +#endif + +/* + sapp_image_desc + + This is used to describe image data to sokol_app.h (window icons and cursor images). + + The pixel format is RGBA8. + + cursor_hotspot_x and _y are used only for cursors, to define which pixel + of the image should be aligned with the mouse position. +*/ +typedef struct sapp_image_desc { + int width; + int height; + int cursor_hotspot_x; + int cursor_hotspot_y; + sapp_range pixels; +} sapp_image_desc; + +/* + sapp_icon_desc + + An icon description structure for use in sapp_desc.icon and + sapp_set_icon(). + + When setting a custom image, the application can provide a number of + candidates differing in size, and sokol_app.h will pick the image(s) + closest to the size expected by the platform's window system. + + To set sokol-app's default icon, set .sokol_default to true. + + Otherwise provide candidate images of different sizes in the + images[] array. + + If both the sokol_default flag is set to true, any image candidates + will be ignored and the sokol_app.h default icon will be set. +*/ +typedef struct sapp_icon_desc { + bool sokol_default; + sapp_image_desc images[SAPP_MAX_ICONIMAGES]; +} sapp_icon_desc; + +/* + sapp_allocator + + Used in sapp_desc to provide custom memory-alloc and -free functions + to sokol_app.h. If memory management should be overridden, both the + alloc_fn and free_fn function must be provided (e.g. it's not valid to + override one function but not the other). +*/ +typedef struct sapp_allocator { + void* (*alloc_fn)(size_t size, void* user_data); + void (*free_fn)(void* ptr, void* user_data); + void* user_data; +} sapp_allocator; + +/* + sapp_log_item + + Log items are defined via X-Macros and expanded to an enum + 'sapp_log_item', and in debug mode to corresponding + human readable error messages. +*/ +#define _SAPP_LOG_ITEMS \ + _SAPP_LOGITEM_XMACRO(OK, "Ok") \ + _SAPP_LOGITEM_XMACRO(MALLOC_FAILED, "memory allocation failed") \ + _SAPP_LOGITEM_XMACRO(MACOS_INVALID_NSOPENGL_PROFILE, "macos: invalid NSOpenGLProfile (valid choices are 1.0 and 4.1)") \ + _SAPP_LOGITEM_XMACRO(WIN32_LOAD_OPENGL32_DLL_FAILED, "failed loading opengl32.dll") \ + _SAPP_LOGITEM_XMACRO(WIN32_CREATE_HELPER_WINDOW_FAILED, "failed to create helper window") \ + _SAPP_LOGITEM_XMACRO(WIN32_HELPER_WINDOW_GETDC_FAILED, "failed to get helper window DC") \ + _SAPP_LOGITEM_XMACRO(WIN32_DUMMY_CONTEXT_SET_PIXELFORMAT_FAILED, "failed to set pixel format for dummy GL context") \ + _SAPP_LOGITEM_XMACRO(WIN32_CREATE_DUMMY_CONTEXT_FAILED, "failed to create dummy GL context") \ + _SAPP_LOGITEM_XMACRO(WIN32_DUMMY_CONTEXT_MAKE_CURRENT_FAILED, "failed to make dummy GL context current") \ + _SAPP_LOGITEM_XMACRO(WIN32_GET_PIXELFORMAT_ATTRIB_FAILED, "failed to get WGL pixel format attribute") \ + _SAPP_LOGITEM_XMACRO(WIN32_WGL_FIND_PIXELFORMAT_FAILED, "failed to find matching WGL pixel format") \ + _SAPP_LOGITEM_XMACRO(WIN32_WGL_DESCRIBE_PIXELFORMAT_FAILED, "failed to get pixel format descriptor") \ + _SAPP_LOGITEM_XMACRO(WIN32_WGL_SET_PIXELFORMAT_FAILED, "failed to set selected pixel format") \ + _SAPP_LOGITEM_XMACRO(WIN32_WGL_ARB_CREATE_CONTEXT_REQUIRED, "ARB_create_context required") \ + _SAPP_LOGITEM_XMACRO(WIN32_WGL_ARB_CREATE_CONTEXT_PROFILE_REQUIRED, "ARB_create_context_profile required") \ + _SAPP_LOGITEM_XMACRO(WIN32_WGL_OPENGL_VERSION_NOT_SUPPORTED, "requested OpenGL version not supported by GL driver (ERROR_INVALID_VERSION_ARB)") \ + _SAPP_LOGITEM_XMACRO(WIN32_WGL_OPENGL_PROFILE_NOT_SUPPORTED, "requested OpenGL profile not support by GL driver (ERROR_INVALID_PROFILE_ARB)") \ + _SAPP_LOGITEM_XMACRO(WIN32_WGL_INCOMPATIBLE_DEVICE_CONTEXT, "CreateContextAttribsARB failed with ERROR_INCOMPATIBLE_DEVICE_CONTEXTS_ARB") \ + _SAPP_LOGITEM_XMACRO(WIN32_WGL_CREATE_CONTEXT_ATTRIBS_FAILED_OTHER, "CreateContextAttribsARB failed for other reason") \ + _SAPP_LOGITEM_XMACRO(WIN32_D3D11_CREATE_DEVICE_AND_SWAPCHAIN_WITH_DEBUG_FAILED, "D3D11CreateDeviceAndSwapChain() with D3D11_CREATE_DEVICE_DEBUG failed, retrying without debug flag.") \ + _SAPP_LOGITEM_XMACRO(WIN32_D3D11_GET_IDXGIFACTORY_FAILED, "could not obtain IDXGIFactory object") \ + _SAPP_LOGITEM_XMACRO(WIN32_D3D11_GET_IDXGIADAPTER_FAILED, "could not obtain IDXGIAdapter object") \ + _SAPP_LOGITEM_XMACRO(WIN32_D3D11_QUERY_INTERFACE_IDXGIDEVICE1_FAILED, "could not obtain IDXGIDevice1 interface") \ + _SAPP_LOGITEM_XMACRO(WIN32_REGISTER_RAW_INPUT_DEVICES_FAILED_MOUSE_LOCK, "RegisterRawInputDevices() failed (on mouse lock)") \ + _SAPP_LOGITEM_XMACRO(WIN32_REGISTER_RAW_INPUT_DEVICES_FAILED_MOUSE_UNLOCK, "RegisterRawInputDevices() failed (on mouse unlock)") \ + _SAPP_LOGITEM_XMACRO(WIN32_GET_RAW_INPUT_DATA_FAILED, "GetRawInputData() failed") \ + _SAPP_LOGITEM_XMACRO(WIN32_DESTROYICON_FOR_CURSOR_FAILED, "DestroyIcon() for a cursor image failed") \ + _SAPP_LOGITEM_XMACRO(LINUX_GLX_LOAD_LIBGL_FAILED, "failed to load libGL") \ + _SAPP_LOGITEM_XMACRO(LINUX_GLX_LOAD_ENTRY_POINTS_FAILED, "failed to load GLX entry points") \ + _SAPP_LOGITEM_XMACRO(LINUX_GLX_EXTENSION_NOT_FOUND, "GLX extension not found") \ + _SAPP_LOGITEM_XMACRO(LINUX_GLX_QUERY_VERSION_FAILED, "failed to query GLX version") \ + _SAPP_LOGITEM_XMACRO(LINUX_GLX_VERSION_TOO_LOW, "GLX version too low (need at least 1.3)") \ + _SAPP_LOGITEM_XMACRO(LINUX_GLX_NO_GLXFBCONFIGS, "glXGetFBConfigs() returned no configs") \ + _SAPP_LOGITEM_XMACRO(LINUX_GLX_NO_SUITABLE_GLXFBCONFIG, "failed to find a suitable GLXFBConfig") \ + _SAPP_LOGITEM_XMACRO(LINUX_GLX_GET_VISUAL_FROM_FBCONFIG_FAILED, "glXGetVisualFromFBConfig failed") \ + _SAPP_LOGITEM_XMACRO(LINUX_GLX_REQUIRED_EXTENSIONS_MISSING, "GLX extensions ARB_create_context and ARB_create_context_profile missing") \ + _SAPP_LOGITEM_XMACRO(LINUX_GLX_CREATE_CONTEXT_FAILED, "Failed to create GL context via glXCreateContextAttribsARB") \ + _SAPP_LOGITEM_XMACRO(LINUX_GLX_CREATE_WINDOW_FAILED, "glXCreateWindow() failed") \ + _SAPP_LOGITEM_XMACRO(LINUX_X11_CREATE_WINDOW_FAILED, "XCreateWindow() failed") \ + _SAPP_LOGITEM_XMACRO(LINUX_EGL_BIND_OPENGL_API_FAILED, "eglBindAPI(EGL_OPENGL_API) failed") \ + _SAPP_LOGITEM_XMACRO(LINUX_EGL_BIND_OPENGL_ES_API_FAILED, "eglBindAPI(EGL_OPENGL_ES_API) failed") \ + _SAPP_LOGITEM_XMACRO(LINUX_EGL_GET_DISPLAY_FAILED, "eglGetDisplay() failed") \ + _SAPP_LOGITEM_XMACRO(LINUX_EGL_INITIALIZE_FAILED, "eglInitialize() failed") \ + _SAPP_LOGITEM_XMACRO(LINUX_EGL_NO_CONFIGS, "eglChooseConfig() returned no configs") \ + _SAPP_LOGITEM_XMACRO(LINUX_EGL_NO_NATIVE_VISUAL, "eglGetConfigAttrib() for EGL_NATIVE_VISUAL_ID failed") \ + _SAPP_LOGITEM_XMACRO(LINUX_EGL_GET_VISUAL_INFO_FAILED, "XGetVisualInfo() failed") \ + _SAPP_LOGITEM_XMACRO(LINUX_EGL_CREATE_WINDOW_SURFACE_FAILED, "eglCreateWindowSurface() failed") \ + _SAPP_LOGITEM_XMACRO(LINUX_EGL_CREATE_CONTEXT_FAILED, "eglCreateContext() failed") \ + _SAPP_LOGITEM_XMACRO(LINUX_EGL_MAKE_CURRENT_FAILED, "eglMakeCurrent() failed") \ + _SAPP_LOGITEM_XMACRO(LINUX_X11_OPEN_DISPLAY_FAILED, "XOpenDisplay() failed") \ + _SAPP_LOGITEM_XMACRO(LINUX_X11_QUERY_SYSTEM_DPI_FAILED, "failed to query system dpi value, assuming default 96.0") \ + _SAPP_LOGITEM_XMACRO(LINUX_X11_DROPPED_FILE_URI_WRONG_SCHEME, "dropped file URL doesn't start with 'file://'") \ + _SAPP_LOGITEM_XMACRO(LINUX_X11_FAILED_TO_BECOME_OWNER_OF_CLIPBOARD, "X11: Failed to become owner of clipboard selection") \ + _SAPP_LOGITEM_XMACRO(ANDROID_UNSUPPORTED_INPUT_EVENT_INPUT_CB, "unsupported input event encountered in _sapp_android_input_cb()") \ + _SAPP_LOGITEM_XMACRO(ANDROID_UNSUPPORTED_INPUT_EVENT_MAIN_CB, "unsupported input event encountered in _sapp_android_main_cb()") \ + _SAPP_LOGITEM_XMACRO(ANDROID_READ_MSG_FAILED, "failed to read message in _sapp_android_main_cb()") \ + _SAPP_LOGITEM_XMACRO(ANDROID_WRITE_MSG_FAILED, "failed to write message in _sapp_android_msg") \ + _SAPP_LOGITEM_XMACRO(ANDROID_MSG_CREATE, "MSG_CREATE") \ + _SAPP_LOGITEM_XMACRO(ANDROID_MSG_RESUME, "MSG_RESUME") \ + _SAPP_LOGITEM_XMACRO(ANDROID_MSG_PAUSE, "MSG_PAUSE") \ + _SAPP_LOGITEM_XMACRO(ANDROID_MSG_FOCUS, "MSG_FOCUS") \ + _SAPP_LOGITEM_XMACRO(ANDROID_MSG_NO_FOCUS, "MSG_NO_FOCUS") \ + _SAPP_LOGITEM_XMACRO(ANDROID_MSG_SET_NATIVE_WINDOW, "MSG_SET_NATIVE_WINDOW") \ + _SAPP_LOGITEM_XMACRO(ANDROID_MSG_SET_INPUT_QUEUE, "MSG_SET_INPUT_QUEUE") \ + _SAPP_LOGITEM_XMACRO(ANDROID_MSG_DESTROY, "MSG_DESTROY") \ + _SAPP_LOGITEM_XMACRO(ANDROID_UNKNOWN_MSG, "unknown msg type received") \ + _SAPP_LOGITEM_XMACRO(ANDROID_LOOP_THREAD_STARTED, "loop thread started") \ + _SAPP_LOGITEM_XMACRO(ANDROID_LOOP_THREAD_DONE, "loop thread done") \ + _SAPP_LOGITEM_XMACRO(ANDROID_NATIVE_ACTIVITY_ONSTART, "NativeActivity onStart()") \ + _SAPP_LOGITEM_XMACRO(ANDROID_NATIVE_ACTIVITY_ONRESUME, "NativeActivity onResume") \ + _SAPP_LOGITEM_XMACRO(ANDROID_NATIVE_ACTIVITY_ONSAVEINSTANCESTATE, "NativeActivity onSaveInstanceState") \ + _SAPP_LOGITEM_XMACRO(ANDROID_NATIVE_ACTIVITY_ONWINDOWFOCUSCHANGED, "NativeActivity onWindowFocusChanged") \ + _SAPP_LOGITEM_XMACRO(ANDROID_NATIVE_ACTIVITY_ONPAUSE, "NativeActivity onPause") \ + _SAPP_LOGITEM_XMACRO(ANDROID_NATIVE_ACTIVITY_ONSTOP, "NativeActivity onStop()") \ + _SAPP_LOGITEM_XMACRO(ANDROID_NATIVE_ACTIVITY_ONNATIVEWINDOWCREATED, "NativeActivity onNativeWindowCreated") \ + _SAPP_LOGITEM_XMACRO(ANDROID_NATIVE_ACTIVITY_ONNATIVEWINDOWDESTROYED, "NativeActivity onNativeWindowDestroyed") \ + _SAPP_LOGITEM_XMACRO(ANDROID_NATIVE_ACTIVITY_ONINPUTQUEUECREATED, "NativeActivity onInputQueueCreated") \ + _SAPP_LOGITEM_XMACRO(ANDROID_NATIVE_ACTIVITY_ONINPUTQUEUEDESTROYED, "NativeActivity onInputQueueDestroyed") \ + _SAPP_LOGITEM_XMACRO(ANDROID_NATIVE_ACTIVITY_ONCONFIGURATIONCHANGED, "NativeActivity onConfigurationChanged") \ + _SAPP_LOGITEM_XMACRO(ANDROID_NATIVE_ACTIVITY_ONLOWMEMORY, "NativeActivity onLowMemory") \ + _SAPP_LOGITEM_XMACRO(ANDROID_NATIVE_ACTIVITY_ONDESTROY, "NativeActivity onDestroy") \ + _SAPP_LOGITEM_XMACRO(ANDROID_NATIVE_ACTIVITY_DONE, "NativeActivity done") \ + _SAPP_LOGITEM_XMACRO(ANDROID_NATIVE_ACTIVITY_ONCREATE, "NativeActivity onCreate") \ + _SAPP_LOGITEM_XMACRO(ANDROID_CREATE_THREAD_PIPE_FAILED, "failed to create thread pipe") \ + _SAPP_LOGITEM_XMACRO(ANDROID_NATIVE_ACTIVITY_CREATE_SUCCESS, "NativeActivity successfully created") \ + _SAPP_LOGITEM_XMACRO(WGPU_DEVICE_LOST, "wgpu: device lost") \ + _SAPP_LOGITEM_XMACRO(WGPU_DEVICE_LOG, "wgpu: device log") \ + _SAPP_LOGITEM_XMACRO(WGPU_DEVICE_UNCAPTURED_ERROR, "wgpu: uncaptured error") \ + _SAPP_LOGITEM_XMACRO(WGPU_SWAPCHAIN_CREATE_SURFACE_FAILED, "wgpu: failed to create surface for swapchain") \ + _SAPP_LOGITEM_XMACRO(WGPU_SWAPCHAIN_SURFACE_GET_CAPABILITIES_FAILED, "wgpu: wgpuSurfaceGetCapabilities failed") \ + _SAPP_LOGITEM_XMACRO(WGPU_SWAPCHAIN_CREATE_DEPTH_STENCIL_TEXTURE_FAILED, "wgpu: failed to create depth-stencil texture for swapchain") \ + _SAPP_LOGITEM_XMACRO(WGPU_SWAPCHAIN_CREATE_DEPTH_STENCIL_VIEW_FAILED, "wgpu: failed to create view object for swapchain depth-stencil texture") \ + _SAPP_LOGITEM_XMACRO(WGPU_SWAPCHAIN_CREATE_MSAA_TEXTURE_FAILED, "wgpu: failed to create msaa texture for swapchain") \ + _SAPP_LOGITEM_XMACRO(WGPU_SWAPCHAIN_CREATE_MSAA_VIEW_FAILED, "wgpu: failed to create view object for swapchain msaa texture") \ + _SAPP_LOGITEM_XMACRO(WGPU_SWAPCHAIN_GETCURRENTTEXTURE_FAILED, "wgpu: wgpuSurfaceGetCurrentTexture() failed") \ + _SAPP_LOGITEM_XMACRO(WGPU_REQUEST_DEVICE_STATUS_ERROR, "wgpu: requesting device failed with status 'error'") \ + _SAPP_LOGITEM_XMACRO(WGPU_REQUEST_DEVICE_STATUS_UNKNOWN, "wgpu: requesting device failed with status 'unknown'") \ + _SAPP_LOGITEM_XMACRO(WGPU_REQUEST_ADAPTER_STATUS_UNAVAILABLE, "wgpu: requesting adapter failed with 'unavailable'") \ + _SAPP_LOGITEM_XMACRO(WGPU_REQUEST_ADAPTER_STATUS_ERROR, "wgpu: requesting adapter failed with status 'error'") \ + _SAPP_LOGITEM_XMACRO(WGPU_REQUEST_ADAPTER_STATUS_UNKNOWN, "wgpu: requesting adapter failed with status 'unknown'") \ + _SAPP_LOGITEM_XMACRO(WGPU_CREATE_INSTANCE_FAILED, "wgpu: failed to create instance") \ + _SAPP_LOGITEM_XMACRO(IMAGE_DATA_SIZE_MISMATCH, "image data size mismatch (must be width*height*4 bytes)") \ + _SAPP_LOGITEM_XMACRO(DROPPED_FILE_PATH_TOO_LONG, "dropped file path too long (sapp_desc.max_dropped_filed_path_length)") \ + _SAPP_LOGITEM_XMACRO(CLIPBOARD_STRING_TOO_BIG, "clipboard string didn't fit into clipboard buffer") \ + +#define _SAPP_LOGITEM_XMACRO(item,msg) SAPP_LOGITEM_##item, +typedef enum sapp_log_item { + _SAPP_LOG_ITEMS +} sapp_log_item; +#undef _SAPP_LOGITEM_XMACRO + +/* + sapp_logger + + Used in sapp_desc to provide a logging function. Please be aware that + without logging function, sokol-app will be completely silent, e.g. it will + not report errors or warnings. For maximum error verbosity, compile in + debug mode (e.g. NDEBUG *not* defined) and install a logger (for instance + the standard logging function from sokol_log.h). +*/ +typedef struct sapp_logger { + void (*func)( + const char* tag, // always "sapp" + uint32_t log_level, // 0=panic, 1=error, 2=warning, 3=info + uint32_t log_item_id, // SAPP_LOGITEM_* + const char* message_or_null, // a message string, may be nullptr in release mode + uint32_t line_nr, // line number in sokol_app.h + const char* filename_or_null, // source filename, may be nullptr in release mode + void* user_data); + void* user_data; +} sapp_logger; + +/* + sokol-app initialization options, used as return value of sokol_main() + or sapp_run() argument. +*/ +typedef struct sapp_desc { + void (*init_cb)(void); // these are the user-provided callbacks without user data + void (*frame_cb)(void); + void (*cleanup_cb)(void); + void (*event_cb)(const sapp_event*); + + void* user_data; // these are the user-provided callbacks with user data + void (*init_userdata_cb)(void*); + void (*frame_userdata_cb)(void*); + void (*cleanup_userdata_cb)(void*); + void (*event_userdata_cb)(const sapp_event*, void*); + + int width; // the preferred width of the window / canvas + int height; // the preferred height of the window / canvas + int sample_count; // MSAA sample count + int swap_interval; // the preferred swap interval (ignored on some platforms) + bool high_dpi; // whether the rendering canvas is full-resolution on HighDPI displays + bool fullscreen; // whether the window should be created in fullscreen mode + bool alpha; // whether the framebuffer should have an alpha channel (ignored on some platforms) + const char* window_title; // the window title as UTF-8 encoded string + bool enable_clipboard; // enable clipboard access, default is false + int clipboard_size; // max size of clipboard content in bytes + bool enable_dragndrop; // enable file dropping (drag'n'drop), default is false + int max_dropped_files; // max number of dropped files to process (default: 1) + int max_dropped_file_path_length; // max length in bytes of a dropped UTF-8 file path (default: 2048) + sapp_icon_desc icon; // the initial window icon to set + sapp_allocator allocator; // optional memory allocation overrides (default: malloc/free) + sapp_logger logger; // logging callback override (default: NO LOGGING!) + + // backend-specific options + int gl_major_version; // override GL/GLES major and minor version (defaults: GL4.1 (macOS) or GL4.3, GLES3.1 (Android) or GLES3.0 + int gl_minor_version; + bool win32_console_utf8; // if true, set the output console codepage to UTF-8 + bool win32_console_create; // if true, attach stdout/stderr to a new console window + bool win32_console_attach; // if true, attach stdout/stderr to parent process + const char* html5_canvas_selector; // css selector of the HTML5 canvas element, default is "#canvas" + bool html5_canvas_resize; // if true, the HTML5 canvas size is set to sapp_desc.width/height, otherwise canvas size is tracked + bool html5_preserve_drawing_buffer; // HTML5 only: whether to preserve default framebuffer content between frames + bool html5_premultiplied_alpha; // HTML5 only: whether the rendered pixels use premultiplied alpha convention + bool html5_ask_leave_site; // initial state of the internal html5_ask_leave_site flag (see sapp_html5_ask_leave_site()) + bool html5_update_document_title; // if true, update the HTML document.title with sapp_desc.window_title + bool html5_bubble_mouse_events; // if true, mouse events will bubble up to the web page + bool html5_bubble_touch_events; // same for touch events + bool html5_bubble_wheel_events; // same for wheel events + bool html5_bubble_key_events; // if true, bubble up *all* key events to browser, not just key events that represent characters + bool html5_bubble_char_events; // if true, bubble up character events to browser + bool html5_use_emsc_set_main_loop; // if true, use emscripten_set_main_loop() instead of emscripten_request_animation_frame_loop() + bool html5_emsc_set_main_loop_simulate_infinite_loop; // this will be passed as the simulate_infinite_loop arg to emscripten_set_main_loop() + bool ios_keyboard_resizes_canvas; // if true, showing the iOS keyboard shrinks the canvas +} sapp_desc; + +/* HTML5 specific: request and response structs for + asynchronously loading dropped-file content. +*/ +typedef enum sapp_html5_fetch_error { + SAPP_HTML5_FETCH_ERROR_NO_ERROR, + SAPP_HTML5_FETCH_ERROR_BUFFER_TOO_SMALL, + SAPP_HTML5_FETCH_ERROR_OTHER, +} sapp_html5_fetch_error; + +typedef struct sapp_html5_fetch_response { + bool succeeded; // true if the loading operation has succeeded + sapp_html5_fetch_error error_code; + int file_index; // index of the dropped file (0..sapp_get_num_dropped_filed()-1) + sapp_range data; // pointer and size of the fetched data (data.ptr == buffer.ptr, data.size <= buffer.size) + sapp_range buffer; // the user-provided buffer ptr/size pair (buffer.ptr == data.ptr, buffer.size >= data.size) + void* user_data; // user-provided user data pointer +} sapp_html5_fetch_response; + +typedef struct sapp_html5_fetch_request { + int dropped_file_index; // 0..sapp_get_num_dropped_files()-1 + void (*callback)(const sapp_html5_fetch_response*); // response callback function pointer (required) + sapp_range buffer; // ptr/size of a memory buffer to load the data into + void* user_data; // optional userdata pointer +} sapp_html5_fetch_request; + +/* + sapp_mouse_cursor + + Predefined cursor image definitions, set with sapp_set_mouse_cursor(sapp_mouse_cursor cursor) +*/ +typedef enum sapp_mouse_cursor { + SAPP_MOUSECURSOR_DEFAULT = 0, // equivalent with system default cursor + SAPP_MOUSECURSOR_ARROW, + SAPP_MOUSECURSOR_IBEAM, + SAPP_MOUSECURSOR_CROSSHAIR, + SAPP_MOUSECURSOR_POINTING_HAND, + SAPP_MOUSECURSOR_RESIZE_EW, + SAPP_MOUSECURSOR_RESIZE_NS, + SAPP_MOUSECURSOR_RESIZE_NWSE, + SAPP_MOUSECURSOR_RESIZE_NESW, + SAPP_MOUSECURSOR_RESIZE_ALL, + SAPP_MOUSECURSOR_NOT_ALLOWED, + SAPP_MOUSECURSOR_CUSTOM_0, + SAPP_MOUSECURSOR_CUSTOM_1, + SAPP_MOUSECURSOR_CUSTOM_2, + SAPP_MOUSECURSOR_CUSTOM_3, + SAPP_MOUSECURSOR_CUSTOM_4, + SAPP_MOUSECURSOR_CUSTOM_5, + SAPP_MOUSECURSOR_CUSTOM_6, + SAPP_MOUSECURSOR_CUSTOM_7, + SAPP_MOUSECURSOR_CUSTOM_8, + SAPP_MOUSECURSOR_CUSTOM_9, + SAPP_MOUSECURSOR_CUSTOM_10, + SAPP_MOUSECURSOR_CUSTOM_11, + SAPP_MOUSECURSOR_CUSTOM_12, + SAPP_MOUSECURSOR_CUSTOM_13, + SAPP_MOUSECURSOR_CUSTOM_14, + SAPP_MOUSECURSOR_CUSTOM_15, + _SAPP_MOUSECURSOR_NUM, +} sapp_mouse_cursor; + +/* user-provided functions */ +extern sapp_desc sokol_main(int argc, char* argv[]); + +/* returns true after sokol-app has been initialized */ +SOKOL_APP_API_DECL bool sapp_isvalid(void); +/* returns the current framebuffer width in pixels */ +SOKOL_APP_API_DECL int sapp_width(void); +/* same as sapp_width(), but returns float */ +SOKOL_APP_API_DECL float sapp_widthf(void); +/* returns the current framebuffer height in pixels */ +SOKOL_APP_API_DECL int sapp_height(void); +/* same as sapp_height(), but returns float */ +SOKOL_APP_API_DECL float sapp_heightf(void); +/* get default framebuffer color pixel format */ +SOKOL_APP_API_DECL int sapp_color_format(void); +/* get default framebuffer depth pixel format */ +SOKOL_APP_API_DECL int sapp_depth_format(void); +/* get default framebuffer sample count */ +SOKOL_APP_API_DECL int sapp_sample_count(void); +/* returns true when high_dpi was requested and actually running in a high-dpi scenario */ +SOKOL_APP_API_DECL bool sapp_high_dpi(void); +/* returns the dpi scaling factor (window pixels to framebuffer pixels) */ +SOKOL_APP_API_DECL float sapp_dpi_scale(void); +/* show or hide the mobile device onscreen keyboard */ +SOKOL_APP_API_DECL void sapp_show_keyboard(bool show); +/* return true if the mobile device onscreen keyboard is currently shown */ +SOKOL_APP_API_DECL bool sapp_keyboard_shown(void); +/* query fullscreen mode */ +SOKOL_APP_API_DECL bool sapp_is_fullscreen(void); +/* toggle fullscreen mode */ +SOKOL_APP_API_DECL void sapp_toggle_fullscreen(void); +/* show or hide the mouse cursor */ +SOKOL_APP_API_DECL void sapp_show_mouse(bool show); +/* show or hide the mouse cursor */ +SOKOL_APP_API_DECL bool sapp_mouse_shown(void); +/* enable/disable mouse-pointer-lock mode */ +SOKOL_APP_API_DECL void sapp_lock_mouse(bool lock); +/* return true if in mouse-pointer-lock mode (this may toggle a few frames later) */ +SOKOL_APP_API_DECL bool sapp_mouse_locked(void); +/* set mouse cursor type */ +SOKOL_APP_API_DECL void sapp_set_mouse_cursor(sapp_mouse_cursor cursor); +/* get current mouse cursor type */ +SOKOL_APP_API_DECL sapp_mouse_cursor sapp_get_mouse_cursor(void); +/* associate a custom mouse cursor image to a sapp_mouse_cursor enum entry */ +SOKOL_APP_API_DECL sapp_mouse_cursor sapp_bind_mouse_cursor_image(sapp_mouse_cursor cursor, const sapp_image_desc* desc); +/* restore the sapp_mouse_cursor enum entry to it's default system appearance */ +SOKOL_APP_API_DECL void sapp_unbind_mouse_cursor_image(sapp_mouse_cursor cursor); +/* return the userdata pointer optionally provided in sapp_desc */ +SOKOL_APP_API_DECL void* sapp_userdata(void); +/* return a copy of the sapp_desc structure */ +SOKOL_APP_API_DECL sapp_desc sapp_query_desc(void); +/* initiate a "soft quit" (sends SAPP_EVENTTYPE_QUIT_REQUESTED) */ +SOKOL_APP_API_DECL void sapp_request_quit(void); +/* cancel a pending quit (when SAPP_EVENTTYPE_QUIT_REQUESTED has been received) */ +SOKOL_APP_API_DECL void sapp_cancel_quit(void); +/* initiate a "hard quit" (quit application without sending SAPP_EVENTTYPE_QUIT_REQUESTED) */ +SOKOL_APP_API_DECL void sapp_quit(void); +/* call from inside event callback to consume the current event (don't forward to platform) */ +SOKOL_APP_API_DECL void sapp_consume_event(void); +/* get the current frame counter (for comparison with sapp_event.frame_count) */ +SOKOL_APP_API_DECL uint64_t sapp_frame_count(void); +/* get an averaged/smoothed frame duration in seconds */ +SOKOL_APP_API_DECL double sapp_frame_duration(void); +/* write string into clipboard */ +SOKOL_APP_API_DECL void sapp_set_clipboard_string(const char* str); +/* read string from clipboard (usually during SAPP_EVENTTYPE_CLIPBOARD_PASTED) */ +SOKOL_APP_API_DECL const char* sapp_get_clipboard_string(void); +/* set the window title (only on desktop platforms) */ +SOKOL_APP_API_DECL void sapp_set_window_title(const char* str); +/* set the window icon (only on Windows and Linux) */ +SOKOL_APP_API_DECL void sapp_set_icon(const sapp_icon_desc* icon_desc); +/* gets the total number of dropped files (after an SAPP_EVENTTYPE_FILES_DROPPED event) */ +SOKOL_APP_API_DECL int sapp_get_num_dropped_files(void); +/* gets the dropped file paths */ +SOKOL_APP_API_DECL const char* sapp_get_dropped_file_path(int index); + +/* special run-function for SOKOL_NO_ENTRY (in standard mode this is an empty stub) */ +SOKOL_APP_API_DECL void sapp_run(const sapp_desc* desc); + +/* EGL: get EGLDisplay object */ +SOKOL_APP_API_DECL const void* sapp_egl_get_display(void); +/* EGL: get EGLContext object */ +SOKOL_APP_API_DECL const void* sapp_egl_get_context(void); + +/* HTML5: enable or disable the hardwired "Leave Site?" dialog box */ +SOKOL_APP_API_DECL void sapp_html5_ask_leave_site(bool ask); +/* HTML5: get byte size of a dropped file */ +SOKOL_APP_API_DECL uint32_t sapp_html5_get_dropped_file_size(int index); +/* HTML5: asynchronously load the content of a dropped file */ +SOKOL_APP_API_DECL void sapp_html5_fetch_dropped_file(const sapp_html5_fetch_request* request); + +/* Metal: get bridged pointer to Metal device object */ +SOKOL_APP_API_DECL const void* sapp_metal_get_device(void); +/* Metal: get bridged pointer to MTKView's current drawable of type CAMetalDrawable */ +SOKOL_APP_API_DECL const void* sapp_metal_get_current_drawable(void); +/* Metal: get bridged pointer to MTKView's depth-stencil texture of type MTLTexture */ +SOKOL_APP_API_DECL const void* sapp_metal_get_depth_stencil_texture(void); +/* Metal: get bridged pointer to MTKView's msaa-color-texture of type MTLTexture (may be null) */ +SOKOL_APP_API_DECL const void* sapp_metal_get_msaa_color_texture(void); +/* macOS: get bridged pointer to macOS NSWindow */ +SOKOL_APP_API_DECL const void* sapp_macos_get_window(void); +/* iOS: get bridged pointer to iOS UIWindow */ +SOKOL_APP_API_DECL const void* sapp_ios_get_window(void); + +/* D3D11: get pointer to ID3D11Device object */ +SOKOL_APP_API_DECL const void* sapp_d3d11_get_device(void); +/* D3D11: get pointer to ID3D11DeviceContext object */ +SOKOL_APP_API_DECL const void* sapp_d3d11_get_device_context(void); +/* D3D11: get pointer to IDXGISwapChain object */ +SOKOL_APP_API_DECL const void* sapp_d3d11_get_swap_chain(void); +/* D3D11: get pointer to ID3D11RenderTargetView object for rendering */ +SOKOL_APP_API_DECL const void* sapp_d3d11_get_render_view(void); +/* D3D11: get pointer ID3D11RenderTargetView object for msaa-resolve (may return null) */ +SOKOL_APP_API_DECL const void* sapp_d3d11_get_resolve_view(void); +/* D3D11: get pointer ID3D11DepthStencilView */ +SOKOL_APP_API_DECL const void* sapp_d3d11_get_depth_stencil_view(void); +/* Win32: get the HWND window handle */ +SOKOL_APP_API_DECL const void* sapp_win32_get_hwnd(void); + +/* WebGPU: get WGPUDevice handle */ +SOKOL_APP_API_DECL const void* sapp_wgpu_get_device(void); +/* WebGPU: get swapchain's WGPUTextureView handle for rendering */ +SOKOL_APP_API_DECL const void* sapp_wgpu_get_render_view(void); +/* WebGPU: get swapchain's MSAA-resolve WGPUTextureView (may return null) */ +SOKOL_APP_API_DECL const void* sapp_wgpu_get_resolve_view(void); +/* WebGPU: get swapchain's WGPUTextureView for the depth-stencil surface */ +SOKOL_APP_API_DECL const void* sapp_wgpu_get_depth_stencil_view(void); + +/* GL: get framebuffer object */ +SOKOL_APP_API_DECL uint32_t sapp_gl_get_framebuffer(void); +/* GL: get major version */ +SOKOL_APP_API_DECL int sapp_gl_get_major_version(void); +/* GL: get minor version */ +SOKOL_APP_API_DECL int sapp_gl_get_minor_version(void); +/* GL: return true if the context is GLES */ +SOKOL_APP_API_DECL bool sapp_gl_is_gles(void); + +/* X11: get Window */ +SOKOL_APP_API_DECL const void* sapp_x11_get_window(void); +/* X11: get Display */ +SOKOL_APP_API_DECL const void* sapp_x11_get_display(void); + +/* Android: get native activity handle */ +SOKOL_APP_API_DECL const void* sapp_android_get_native_activity(void); + +#ifdef __cplusplus +} /* extern "C" */ + +/* reference-based equivalents for C++ */ +inline void sapp_run(const sapp_desc& desc) { return sapp_run(&desc); } + +#endif + +#endif // SOKOL_APP_INCLUDED + +// ██ ███ ███ ██████ ██ ███████ ███ ███ ███████ ███ ██ ████████ █████ ████████ ██ ██████ ███ ██ +// ██ ████ ████ ██ ██ ██ ██ ████ ████ ██ ████ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██ +// ██ ██ ████ ██ ██████ ██ █████ ██ ████ ██ █████ ██ ██ ██ ██ ███████ ██ ██ ██ ██ ██ ██ ██ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██ ██ ██ ██ ███████ ███████ ██ ██ ███████ ██ ████ ██ ██ ██ ██ ██ ██████ ██ ████ +// +// >>implementation +#ifdef SOKOL_APP_IMPL +#define SOKOL_APP_IMPL_INCLUDED (1) + +#if defined(SOKOL_MALLOC) || defined(SOKOL_CALLOC) || defined(SOKOL_FREE) +#error "SOKOL_MALLOC/CALLOC/FREE macros are no longer supported, please use sapp_desc.allocator to override memory allocation functions" +#endif + +#include // malloc, free +#include // memset, strncmp +#include // size_t +#include // roundf + +// helper macros +#define _sapp_def(val, def) (((val) == 0) ? (def) : (val)) +#define _sapp_absf(a) (((a)<0.0f)?-(a):(a)) + +#define _SAPP_MAX_TITLE_LENGTH (128) +#define _SAPP_FALLBACK_DEFAULT_WINDOW_WIDTH (640) +#define _SAPP_FALLBACK_DEFAULT_WINDOW_HEIGHT (480) +// NOTE: the pixel format values *must* be compatible with sg_pixel_format +#define _SAPP_PIXELFORMAT_RGBA8 (23) +#define _SAPP_PIXELFORMAT_BGRA8 (28) +#define _SAPP_PIXELFORMAT_DEPTH (43) +#define _SAPP_PIXELFORMAT_DEPTH_STENCIL (44) + +// check if the config defines are alright +#if defined(__APPLE__) + // see https://clang.llvm.org/docs/LanguageExtensions.html#automatic-reference-counting + #if !defined(__cplusplus) + #if __has_feature(objc_arc) && !__has_feature(objc_arc_fields) + #error "sokol_app.h requires __has_feature(objc_arc_field) if ARC is enabled (use a more recent compiler version)" + #endif + #endif + #define _SAPP_APPLE (1) + #include + #if defined(TARGET_OS_IPHONE) && !TARGET_OS_IPHONE + /* MacOS */ + #define _SAPP_MACOS (1) + #if !defined(SOKOL_METAL) && !defined(SOKOL_GLCORE) && !defined(SOKOL_WGPU) + #error("sokol_app.h: unknown 3D API selected for MacOS, must be SOKOL_METAL, SOKOL_GLCORE or SOKOL_WGPU") + #endif + #else + /* iOS or iOS Simulator */ + #define _SAPP_IOS (1) + #if !defined(SOKOL_METAL) && !defined(SOKOL_GLES3) + #error("sokol_app.h: unknown 3D API selected for iOS, must be SOKOL_METAL or SOKOL_GLES3") + #endif + #if TARGET_OS_TV + #define _SAPP_TVOS (1) + #endif + #endif +#elif defined(__EMSCRIPTEN__) + /* emscripten (asm.js or wasm) */ + #define _SAPP_EMSCRIPTEN (1) + #if !defined(SOKOL_GLES3) && !defined(SOKOL_WGPU) + #error("sokol_app.h: unknown 3D API selected for emscripten, must be SOKOL_GLES3 or SOKOL_WGPU") + #endif +#elif defined(_WIN32) + /* Windows (D3D11 or GL) */ + #define _SAPP_WIN32 (1) + #if !defined(SOKOL_D3D11) && !defined(SOKOL_GLCORE) && !defined(SOKOL_WGPU) && !defined(SOKOL_NOAPI) + #error("sokol_app.h: unknown 3D API selected for Win32, must be SOKOL_D3D11, SOKOL_GLCORE, SOKOL_WGPU or SOKOL_NOAPI") + #endif +#elif defined(__ANDROID__) + /* Android */ + #define _SAPP_ANDROID (1) + #if !defined(SOKOL_GLES3) + #error("sokol_app.h: unknown 3D API selected for Android, must be SOKOL_GLES3") + #endif + #if defined(SOKOL_NO_ENTRY) + #error("sokol_app.h: SOKOL_NO_ENTRY is not supported on Android") + #endif +#elif defined(__linux__) || defined(__unix__) + /* Linux */ + #define _SAPP_LINUX (1) + #if !defined(SOKOL_GLCORE) && !defined(SOKOL_GLES3) && !defined(SOKOL_WGPU) + #error("sokol_app.h: unknown 3D API selected for Linux, must be SOKOL_GLCORE, SOKOL_GLES3 or SOKOL_WGPU") + #endif + #if defined(SOKOL_GLCORE) + #if defined(SOKOL_FORCE_EGL) + #define _SAPP_EGL (1) + #else + #define _SAPP_GLX (1) + #endif + #define GL_GLEXT_PROTOTYPES + #include + #elif defined(SOKOL_GLES3) + #define _SAPP_EGL (1) + #include + #include + #endif +#else +#error "sokol_app.h: Unknown platform" +#endif + +#if defined(SOKOL_GLCORE) || defined(SOKOL_GLES3) + #define _SAPP_ANY_GL (1) +#endif + +#ifndef SOKOL_API_IMPL + #define SOKOL_API_IMPL +#endif +#ifndef SOKOL_DEBUG + #ifndef NDEBUG + #define SOKOL_DEBUG + #endif +#endif +#ifndef SOKOL_ASSERT + #include + #define SOKOL_ASSERT(c) assert(c) +#endif +#ifndef SOKOL_UNREACHABLE + #define SOKOL_UNREACHABLE SOKOL_ASSERT(false) +#endif + +#ifndef _SOKOL_PRIVATE + #if defined(__GNUC__) || defined(__clang__) + #define _SOKOL_PRIVATE __attribute__((unused)) static + #else + #define _SOKOL_PRIVATE static + #endif +#endif +#ifndef _SOKOL_UNUSED + #define _SOKOL_UNUSED(x) (void)(x) +#endif + +#if defined(SOKOL_WGPU) + #include + #if !defined(__EMSCRIPTEN__) + #define _SAPP_WGPU_HAS_WAIT (1) + #endif +#endif + +#if defined(_SAPP_APPLE) + #ifndef GL_SILENCE_DEPRECATION + #define GL_SILENCE_DEPRECATION + #endif + #if defined(SOKOL_METAL) + #import + #import + #endif + #if defined(_SAPP_MACOS) + #import + #if defined(_SAPP_ANY_GL) + #include + #endif + #if defined(SOKOL_WGPU) + #import + #import + #endif + #elif defined(_SAPP_IOS) + #import + #if defined(_SAPP_ANY_GL) + #import + #include + #endif + #endif + #include + #include +#elif defined(_SAPP_EMSCRIPTEN) + #if defined(SOKOL_GLES3) + #include + #endif + #include + #include +#elif defined(_SAPP_WIN32) + #ifdef _MSC_VER + #pragma warning(push) + #pragma warning(disable:4201) /* nonstandard extension used: nameless struct/union */ + #pragma warning(disable:4204) /* nonstandard extension used: non-constant aggregate initializer */ + #pragma warning(disable:4054) /* 'type cast': from function pointer */ + #pragma warning(disable:4055) /* 'type cast': from data pointer */ + #pragma warning(disable:4505) /* unreferenced local function has been removed */ + #pragma warning(disable:4115) /* /W4: 'ID3D11ModuleInstance': named type definition in parentheses (in d3d11.h) */ + #endif + #ifndef WIN32_LEAN_AND_MEAN + #define WIN32_LEAN_AND_MEAN + #endif + #ifndef NOMINMAX + #define NOMINMAX + #endif + #include + #include + #include + + #if defined(__GNUC__) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wunknown-pragmas" + #endif + + #if !defined(SOKOL_NO_ENTRY) // if SOKOL_NO_ENTRY is defined, it's the application's responsibility to use the right subsystem + + #if defined(SOKOL_WIN32_FORCE_MAIN) && defined(SOKOL_WIN32_FORCE_WINMAIN) + // If both are defined, it's the application's responsibility to use the right subsystem + #elif defined(SOKOL_WIN32_FORCE_MAIN) + #pragma comment (linker, "/subsystem:console") + #else + #pragma comment (linker, "/subsystem:windows") + #endif + #endif + #include /* freopen_s() */ + #include /* wcslen() */ + + #pragma comment (lib, "kernel32") + #pragma comment (lib, "user32") + #pragma comment (lib, "shell32") /* CommandLineToArgvW, DragQueryFileW, DragFinished */ + #pragma comment (lib, "gdi32") + #if defined(SOKOL_D3D11) + #pragma comment (lib, "dxgi") + #pragma comment (lib, "d3d11") + #endif + + #if defined(__GNUC__) + #pragma GCC diagnostic pop + #endif + + #if defined(SOKOL_D3D11) + #ifndef D3D11_NO_HELPERS + #define D3D11_NO_HELPERS + #endif + #include + #include + // DXGI_SWAP_EFFECT_FLIP_DISCARD is only defined in newer Windows SDKs, so don't depend on it + #define _SAPP_DXGI_SWAP_EFFECT_FLIP_DISCARD (4) + #endif + #ifndef WM_MOUSEHWHEEL /* see https://github.com/floooh/sokol/issues/138 */ + #define WM_MOUSEHWHEEL (0x020E) + #endif + #ifndef WM_DPICHANGED + #define WM_DPICHANGED (0x02E0) + #endif +#elif defined(_SAPP_ANDROID) + #include + #include + #include + #include + #include + #include + #include +#elif defined(_SAPP_LINUX) + #define GL_GLEXT_PROTOTYPES + #include + #include + #include + #include + #include + #include + #include + #include + #include /* XC_* font cursors */ + #include /* CARD32 */ + #if defined(_SAPP_EGL) + #include + #endif + #include /* dlopen, dlsym, dlclose */ + #include /* LONG_MAX */ + #include /* only used a linker-guard, search for _sapp_linux_run() and see first comment */ + #include + #include +#endif + +#if defined(_SAPP_APPLE) + // this is ARC compatible + #if defined(__cplusplus) + #define _SAPP_CLEAR_ARC_STRUCT(type, item) { item = type(); } + #else + #define _SAPP_CLEAR_ARC_STRUCT(type, item) { item = (type) { 0 }; } + #endif +#else + #define _SAPP_CLEAR_ARC_STRUCT(type, item) { _sapp_clear(&item, sizeof(item)); } +#endif + + +// ███████ ██████ █████ ███ ███ ███████ ████████ ██ ███ ███ ██ ███ ██ ██████ +// ██ ██ ██ ██ ██ ████ ████ ██ ██ ██ ████ ████ ██ ████ ██ ██ +// █████ ██████ ███████ ██ ████ ██ █████ ██ ██ ██ ████ ██ ██ ██ ██ ██ ██ ███ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██ ██ ██ ██ ██ ██ ██ ███████ ██ ██ ██ ██ ██ ██ ████ ██████ +// +// >>frame timing +#define _SAPP_RING_NUM_SLOTS (256) +typedef struct { + int head; + int tail; + double buf[_SAPP_RING_NUM_SLOTS]; +} _sapp_ring_t; + +_SOKOL_PRIVATE int _sapp_ring_idx(int i) { + return i % _SAPP_RING_NUM_SLOTS; +} + +_SOKOL_PRIVATE void _sapp_ring_init(_sapp_ring_t* ring) { + ring->head = 0; + ring->tail = 0; +} + +_SOKOL_PRIVATE bool _sapp_ring_full(_sapp_ring_t* ring) { + return _sapp_ring_idx(ring->head + 1) == ring->tail; +} + +_SOKOL_PRIVATE bool _sapp_ring_empty(_sapp_ring_t* ring) { + return ring->head == ring->tail; +} + +_SOKOL_PRIVATE int _sapp_ring_count(_sapp_ring_t* ring) { + int count; + if (ring->head >= ring->tail) { + count = ring->head - ring->tail; + } else { + count = (ring->head + _SAPP_RING_NUM_SLOTS) - ring->tail; + } + SOKOL_ASSERT((count >= 0) && (count < _SAPP_RING_NUM_SLOTS)); + return count; +} + +_SOKOL_PRIVATE void _sapp_ring_enqueue(_sapp_ring_t* ring, double val) { + SOKOL_ASSERT(!_sapp_ring_full(ring)); + ring->buf[ring->head] = val; + ring->head = _sapp_ring_idx(ring->head + 1); +} + +_SOKOL_PRIVATE double _sapp_ring_dequeue(_sapp_ring_t* ring) { + SOKOL_ASSERT(!_sapp_ring_empty(ring)); + double val = ring->buf[ring->tail]; + ring->tail = _sapp_ring_idx(ring->tail + 1); + return val; +} + +/* + NOTE: + + Q: Why not use CAMetalDrawable.presentedTime on macOS and iOS? + A: The value appears to be highly unstable during the first few + seconds, sometimes several frames are dropped in sequence, or + switch between 120 and 60 Hz for a few frames. Simply measuring + and averaging the frame time yielded a more stable frame duration. + Maybe switching to CVDisplayLink would yield better results. + Until then just measure the time. +*/ +typedef struct { + #if defined(_SAPP_APPLE) + struct { + mach_timebase_info_data_t timebase; + uint64_t start; + } mach; + #elif defined(_SAPP_EMSCRIPTEN) + // empty + #elif defined(_SAPP_WIN32) + struct { + LARGE_INTEGER freq; + LARGE_INTEGER start; + } win; + #else // Linux, Android, ... + #ifdef CLOCK_MONOTONIC + #define _SAPP_CLOCK_MONOTONIC CLOCK_MONOTONIC + #else + // on some embedded platforms, CLOCK_MONOTONIC isn't defined + #define _SAPP_CLOCK_MONOTONIC (1) + #endif + struct { + uint64_t start; + } posix; + #endif +} _sapp_timestamp_t; + +_SOKOL_PRIVATE int64_t _sapp_int64_muldiv(int64_t value, int64_t numer, int64_t denom) { + int64_t q = value / denom; + int64_t r = value % denom; + return q * numer + r * numer / denom; +} + +_SOKOL_PRIVATE void _sapp_timestamp_init(_sapp_timestamp_t* ts) { + #if defined(_SAPP_APPLE) + mach_timebase_info(&ts->mach.timebase); + ts->mach.start = mach_absolute_time(); + #elif defined(_SAPP_EMSCRIPTEN) + (void)ts; + #elif defined(_SAPP_WIN32) + QueryPerformanceFrequency(&ts->win.freq); + QueryPerformanceCounter(&ts->win.start); + #else + struct timespec tspec; + clock_gettime(_SAPP_CLOCK_MONOTONIC, &tspec); + ts->posix.start = (uint64_t)tspec.tv_sec*1000000000 + (uint64_t)tspec.tv_nsec; + #endif +} + +_SOKOL_PRIVATE double _sapp_timestamp_now(_sapp_timestamp_t* ts) { + #if defined(_SAPP_APPLE) + const uint64_t traw = mach_absolute_time() - ts->mach.start; + const uint64_t now = (uint64_t) _sapp_int64_muldiv((int64_t)traw, (int64_t)ts->mach.timebase.numer, (int64_t)ts->mach.timebase.denom); + return (double)now / 1000000000.0; + #elif defined(_SAPP_EMSCRIPTEN) + (void)ts; + SOKOL_ASSERT(false); + return 0.0; + #elif defined(_SAPP_WIN32) + LARGE_INTEGER qpc; + QueryPerformanceCounter(&qpc); + const uint64_t now = (uint64_t)_sapp_int64_muldiv(qpc.QuadPart - ts->win.start.QuadPart, 1000000000, ts->win.freq.QuadPart); + return (double)now / 1000000000.0; + #else + struct timespec tspec; + clock_gettime(_SAPP_CLOCK_MONOTONIC, &tspec); + const uint64_t now = ((uint64_t)tspec.tv_sec*1000000000 + (uint64_t)tspec.tv_nsec) - ts->posix.start; + return (double)now / 1000000000.0; + #endif +} + +typedef struct { + double last; + double accum; + double avg; + int spike_count; + int num; + _sapp_timestamp_t timestamp; + _sapp_ring_t ring; +} _sapp_timing_t; + +_SOKOL_PRIVATE void _sapp_timing_reset(_sapp_timing_t* t) { + t->last = 0.0; + t->accum = 0.0; + t->spike_count = 0; + t->num = 0; + _sapp_ring_init(&t->ring); +} + +_SOKOL_PRIVATE void _sapp_timing_init(_sapp_timing_t* t) { + t->avg = 1.0 / 60.0; // dummy value until first actual value is available + _sapp_timing_reset(t); + _sapp_timestamp_init(&t->timestamp); +} + +_SOKOL_PRIVATE void _sapp_timing_put(_sapp_timing_t* t, double dur) { + // arbitrary upper limit to ignore outliers (e.g. during window resizing, or debugging) + double min_dur = 0.0; + double max_dur = 0.1; + // if we have enough samples for a useful average, use a much tighter 'valid window' + if (_sapp_ring_full(&t->ring)) { + min_dur = t->avg * 0.8; + max_dur = t->avg * 1.2; + } + if ((dur < min_dur) || (dur > max_dur)) { + t->spike_count++; + // if there have been many spikes in a row, the display refresh rate + // might have changed, so a timing reset is needed + if (t->spike_count > 20) { + _sapp_timing_reset(t); + } + return; + } + if (_sapp_ring_full(&t->ring)) { + double old_val = _sapp_ring_dequeue(&t->ring); + t->accum -= old_val; + t->num -= 1; + } + _sapp_ring_enqueue(&t->ring, dur); + t->accum += dur; + t->num += 1; + SOKOL_ASSERT(t->num > 0); + t->avg = t->accum / t->num; + t->spike_count = 0; +} + +_SOKOL_PRIVATE void _sapp_timing_discontinuity(_sapp_timing_t* t) { + t->last = 0.0; +} + +_SOKOL_PRIVATE void _sapp_timing_measure(_sapp_timing_t* t) { + const double now = _sapp_timestamp_now(&t->timestamp); + if (t->last > 0.0) { + double dur = now - t->last; + _sapp_timing_put(t, dur); + } + t->last = now; +} + +_SOKOL_PRIVATE void _sapp_timing_external(_sapp_timing_t* t, double now) { + if (t->last > 0.0) { + double dur = now - t->last; + _sapp_timing_put(t, dur); + } + t->last = now; +} + +_SOKOL_PRIVATE double _sapp_timing_get_avg(_sapp_timing_t* t) { + return t->avg; +} + +// ███████ ████████ ██████ ██ ██ ██████ ████████ ███████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ███████ ██ ██████ ██ ██ ██ ██ ███████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ███████ ██ ██ ██ ██████ ██████ ██ ███████ +// +// >> structs +#if defined(SOKOL_WGPU) +typedef struct { + WGPUInstance instance; + WGPUAdapter adapter; + WGPUDevice device; + WGPUSurface surface; + WGPUTextureFormat render_format; + WGPUTexture msaa_tex; + WGPUTextureView msaa_view; + WGPUTexture depth_stencil_tex; + WGPUTextureView depth_stencil_view; + WGPUTextureView swapchain_view; + bool init_done; +} _sapp_wgpu_t; +#endif + +#if defined(_SAPP_MACOS) +@interface _sapp_macos_app_delegate : NSObject +@end +@interface _sapp_macos_window : NSWindow +@end +@interface _sapp_macos_window_delegate : NSObject +@end +#if defined(SOKOL_METAL) + @interface _sapp_macos_view : MTKView + @end +#elif defined(SOKOL_GLCORE) + @interface _sapp_macos_view : NSOpenGLView + - (void)timerFired:(id)sender; + @end +#elif defined(SOKOL_WGPU) + @interface _sapp_macos_view : NSView + - (void)displayLinkFired:(id)sender; + @end +#endif // SOKOL_GLCORE + +typedef struct { + uint32_t flags_changed_store; + uint8_t mouse_buttons; + NSWindow* window; + NSTrackingArea* tracking_area; + id keyup_monitor; + _sapp_macos_app_delegate* app_dlg; + _sapp_macos_window_delegate* win_dlg; + _sapp_macos_view* view; + NSCursor* standard_cursors[_SAPP_MOUSECURSOR_NUM]; + NSCursor* custom_cursors[_SAPP_MOUSECURSOR_NUM]; + #if defined(SOKOL_METAL) + id mtl_device; + #endif + #if defined(SOKOL_WGPU) + struct { + CAMetalLayer* mtl_layer; + CADisplayLink* display_link; + } wgpu; + #endif +} _sapp_macos_t; + +#endif // _SAPP_MACOS + +#if defined(_SAPP_IOS) + +@interface _sapp_app_delegate : NSObject +@end +@interface _sapp_textfield_dlg : NSObject +- (void)keyboardWasShown:(NSNotification*)notif; +- (void)keyboardWillBeHidden:(NSNotification*)notif; +- (void)keyboardDidChangeFrame:(NSNotification*)notif; +@end +#if defined(SOKOL_METAL) + @interface _sapp_ios_view : MTKView; + @end +#else + @interface _sapp_ios_view : GLKView + @end +#endif + +typedef struct { + UIWindow* window; + _sapp_ios_view* view; + UITextField* textfield; + _sapp_textfield_dlg* textfield_dlg; + #if defined(SOKOL_METAL) + UIViewController* view_ctrl; + id mtl_device; + #else + GLKViewController* view_ctrl; + EAGLContext* eagl_ctx; + #endif + bool suspended; +} _sapp_ios_t; + +#endif // _SAPP_IOS + +#if defined(_SAPP_EMSCRIPTEN) + +typedef struct { + bool mouse_lock_requested; + uint16_t mouse_buttons; +} _sapp_emsc_t; +#endif // _SAPP_EMSCRIPTEN + +#if defined(SOKOL_D3D11) && defined(_SAPP_WIN32) +typedef struct { + ID3D11Device* device; + ID3D11DeviceContext* device_context; + ID3D11Texture2D* rt; + ID3D11RenderTargetView* rtv; + ID3D11Texture2D* msaa_rt; + ID3D11RenderTargetView* msaa_rtv; + ID3D11Texture2D* ds; + ID3D11DepthStencilView* dsv; + DXGI_SWAP_CHAIN_DESC swap_chain_desc; + IDXGISwapChain* swap_chain; + IDXGIDevice1* dxgi_device; + bool use_dxgi_frame_stats; + UINT sync_refresh_count; +} _sapp_d3d11_t; +#endif + +#if defined(_SAPP_WIN32) + +#ifndef DPI_ENUMS_DECLARED +typedef enum PROCESS_DPI_AWARENESS +{ + PROCESS_DPI_UNAWARE = 0, + PROCESS_SYSTEM_DPI_AWARE = 1, + PROCESS_PER_MONITOR_DPI_AWARE = 2 +} PROCESS_DPI_AWARENESS; +typedef enum MONITOR_DPI_TYPE { + MDT_EFFECTIVE_DPI = 0, + MDT_ANGULAR_DPI = 1, + MDT_RAW_DPI = 2, + MDT_DEFAULT = MDT_EFFECTIVE_DPI +} MONITOR_DPI_TYPE; +#endif // DPI_ENUMS_DECLARED + +typedef struct { + bool aware; + float content_scale; + float window_scale; + float mouse_scale; +} _sapp_win32_dpi_t; + +typedef struct { + HWND hwnd; + HMONITOR hmonitor; + HDC dc; + HICON big_icon; + HICON small_icon; + HCURSOR standard_cursors[_SAPP_MOUSECURSOR_NUM]; + HCURSOR custom_cursors[_SAPP_MOUSECURSOR_NUM]; + UINT orig_codepage; + WCHAR surrogate; + RECT stored_window_rect; // used to restore window pos/size when toggling fullscreen => windowed + bool is_win10_or_greater; + bool in_create_window; + bool iconified; + _sapp_win32_dpi_t dpi; + struct { + struct { + LONG pos_x, pos_y; + bool pos_valid; + } lock; + struct { + LONG pos_x, pos_y; + bool pos_valid; + } raw_input; + bool requested_lock; + bool tracked; + uint8_t capture_mask; + } mouse; + struct { + size_t size; + void* ptr; + } raw_input_data; +} _sapp_win32_t; + +#if defined(SOKOL_GLCORE) +#define WGL_NUMBER_PIXEL_FORMATS_ARB 0x2000 +#define WGL_SUPPORT_OPENGL_ARB 0x2010 +#define WGL_DRAW_TO_WINDOW_ARB 0x2001 +#define WGL_PIXEL_TYPE_ARB 0x2013 +#define WGL_TYPE_RGBA_ARB 0x202b +#define WGL_ACCELERATION_ARB 0x2003 +#define WGL_NO_ACCELERATION_ARB 0x2025 +#define WGL_RED_BITS_ARB 0x2015 +#define WGL_GREEN_BITS_ARB 0x2017 +#define WGL_BLUE_BITS_ARB 0x2019 +#define WGL_ALPHA_BITS_ARB 0x201b +#define WGL_DEPTH_BITS_ARB 0x2022 +#define WGL_STENCIL_BITS_ARB 0x2023 +#define WGL_DOUBLE_BUFFER_ARB 0x2011 +#define WGL_SAMPLES_ARB 0x2042 +#define WGL_CONTEXT_DEBUG_BIT_ARB 0x00000001 +#define WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB 0x00000002 +#define WGL_CONTEXT_PROFILE_MASK_ARB 0x9126 +#define WGL_CONTEXT_CORE_PROFILE_BIT_ARB 0x00000001 +#define WGL_CONTEXT_MAJOR_VERSION_ARB 0x2091 +#define WGL_CONTEXT_MINOR_VERSION_ARB 0x2092 +#define WGL_CONTEXT_FLAGS_ARB 0x2094 +#define ERROR_INVALID_VERSION_ARB 0x2095 +#define ERROR_INVALID_PROFILE_ARB 0x2096 +#define ERROR_INCOMPATIBLE_DEVICE_CONTEXTS_ARB 0x2054 +typedef BOOL (WINAPI * PFNWGLSWAPINTERVALEXTPROC)(int); +typedef BOOL (WINAPI * PFNWGLGETPIXELFORMATATTRIBIVARBPROC)(HDC,int,int,UINT,const int*,int*); +typedef const char* (WINAPI * PFNWGLGETEXTENSIONSSTRINGEXTPROC)(void); +typedef const char* (WINAPI * PFNWGLGETEXTENSIONSSTRINGARBPROC)(HDC); +typedef HGLRC (WINAPI * PFNWGLCREATECONTEXTATTRIBSARBPROC)(HDC,HGLRC,const int*); +typedef HGLRC (WINAPI * PFN_wglCreateContext)(HDC); +typedef BOOL (WINAPI * PFN_wglDeleteContext)(HGLRC); +typedef PROC (WINAPI * PFN_wglGetProcAddress)(LPCSTR); +typedef HDC (WINAPI * PFN_wglGetCurrentDC)(void); +typedef BOOL (WINAPI * PFN_wglMakeCurrent)(HDC,HGLRC); + +typedef struct { + HINSTANCE opengl32; + HGLRC gl_ctx; + PFN_wglCreateContext CreateContext; + PFN_wglDeleteContext DeleteContext; + PFN_wglGetProcAddress GetProcAddress; + PFN_wglGetCurrentDC GetCurrentDC; + PFN_wglMakeCurrent MakeCurrent; + PFNWGLSWAPINTERVALEXTPROC SwapIntervalEXT; + PFNWGLGETPIXELFORMATATTRIBIVARBPROC GetPixelFormatAttribivARB; + PFNWGLGETEXTENSIONSSTRINGEXTPROC GetExtensionsStringEXT; + PFNWGLGETEXTENSIONSSTRINGARBPROC GetExtensionsStringARB; + PFNWGLCREATECONTEXTATTRIBSARBPROC CreateContextAttribsARB; + // special case glGetIntegerv + void (WINAPI *GetIntegerv)(uint32_t pname, int32_t* data); + bool ext_swap_control; + bool arb_multisample; + bool arb_pixel_format; + bool arb_create_context; + bool arb_create_context_profile; + HWND msg_hwnd; + HDC msg_dc; +} _sapp_wgl_t; +#endif // SOKOL_GLCORE + +#endif // _SAPP_WIN32 + +#if defined(_SAPP_ANDROID) +typedef enum { + _SOKOL_ANDROID_MSG_CREATE, + _SOKOL_ANDROID_MSG_RESUME, + _SOKOL_ANDROID_MSG_PAUSE, + _SOKOL_ANDROID_MSG_FOCUS, + _SOKOL_ANDROID_MSG_NO_FOCUS, + _SOKOL_ANDROID_MSG_SET_NATIVE_WINDOW, + _SOKOL_ANDROID_MSG_SET_INPUT_QUEUE, + _SOKOL_ANDROID_MSG_DESTROY, +} _sapp_android_msg_t; + +typedef struct { + pthread_t thread; + pthread_mutex_t mutex; + pthread_cond_t cond; + int read_from_main_fd; + int write_from_main_fd; +} _sapp_android_pt_t; + +typedef struct { + ANativeWindow* window; + AInputQueue* input; +} _sapp_android_resources_t; + +typedef struct { + ANativeActivity* activity; + _sapp_android_pt_t pt; + _sapp_android_resources_t pending; + _sapp_android_resources_t current; + ALooper* looper; + bool is_thread_started; + bool is_thread_stopping; + bool is_thread_stopped; + bool has_created; + bool has_resumed; + bool has_focus; + EGLConfig config; + EGLDisplay display; + EGLContext context; + EGLSurface surface; +} _sapp_android_t; + +#endif // _SAPP_ANDROID + +#if defined(_SAPP_LINUX) + +#define _SAPP_X11_XDND_VERSION (5) +#define _SAPP_X11_MAX_X11_KEYCODES (256) + +#define GLX_VENDOR 1 +#define GLX_RGBA_BIT 0x00000001 +#define GLX_WINDOW_BIT 0x00000001 +#define GLX_DRAWABLE_TYPE 0x8010 +#define GLX_RENDER_TYPE 0x8011 +#define GLX_DOUBLEBUFFER 5 +#define GLX_RED_SIZE 8 +#define GLX_GREEN_SIZE 9 +#define GLX_BLUE_SIZE 10 +#define GLX_ALPHA_SIZE 11 +#define GLX_DEPTH_SIZE 12 +#define GLX_STENCIL_SIZE 13 +#define GLX_SAMPLES 0x186a1 +#define GLX_CONTEXT_CORE_PROFILE_BIT_ARB 0x00000001 +#define GLX_CONTEXT_PROFILE_MASK_ARB 0x9126 +#define GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB 0x00000002 +#define GLX_CONTEXT_MAJOR_VERSION_ARB 0x2091 +#define GLX_CONTEXT_MINOR_VERSION_ARB 0x2092 +#define GLX_CONTEXT_FLAGS_ARB 0x2094 + +typedef XID GLXWindow; +typedef XID GLXDrawable; +typedef struct __GLXFBConfig* GLXFBConfig; +typedef struct __GLXcontext* GLXContext; +typedef void (*__GLXextproc)(void); + +typedef int (*PFNGLXGETFBCONFIGATTRIBPROC)(Display*,GLXFBConfig,int,int*); +typedef const char* (*PFNGLXGETCLIENTSTRINGPROC)(Display*,int); +typedef Bool (*PFNGLXQUERYEXTENSIONPROC)(Display*,int*,int*); +typedef Bool (*PFNGLXQUERYVERSIONPROC)(Display*,int*,int*); +typedef void (*PFNGLXDESTROYCONTEXTPROC)(Display*,GLXContext); +typedef Bool (*PFNGLXMAKECURRENTPROC)(Display*,GLXDrawable,GLXContext); +typedef void (*PFNGLXSWAPBUFFERSPROC)(Display*,GLXDrawable); +typedef const char* (*PFNGLXQUERYEXTENSIONSSTRINGPROC)(Display*,int); +typedef GLXFBConfig* (*PFNGLXGETFBCONFIGSPROC)(Display*,int,int*); +typedef __GLXextproc (* PFNGLXGETPROCADDRESSPROC)(const char *procName); +typedef void (*PFNGLXSWAPINTERVALEXTPROC)(Display*,GLXDrawable,int); +typedef XVisualInfo* (*PFNGLXGETVISUALFROMFBCONFIGPROC)(Display*,GLXFBConfig); +typedef GLXWindow (*PFNGLXCREATEWINDOWPROC)(Display*,GLXFBConfig,Window,const int*); +typedef void (*PFNGLXDESTROYWINDOWPROC)(Display*,GLXWindow); + +typedef int (*PFNGLXSWAPINTERVALMESAPROC)(int); +typedef GLXContext (*PFNGLXCREATECONTEXTATTRIBSARBPROC)(Display*,GLXFBConfig,GLXContext,Bool,const int*); + +typedef struct { + bool available; + int major_opcode; + int event_base; + int error_base; + int major; + int minor; +} _sapp_xi_t; + +typedef struct { + int version; + Window source; + Atom format; + Atom XdndAware; + Atom XdndEnter; + Atom XdndPosition; + Atom XdndStatus; + Atom XdndActionCopy; + Atom XdndDrop; + Atom XdndFinished; + Atom XdndSelection; + Atom XdndTypeList; + Atom text_uri_list; +} _sapp_xdnd_t; + +typedef struct { + uint8_t mouse_buttons; + Display* display; + int screen; + Window root; + Colormap colormap; + Window window; + Cursor hidden_cursor; + Cursor standard_cursors[_SAPP_MOUSECURSOR_NUM]; + Cursor custom_cursors[_SAPP_MOUSECURSOR_NUM]; + int window_state; + float dpi; + unsigned char error_code; + Atom UTF8_STRING; + Atom CLIPBOARD; + Atom TARGETS; + Atom WM_PROTOCOLS; + Atom WM_DELETE_WINDOW; + Atom WM_STATE; + Atom NET_WM_NAME; + Atom NET_WM_ICON_NAME; + Atom NET_WM_ICON; + Atom NET_WM_STATE; + Atom NET_WM_STATE_FULLSCREEN; + _sapp_xi_t xi; + _sapp_xdnd_t xdnd; + // XLib manual says keycodes are in the range [8, 255] inclusive. + // https://tronche.com/gui/x/xlib/input/keyboard-encoding.html + bool key_repeat[_SAPP_X11_MAX_X11_KEYCODES]; +} _sapp_x11_t; + +#if defined(_SAPP_GLX) + +typedef struct { + void* libgl; + int major; + int minor; + int event_base; + int error_base; + GLXContext ctx; + GLXWindow window; + + // GLX 1.3 functions + PFNGLXGETFBCONFIGSPROC GetFBConfigs; + PFNGLXGETFBCONFIGATTRIBPROC GetFBConfigAttrib; + PFNGLXGETCLIENTSTRINGPROC GetClientString; + PFNGLXQUERYEXTENSIONPROC QueryExtension; + PFNGLXQUERYVERSIONPROC QueryVersion; + PFNGLXDESTROYCONTEXTPROC DestroyContext; + PFNGLXMAKECURRENTPROC MakeCurrent; + PFNGLXSWAPBUFFERSPROC SwapBuffers; + PFNGLXQUERYEXTENSIONSSTRINGPROC QueryExtensionsString; + PFNGLXGETVISUALFROMFBCONFIGPROC GetVisualFromFBConfig; + PFNGLXCREATEWINDOWPROC CreateWindow; + PFNGLXDESTROYWINDOWPROC DestroyWindow; + + // GLX 1.4 and extension functions + PFNGLXGETPROCADDRESSPROC GetProcAddress; + PFNGLXGETPROCADDRESSPROC GetProcAddressARB; + PFNGLXSWAPINTERVALEXTPROC SwapIntervalEXT; + PFNGLXSWAPINTERVALMESAPROC SwapIntervalMESA; + PFNGLXCREATECONTEXTATTRIBSARBPROC CreateContextAttribsARB; + + // special case glGetIntegerv + void (*GetIntegerv)(uint32_t pname, int32_t* data); + + // extension availability + bool EXT_swap_control; + bool MESA_swap_control; + bool ARB_multisample; + bool ARB_create_context; + bool ARB_create_context_profile; +} _sapp_glx_t; +#endif // _SAPP_GLX + +#if defined(_SAPP_EGL) +typedef struct { + EGLDisplay display; + EGLContext context; + EGLSurface surface; +} _sapp_egl_t; +#endif // _SAPP_EGL +#endif // _SAPP_LINUX + +#if defined(_SAPP_ANY_GL) +typedef struct { + uint32_t framebuffer; +} _sapp_gl_t; +#endif + +typedef struct { + bool enabled; + int buf_size; + char* buffer; +} _sapp_clipboard_t; + +typedef struct { + bool enabled; + int max_files; + int max_path_length; + int num_files; + int buf_size; + char* buffer; +} _sapp_drop_t; + +typedef struct { + float x, y; + float dx, dy; + bool shown; + bool locked; + bool pos_valid; + sapp_mouse_cursor current_cursor; +} _sapp_mouse_t; + +typedef struct { + sapp_desc desc; + bool valid; + bool fullscreen; + bool first_frame; + bool init_called; + bool cleanup_called; + bool quit_requested; + bool quit_ordered; + bool event_consumed; + bool html5_ask_leave_site; + bool onscreen_keyboard_shown; + int window_width; + int window_height; + int framebuffer_width; + int framebuffer_height; + int sample_count; + int swap_interval; + float dpi_scale; + uint64_t frame_count; + _sapp_timing_t timing; + sapp_event event; + _sapp_mouse_t mouse; + _sapp_clipboard_t clipboard; + _sapp_drop_t drop; + sapp_icon_desc default_icon_desc; + uint32_t* default_icon_pixels; + #if defined(SOKOL_WGPU) + _sapp_wgpu_t wgpu; + #endif + #if defined(_SAPP_MACOS) + _sapp_macos_t macos; + #elif defined(_SAPP_IOS) + _sapp_ios_t ios; + #elif defined(_SAPP_EMSCRIPTEN) + _sapp_emsc_t emsc; + #elif defined(_SAPP_WIN32) + _sapp_win32_t win32; + #if defined(SOKOL_D3D11) + _sapp_d3d11_t d3d11; + #elif defined(SOKOL_GLCORE) + _sapp_wgl_t wgl; + #endif + #elif defined(_SAPP_ANDROID) + _sapp_android_t android; + #elif defined(_SAPP_LINUX) + _sapp_x11_t x11; + #if defined(_SAPP_GLX) + _sapp_glx_t glx; + #elif defined(_SAPP_EGL) + _sapp_egl_t egl; + #endif + #endif + #if defined(_SAPP_ANY_GL) + _sapp_gl_t gl; + #endif + char html5_canvas_selector[_SAPP_MAX_TITLE_LENGTH]; + char window_title[_SAPP_MAX_TITLE_LENGTH]; // UTF-8 + wchar_t window_title_wide[_SAPP_MAX_TITLE_LENGTH]; // UTF-32 or UCS-2 */ + sapp_keycode keycodes[SAPP_MAX_KEYCODES]; + bool custom_cursor_bound[_SAPP_MOUSECURSOR_NUM]; // true if a custom mouse cursor is bound on that slot +} _sapp_t; +static _sapp_t _sapp; + +// ██ ██████ ██████ ██████ ██ ███ ██ ██████ +// ██ ██ ██ ██ ██ ██ ████ ██ ██ +// ██ ██ ██ ██ ███ ██ ███ ██ ██ ██ ██ ██ ███ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ███████ ██████ ██████ ██████ ██ ██ ████ ██████ +// +// >>logging +#if defined(SOKOL_DEBUG) +#define _SAPP_LOGITEM_XMACRO(item,msg) #item ": " msg, +static const char* _sapp_log_messages[] = { + _SAPP_LOG_ITEMS +}; +#undef _SAPP_LOGITEM_XMACRO +#endif // SOKOL_DEBUG + +#define _SAPP_PANIC(code) _sapp_log(SAPP_LOGITEM_ ##code, 0, 0, __LINE__) +#define _SAPP_ERROR(code) _sapp_log(SAPP_LOGITEM_ ##code, 1, 0, __LINE__) +#define _SAPP_WARN(code) _sapp_log(SAPP_LOGITEM_ ##code, 2, 0, __LINE__) +#define _SAPP_INFO(code) _sapp_log(SAPP_LOGITEM_ ##code, 3, 0, __LINE__) +#define _SAPP_PANIC_MSG(code, msg) _sapp_log(SAPP_LOGITEM_ ##code, 0, msg, __LINE__) +#define _SAPP_ERROR_MSG(code, msg) _sapp_log(SAPP_LOGITEM_ ##code, 1, msg, __LINE__) +#define _SAPP_WARN_MSG(code, msg) _sapp_log(SAPP_LOGITEM_ ##code, 2, msg, __LINE__) +#define _SAPP_INFO_MSG(code, msg) _sapp_log(SAPP_LOGITEM_ ##code, 3, msg, __LINE__) + +static void _sapp_log(sapp_log_item log_item, uint32_t log_level, const char* msg, uint32_t line_nr) { + if (_sapp.desc.logger.func) { + const char* filename = 0; + #if defined(SOKOL_DEBUG) + filename = __FILE__; + if (0 == msg) { + msg = _sapp_log_messages[log_item]; + } + #endif + _sapp.desc.logger.func("sapp", log_level, (uint32_t)log_item, msg, line_nr, filename, _sapp.desc.logger.user_data); + } else { + // for log level PANIC it would be 'undefined behaviour' to continue + if (log_level == 0) { + abort(); + } + } +} + +// ███ ███ ███████ ███ ███ ██████ ██████ ██ ██ +// ████ ████ ██ ████ ████ ██ ██ ██ ██ ██ ██ +// ██ ████ ██ █████ ██ ████ ██ ██ ██ ██████ ████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██ ██ ███████ ██ ██ ██████ ██ ██ ██ +// +// >>memory +_SOKOL_PRIVATE void _sapp_clear(void* ptr, size_t size) { + SOKOL_ASSERT(ptr && (size > 0)); + memset(ptr, 0, size); +} + +_SOKOL_PRIVATE void* _sapp_malloc(size_t size) { + SOKOL_ASSERT(size > 0); + void* ptr; + if (_sapp.desc.allocator.alloc_fn) { + ptr = _sapp.desc.allocator.alloc_fn(size, _sapp.desc.allocator.user_data); + } else { + ptr = malloc(size); + } + if (0 == ptr) { + _SAPP_PANIC(MALLOC_FAILED); + } + return ptr; +} + +_SOKOL_PRIVATE void* _sapp_malloc_clear(size_t size) { + void* ptr = _sapp_malloc(size); + _sapp_clear(ptr, size); + return ptr; +} + +_SOKOL_PRIVATE void _sapp_free(void* ptr) { + if (_sapp.desc.allocator.free_fn) { + _sapp.desc.allocator.free_fn(ptr, _sapp.desc.allocator.user_data); + } else { + free(ptr); + } +} + +// ██ ██ ███████ ██ ██████ ███████ ██████ ███████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ███████ █████ ██ ██████ █████ ██████ ███████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██ ██ ███████ ███████ ██ ███████ ██ ██ ███████ +// +// >>helpers + +// round float to int and at least 1 +_SOKOL_PRIVATE int _sapp_roundf_gzero(float f) { + int val = (int)roundf(f); + if (val <= 0) { + val = 1; + } + return val; +} + +_SOKOL_PRIVATE void _sapp_call_init(void) { + if (_sapp.desc.init_cb) { + _sapp.desc.init_cb(); + } else if (_sapp.desc.init_userdata_cb) { + _sapp.desc.init_userdata_cb(_sapp.desc.user_data); + } + _sapp.init_called = true; +} + +_SOKOL_PRIVATE void _sapp_call_frame(void) { + if (_sapp.init_called && !_sapp.cleanup_called) { + if (_sapp.desc.frame_cb) { + _sapp.desc.frame_cb(); + } else if (_sapp.desc.frame_userdata_cb) { + _sapp.desc.frame_userdata_cb(_sapp.desc.user_data); + } + } +} + +_SOKOL_PRIVATE void _sapp_call_cleanup(void) { + if (!_sapp.cleanup_called) { + if (_sapp.desc.cleanup_cb) { + _sapp.desc.cleanup_cb(); + } else if (_sapp.desc.cleanup_userdata_cb) { + _sapp.desc.cleanup_userdata_cb(_sapp.desc.user_data); + } + _sapp.cleanup_called = true; + } +} + +_SOKOL_PRIVATE bool _sapp_call_event(const sapp_event* e) { + if (!_sapp.cleanup_called) { + if (_sapp.desc.event_cb) { + _sapp.desc.event_cb(e); + } else if (_sapp.desc.event_userdata_cb) { + _sapp.desc.event_userdata_cb(e, _sapp.desc.user_data); + } + } + if (_sapp.event_consumed) { + _sapp.event_consumed = false; + return true; + } else { + return false; + } +} + +_SOKOL_PRIVATE char* _sapp_dropped_file_path_ptr(int index) { + SOKOL_ASSERT(_sapp.drop.buffer); + SOKOL_ASSERT((index >= 0) && (index <= _sapp.drop.max_files)); + int offset = index * _sapp.drop.max_path_length; + SOKOL_ASSERT(offset < _sapp.drop.buf_size); + return &_sapp.drop.buffer[offset]; +} + +/* Copy a string (either zero-terminated or with explicit length) + into a fixed size buffer with guaranteed zero-termination. + + Return false if the string didn't fit into the buffer and had to be clamped. + + FIXME: Currently UTF-8 strings might become invalid if the string + is clamped, because the last zero-byte might be written into + the middle of a multi-byte sequence. +*/ +_SOKOL_PRIVATE bool _sapp_strcpy_range(const char* src, size_t src_len, char* dst, size_t dst_buf_len) { + SOKOL_ASSERT(src && dst && (dst_buf_len > 0)); + if (0 == src_len) { + src_len = dst_buf_len; + } + char* const end = &(dst[dst_buf_len-1]); + char c = 0; + for (size_t i = 0; i < dst_buf_len; i++) { + c = *src; + if (i >= src_len) { + c = 0; + } + if (c != 0) { + src++; + } + *dst++ = c; + } + // truncated? + if (c != 0) { + *end = 0; + return false; + } else { + return true; + } +} + +_SOKOL_PRIVATE bool _sapp_strcpy(const char* src, char* dst, size_t dst_buf_len) { + return _sapp_strcpy_range(src, 0, dst, dst_buf_len); +} + +_SOKOL_PRIVATE sapp_desc _sapp_desc_defaults(const sapp_desc* desc) { + SOKOL_ASSERT((desc->allocator.alloc_fn && desc->allocator.free_fn) || (!desc->allocator.alloc_fn && !desc->allocator.free_fn)); + sapp_desc res = *desc; + res.sample_count = _sapp_def(res.sample_count, 1); + res.swap_interval = _sapp_def(res.swap_interval, 1); + if (0 == res.gl_major_version) { + #if defined(SOKOL_GLCORE) + res.gl_major_version = 4; + #if defined(_SAPP_APPLE) + res.gl_minor_version = 1; + #else + res.gl_minor_version = 3; + #endif + #elif defined(SOKOL_GLES3) + res.gl_major_version = 3; + #if defined(_SAPP_ANDROID) || defined(_SAPP_LINUX) + res.gl_minor_version = 1; + #else + res.gl_minor_version = 0; + #endif + #endif + } + res.html5_canvas_selector = _sapp_def(res.html5_canvas_selector, "#canvas"); + res.clipboard_size = _sapp_def(res.clipboard_size, 8192); + res.max_dropped_files = _sapp_def(res.max_dropped_files, 1); + res.max_dropped_file_path_length = _sapp_def(res.max_dropped_file_path_length, 2048); + res.window_title = _sapp_def(res.window_title, "sokol"); + return res; +} + +_SOKOL_PRIVATE void _sapp_init_state(const sapp_desc* desc) { + SOKOL_ASSERT(desc); + SOKOL_ASSERT(desc->width >= 0); + SOKOL_ASSERT(desc->height >= 0); + SOKOL_ASSERT(desc->sample_count >= 0); + SOKOL_ASSERT(desc->swap_interval >= 0); + SOKOL_ASSERT(desc->clipboard_size >= 0); + SOKOL_ASSERT(desc->max_dropped_files >= 0); + SOKOL_ASSERT(desc->max_dropped_file_path_length >= 0); + _SAPP_CLEAR_ARC_STRUCT(_sapp_t, _sapp); + _sapp.desc = _sapp_desc_defaults(desc); + _sapp.first_frame = true; + // NOTE: _sapp.desc.width/height may be 0! Platform backends need to deal with this + _sapp.window_width = _sapp.desc.width; + _sapp.window_height = _sapp.desc.height; + _sapp.framebuffer_width = _sapp.window_width; + _sapp.framebuffer_height = _sapp.window_height; + _sapp.sample_count = _sapp.desc.sample_count; + _sapp.swap_interval = _sapp.desc.swap_interval; + _sapp_strcpy(_sapp.desc.html5_canvas_selector, _sapp.html5_canvas_selector, sizeof(_sapp.html5_canvas_selector)); + _sapp.desc.html5_canvas_selector = _sapp.html5_canvas_selector; + _sapp.html5_ask_leave_site = _sapp.desc.html5_ask_leave_site; + _sapp.clipboard.enabled = _sapp.desc.enable_clipboard; + if (_sapp.clipboard.enabled) { + _sapp.clipboard.buf_size = _sapp.desc.clipboard_size; + _sapp.clipboard.buffer = (char*) _sapp_malloc_clear((size_t)_sapp.clipboard.buf_size); + } + _sapp.drop.enabled = _sapp.desc.enable_dragndrop; + if (_sapp.drop.enabled) { + _sapp.drop.max_files = _sapp.desc.max_dropped_files; + _sapp.drop.max_path_length = _sapp.desc.max_dropped_file_path_length; + _sapp.drop.buf_size = _sapp.drop.max_files * _sapp.drop.max_path_length; + _sapp.drop.buffer = (char*) _sapp_malloc_clear((size_t)_sapp.drop.buf_size); + } + _sapp_strcpy(_sapp.desc.window_title, _sapp.window_title, sizeof(_sapp.window_title)); + _sapp.desc.window_title = _sapp.window_title; + _sapp.dpi_scale = 1.0f; + _sapp.fullscreen = _sapp.desc.fullscreen; + _sapp.mouse.shown = true; + _sapp_timing_init(&_sapp.timing); +} + +_SOKOL_PRIVATE void _sapp_discard_state(void) { + if (_sapp.clipboard.enabled) { + SOKOL_ASSERT(_sapp.clipboard.buffer); + _sapp_free((void*)_sapp.clipboard.buffer); + } + if (_sapp.drop.enabled) { + SOKOL_ASSERT(_sapp.drop.buffer); + _sapp_free((void*)_sapp.drop.buffer); + } + if (_sapp.default_icon_pixels) { + _sapp_free((void*)_sapp.default_icon_pixels); + } + for (int i = 0; i < _SAPP_MOUSECURSOR_NUM; i++) { + sapp_unbind_mouse_cursor_image((sapp_mouse_cursor) i); + } + _SAPP_CLEAR_ARC_STRUCT(_sapp_t, _sapp); +} + +_SOKOL_PRIVATE void _sapp_init_event(sapp_event_type type) { + _sapp_clear(&_sapp.event, sizeof(_sapp.event)); + _sapp.event.type = type; + _sapp.event.frame_count = _sapp.frame_count; + _sapp.event.mouse_button = SAPP_MOUSEBUTTON_INVALID; + _sapp.event.window_width = _sapp.window_width; + _sapp.event.window_height = _sapp.window_height; + _sapp.event.framebuffer_width = _sapp.framebuffer_width; + _sapp.event.framebuffer_height = _sapp.framebuffer_height; + _sapp.event.mouse_x = _sapp.mouse.x; + _sapp.event.mouse_y = _sapp.mouse.y; + _sapp.event.mouse_dx = _sapp.mouse.dx; + _sapp.event.mouse_dy = _sapp.mouse.dy; +} + +_SOKOL_PRIVATE bool _sapp_events_enabled(void) { + /* only send events when an event callback is set, and the init function was called */ + return (_sapp.desc.event_cb || _sapp.desc.event_userdata_cb) && _sapp.init_called; +} + +_SOKOL_PRIVATE sapp_keycode _sapp_translate_key(int scan_code) { + if ((scan_code >= 0) && (scan_code < SAPP_MAX_KEYCODES)) { + return _sapp.keycodes[scan_code]; + } else { + return SAPP_KEYCODE_INVALID; + } +} + +_SOKOL_PRIVATE void _sapp_clear_drop_buffer(void) { + if (_sapp.drop.enabled) { + SOKOL_ASSERT(_sapp.drop.buffer); + _sapp_clear(_sapp.drop.buffer, (size_t)_sapp.drop.buf_size); + } +} + +_SOKOL_PRIVATE void _sapp_frame(void) { + if (_sapp.first_frame) { + _sapp.first_frame = false; + _sapp_call_init(); + } + _sapp_call_frame(); + _sapp.frame_count++; +} + +_SOKOL_PRIVATE bool _sapp_image_validate(const sapp_image_desc* desc) { + SOKOL_ASSERT(desc->width > 0); + SOKOL_ASSERT(desc->height > 0); + SOKOL_ASSERT(desc->pixels.ptr != 0); + SOKOL_ASSERT(desc->pixels.size > 0); + const size_t wh_size = (size_t)(desc->width * desc->height) * sizeof(uint32_t); + if (wh_size != desc->pixels.size) { + _SAPP_ERROR(IMAGE_DATA_SIZE_MISMATCH); + return false; + } + return true; +} + +_SOKOL_PRIVATE int _sapp_image_bestmatch(const sapp_image_desc image_descs[], int num_images, int width, int height) { + int least_diff = 0x7FFFFFFF; + int least_index = 0; + for (int i = 0; i < num_images; i++) { + int diff = (image_descs[i].width * image_descs[i].height) - (width * height); + if (diff < 0) { + diff = -diff; + } + if (diff < least_diff) { + least_diff = diff; + least_index = i; + } + } + return least_index; +} + +_SOKOL_PRIVATE int _sapp_icon_num_images(const sapp_icon_desc* desc) { + int index = 0; + for (; index < SAPP_MAX_ICONIMAGES; index++) { + if (0 == desc->images[index].pixels.ptr) { + break; + } + } + return index; +} + +_SOKOL_PRIVATE bool _sapp_validate_icon_desc(const sapp_icon_desc* desc, int num_images) { + SOKOL_ASSERT(num_images <= SAPP_MAX_ICONIMAGES); + for (int i = 0; i < num_images; i++) { + const sapp_image_desc* img_desc = &desc->images[i]; + if (!_sapp_image_validate(img_desc)) { + return false; + } + } + return true; +} + +_SOKOL_PRIVATE void _sapp_setup_default_icon(void) { + SOKOL_ASSERT(0 == _sapp.default_icon_pixels); + + const int num_icons = 3; + const int icon_sizes[3] = { 16, 32, 64 }; // must be multiple of 8! + + // allocate a pixel buffer for all icon pixels + int all_num_pixels = 0; + for (int i = 0; i < num_icons; i++) { + all_num_pixels += icon_sizes[i] * icon_sizes[i]; + } + _sapp.default_icon_pixels = (uint32_t*) _sapp_malloc_clear((size_t)all_num_pixels * sizeof(uint32_t)); + + // initialize default_icon_desc struct + uint32_t* dst = _sapp.default_icon_pixels; + const uint32_t* dst_end = dst + all_num_pixels; + (void)dst_end; // silence unused warning in release mode + for (int i = 0; i < num_icons; i++) { + const int dim = (int) icon_sizes[i]; + const int num_pixels = dim * dim; + sapp_image_desc* img_desc = &_sapp.default_icon_desc.images[i]; + img_desc->width = dim; + img_desc->height = dim; + img_desc->pixels.ptr = dst; + img_desc->pixels.size = (size_t)num_pixels * sizeof(uint32_t); + dst += num_pixels; + } + SOKOL_ASSERT(dst == dst_end); + + // Amstrad CPC font 'S' + const uint8_t tile[8] = { + 0x3C, + 0x66, + 0x60, + 0x3C, + 0x06, + 0x66, + 0x3C, + 0x00, + }; + // rainbow colors + const uint32_t colors[8] = { + 0xFF4370FF, + 0xFF26A7FF, + 0xFF58EEFF, + 0xFF57E1D4, + 0xFF65CC9C, + 0xFF6ABB66, + 0xFFF5A542, + 0xFFC2577E, + }; + dst = _sapp.default_icon_pixels; + const uint32_t blank = 0x00FFFFFF; + const uint32_t shadow = 0xFF000000; + for (int i = 0; i < num_icons; i++) { + const int dim = icon_sizes[i]; + SOKOL_ASSERT((dim % 8) == 0); + const int scale = dim / 8; + for (int ty = 0, y = 0; ty < 8; ty++) { + const uint32_t color = colors[ty]; + for (int sy = 0; sy < scale; sy++, y++) { + uint8_t bits = tile[ty]; + for (int tx = 0, x = 0; tx < 8; tx++, bits<<=1) { + uint32_t pixel = (0 == (bits & 0x80)) ? blank : color; + for (int sx = 0; sx < scale; sx++, x++) { + SOKOL_ASSERT(dst < dst_end); + *dst++ = pixel; + } + } + } + } + } + SOKOL_ASSERT(dst == dst_end); + + // right shadow + dst = _sapp.default_icon_pixels; + for (int i = 0; i < num_icons; i++) { + const int dim = icon_sizes[i]; + for (int y = 0; y < dim; y++) { + uint32_t prev_color = blank; + for (int x = 0; x < dim; x++) { + const int dst_index = y * dim + x; + const uint32_t cur_color = dst[dst_index]; + if ((cur_color == blank) && (prev_color != blank)) { + dst[dst_index] = shadow; + } + prev_color = cur_color; + } + } + dst += dim * dim; + } + SOKOL_ASSERT(dst == dst_end); + + // bottom shadow + dst = _sapp.default_icon_pixels; + for (int i = 0; i < num_icons; i++) { + const int dim = icon_sizes[i]; + for (int x = 0; x < dim; x++) { + uint32_t prev_color = blank; + for (int y = 0; y < dim; y++) { + const int dst_index = y * dim + x; + const uint32_t cur_color = dst[dst_index]; + if ((cur_color == blank) && (prev_color != blank)) { + dst[dst_index] = shadow; + } + prev_color = cur_color; + } + } + dst += dim * dim; + } + SOKOL_ASSERT(dst == dst_end); +} + +// ██ ██ ██████ ██████ ██ ██ +// ██ ██ ██ ██ ██ ██ ██ +// ██ █ ██ ██ ███ ██████ ██ ██ +// ██ ███ ██ ██ ██ ██ ██ ██ +// ███ ███ ██████ ██ ██████ +// +// >>wgpu +#if defined(SOKOL_WGPU) + +_SOKOL_PRIVATE WGPUStringView _sapp_wgpu_stringview(const char* str) { + WGPUStringView res; + if (str) { + res.data = str; + res.length = strlen(str); + } else { + res.data = 0; + res.length = 0; + } + return res; +} + +_SOKOL_PRIVATE WGPUCallbackMode _sapp_wgpu_callbackmode(void) { + #if defined(_SAPP_WGPU_HAS_WAIT) + return WGPUCallbackMode_WaitAnyOnly; + #else + return WGPUCallbackMode_AllowProcessEvents; + #endif +} + +_SOKOL_PRIVATE void _sapp_wgpu_await(WGPUFuture future) { + #if defined(_SAPP_WGPU_HAS_WAIT) + SOKOL_ASSERT(_sapp.wgpu.instance); + WGPUFutureWaitInfo wait_info; + _sapp_clear(&wait_info, sizeof(wait_info)); + wait_info.future = future; + WGPUWaitStatus res = wgpuInstanceWaitAny(_sapp.wgpu.instance, 1, &wait_info, UINT64_MAX); + SOKOL_ASSERT(res == WGPUWaitStatus_Success); _SOKOL_UNUSED(res); + #else + // this code path should never be called + _SOKOL_UNUSED(future); + SOKOL_ASSERT(false); + #endif +} + +_SOKOL_PRIVATE WGPUTextureFormat _sapp_wgpu_pick_render_format(size_t count, const WGPUTextureFormat* formats) { + // NOTE: only accept non-SRGB formats until sokol_app.h gets proper SRGB support + SOKOL_ASSERT((count > 0) && formats); + for (size_t i = 0; i < count; i++) { + const WGPUTextureFormat fmt = formats[i]; + switch (fmt) { + case WGPUTextureFormat_RGBA8Unorm: + case WGPUTextureFormat_BGRA8Unorm: + return fmt; + default: break; + } + } + // FIXME: fallback might still return an SRGB format + return formats[0]; +} + +_SOKOL_PRIVATE void _sapp_wgpu_create_swapchain(bool called_from_resize) { + SOKOL_ASSERT(_sapp.wgpu.instance); + SOKOL_ASSERT(_sapp.wgpu.device); + SOKOL_ASSERT(0 == _sapp.wgpu.msaa_tex); + SOKOL_ASSERT(0 == _sapp.wgpu.msaa_view); + SOKOL_ASSERT(0 == _sapp.wgpu.depth_stencil_tex); + SOKOL_ASSERT(0 == _sapp.wgpu.depth_stencil_view); + + if (!called_from_resize) { + SOKOL_ASSERT(0 == _sapp.wgpu.surface); + WGPUSurfaceDescriptor surf_desc; + _sapp_clear(&surf_desc, sizeof(surf_desc)); + #if defined (_SAPP_EMSCRIPTEN) + WGPUEmscriptenSurfaceSourceCanvasHTMLSelector html_canvas_desc; + _sapp_clear(&html_canvas_desc, sizeof(html_canvas_desc)); + html_canvas_desc.chain.sType = WGPUSType_EmscriptenSurfaceSourceCanvasHTMLSelector; + html_canvas_desc.selector = _sapp_wgpu_stringview(_sapp.html5_canvas_selector); + surf_desc.nextInChain = &html_canvas_desc.chain; + #elif defined(_SAPP_MACOS) + WGPUSurfaceSourceMetalLayer from_metal_layer; + _sapp_clear(&from_metal_layer, sizeof(from_metal_layer)); + from_metal_layer.chain.sType = WGPUSType_SurfaceSourceMetalLayer; + from_metal_layer.layer = _sapp.macos.view.layer; + surf_desc.nextInChain = &from_metal_layer.chain; + #elif defined(_SAPP_WIN32) + WGPUSurfaceSourceWindowsHWND from_hwnd; + _sapp_clear(&from_hwnd, sizeof(from_hwnd)); + from_hwnd.chain.sType = WGPUSType_SurfaceSourceWindowsHWND; + from_hwnd.hinstance = GetModuleHandleW(NULL); + from_hwnd.hwnd = _sapp.win32.hwnd; + surf_desc.nextInChain = &from_hwnd.chain; + #elif defined(_SAPP_LINUX) + WGPUSurfaceSourceXlibWindow from_xlib; + _sapp_clear(&from_xlib, sizeof(from_xlib)); + from_xlib.chain.sType = WGPUSType_SurfaceSourceXlibWindow; + from_xlib.display = _sapp.x11.display; + from_xlib.window = _sapp.x11.window; + surf_desc.nextInChain = &from_xlib.chain; + #else + #error "sokol_app.h: unsupported WebGPU platform" + #endif + _sapp.wgpu.surface = wgpuInstanceCreateSurface(_sapp.wgpu.instance, &surf_desc); + if (0 == _sapp.wgpu.surface) { + _SAPP_PANIC(WGPU_SWAPCHAIN_CREATE_SURFACE_FAILED); + } + WGPUSurfaceCapabilities surf_caps; + _sapp_clear(&surf_caps, sizeof(surf_caps)); + WGPUStatus caps_status = wgpuSurfaceGetCapabilities(_sapp.wgpu.surface, _sapp.wgpu.adapter, &surf_caps); + if (caps_status != WGPUStatus_Success) { + _SAPP_PANIC(WGPU_SWAPCHAIN_SURFACE_GET_CAPABILITIES_FAILED); + } + _sapp.wgpu.render_format = _sapp_wgpu_pick_render_format(surf_caps.formatCount, surf_caps.formats); + } + + SOKOL_ASSERT(_sapp.wgpu.surface); + WGPUSurfaceConfiguration surf_conf; + _sapp_clear(&surf_conf, sizeof(surf_conf)); + surf_conf.device = _sapp.wgpu.device; + surf_conf.format = _sapp.wgpu.render_format; + surf_conf.usage = WGPUTextureUsage_RenderAttachment; + surf_conf.width = (uint32_t)_sapp.framebuffer_width; + surf_conf.height = (uint32_t)_sapp.framebuffer_height; + surf_conf.alphaMode = WGPUCompositeAlphaMode_Opaque; + #if defined(_SAPP_EMSCRIPTEN) + // FIXME: make this further configurable? + if (_sapp.desc.html5_premultiplied_alpha) { + surf_conf.alphaMode = WGPUCompositeAlphaMode_Premultiplied; + } + #endif + surf_conf.presentMode = WGPUPresentMode_Fifo; + wgpuSurfaceConfigure(_sapp.wgpu.surface, &surf_conf); + + WGPUTextureDescriptor ds_desc; + _sapp_clear(&ds_desc, sizeof(ds_desc)); + ds_desc.usage = WGPUTextureUsage_RenderAttachment; + ds_desc.dimension = WGPUTextureDimension_2D; + ds_desc.size.width = (uint32_t)_sapp.framebuffer_width; + ds_desc.size.height = (uint32_t)_sapp.framebuffer_height; + ds_desc.size.depthOrArrayLayers = 1; + ds_desc.format = WGPUTextureFormat_Depth32FloatStencil8; + ds_desc.mipLevelCount = 1; + ds_desc.sampleCount = (uint32_t)_sapp.sample_count; + _sapp.wgpu.depth_stencil_tex = wgpuDeviceCreateTexture(_sapp.wgpu.device, &ds_desc); + if (0 == _sapp.wgpu.depth_stencil_tex) { + _SAPP_PANIC(WGPU_SWAPCHAIN_CREATE_DEPTH_STENCIL_TEXTURE_FAILED); + } + _sapp.wgpu.depth_stencil_view = wgpuTextureCreateView(_sapp.wgpu.depth_stencil_tex, 0); + if (0 == _sapp.wgpu.depth_stencil_view) { + _SAPP_PANIC(WGPU_SWAPCHAIN_CREATE_DEPTH_STENCIL_VIEW_FAILED); + } + + if (_sapp.sample_count > 1) { + WGPUTextureDescriptor msaa_desc; + _sapp_clear(&msaa_desc, sizeof(msaa_desc)); + msaa_desc.usage = WGPUTextureUsage_RenderAttachment; + msaa_desc.dimension = WGPUTextureDimension_2D; + msaa_desc.size.width = (uint32_t)_sapp.framebuffer_width; + msaa_desc.size.height = (uint32_t)_sapp.framebuffer_height; + msaa_desc.size.depthOrArrayLayers = 1; + msaa_desc.format = _sapp.wgpu.render_format; + msaa_desc.mipLevelCount = 1; + msaa_desc.sampleCount = (uint32_t)_sapp.sample_count; + _sapp.wgpu.msaa_tex = wgpuDeviceCreateTexture(_sapp.wgpu.device, &msaa_desc); + if (0 == _sapp.wgpu.msaa_tex) { + _SAPP_PANIC(WGPU_SWAPCHAIN_CREATE_MSAA_TEXTURE_FAILED); + } + _sapp.wgpu.msaa_view = wgpuTextureCreateView(_sapp.wgpu.msaa_tex, 0); + if (0 == _sapp.wgpu.msaa_view) { + _SAPP_PANIC(WGPU_SWAPCHAIN_CREATE_MSAA_VIEW_FAILED); + } + } +} + +_SOKOL_PRIVATE void _sapp_wgpu_discard_swapchain(bool called_from_resize) { + if (_sapp.wgpu.msaa_view) { + wgpuTextureViewRelease(_sapp.wgpu.msaa_view); + _sapp.wgpu.msaa_view = 0; + } + if (_sapp.wgpu.msaa_tex) { + wgpuTextureRelease(_sapp.wgpu.msaa_tex); + _sapp.wgpu.msaa_tex = 0; + } + if (_sapp.wgpu.depth_stencil_view) { + wgpuTextureViewRelease(_sapp.wgpu.depth_stencil_view); + _sapp.wgpu.depth_stencil_view = 0; + } + if (_sapp.wgpu.depth_stencil_tex) { + wgpuTextureRelease(_sapp.wgpu.depth_stencil_tex); + _sapp.wgpu.depth_stencil_tex = 0; + } + if (!called_from_resize) { + if (_sapp.wgpu.surface) { + wgpuSurfaceRelease(_sapp.wgpu.surface); + _sapp.wgpu.surface = 0; + } + } +} + +_SOKOL_PRIVATE WGPUTextureView _sapp_wgpu_swapchain_next(void) { + WGPUSurfaceTexture surf_tex; + _sapp_clear(&surf_tex, sizeof(surf_tex)); + wgpuSurfaceGetCurrentTexture(_sapp.wgpu.surface, &surf_tex); + switch (surf_tex.status) { + case WGPUSurfaceGetCurrentTextureStatus_SuccessOptimal: + case WGPUSurfaceGetCurrentTextureStatus_SuccessSuboptimal: + // all ok + break; + case WGPUSurfaceGetCurrentTextureStatus_Timeout: + case WGPUSurfaceGetCurrentTextureStatus_Outdated: + case WGPUSurfaceGetCurrentTextureStatus_Lost: + // skip this frame and reconfigure surface + if (surf_tex.texture) { + wgpuTextureRelease(surf_tex.texture); + } + _sapp_wgpu_discard_swapchain(false); + _sapp_wgpu_create_swapchain(false); + return 0; + case WGPUSurfaceGetCurrentTextureStatus_Error: + default: + _SAPP_PANIC(WGPU_SWAPCHAIN_GETCURRENTTEXTURE_FAILED); + break; + } + return wgpuTextureCreateView(surf_tex.texture, 0); +} + +_SOKOL_PRIVATE void _sapp_wgpu_swapchain_size_changed(void) { + if (_sapp.wgpu.surface) { + _sapp_wgpu_discard_swapchain(true); + _sapp_wgpu_create_swapchain(true); + } +} + +_SOKOL_PRIVATE void _sapp_wgpu_device_lost_cb(const WGPUDevice* dev, WGPUDeviceLostReason reason, WGPUStringView msg, void* ud1, void* ud2) { + _SOKOL_UNUSED(dev); _SOKOL_UNUSED(reason); _SOKOL_UNUSED(ud1); _SOKOL_UNUSED(ud2); + // NOTE: on wgpuInstanceRelease(), the device lost callback is always called with + // WGPUDeviceLostReason_CallbackCancelled (even though no device should exist at that point) + if (reason != WGPUDeviceLostReason_CallbackCancelled) { + SOKOL_ASSERT(msg.data && (msg.length > 0)); + char buf[1024]; + _sapp_strcpy_range(msg.data, msg.length, buf, sizeof(buf)); + _SAPP_ERROR_MSG(WGPU_DEVICE_LOST, buf); + } +} + +// NOTE: emdawnwebgpu doesn't seem to have a device logging callback +#if !defined(_SAPP_EMSCRIPTEN) +_SOKOL_PRIVATE void _sapp_wgpu_device_logging_cb(WGPULoggingType log_type, WGPUStringView msg, void* ud1, void* ud2) { + _SOKOL_UNUSED(log_type); _SOKOL_UNUSED(ud1); _SOKOL_UNUSED(ud2); + SOKOL_ASSERT(msg.data && (msg.length > 0)); + char buf[1024]; + _sapp_strcpy_range(msg.data, msg.length, buf, sizeof(buf)); + switch (log_type) { + case WGPULoggingType_Warning: + _SAPP_WARN_MSG(WGPU_DEVICE_LOG, buf); + break; + case WGPULoggingType_Error: + _SAPP_ERROR_MSG(WGPU_DEVICE_LOG, buf); + break; + default: + _SAPP_INFO_MSG(WGPU_DEVICE_LOG, buf); + break; + } +} +#endif + +_SOKOL_PRIVATE void _sapp_wgpu_uncaptured_error_cb(const WGPUDevice* dev, WGPUErrorType err_type, WGPUStringView msg, void* ud1, void* ud2) { + _SOKOL_UNUSED(dev); _SOKOL_UNUSED(ud1); _SOKOL_UNUSED(ud2); + if (err_type != WGPUErrorType_NoError) { + SOKOL_ASSERT(msg.data && (msg.length > 0)); + char buf[1024]; + _sapp_strcpy_range(msg.data, msg.length, buf, sizeof(buf)); + _SAPP_ERROR_MSG(WGPU_DEVICE_UNCAPTURED_ERROR, buf); + } +} + +_SOKOL_PRIVATE void _sapp_wgpu_request_device_cb(WGPURequestDeviceStatus status, WGPUDevice device, WGPUStringView msg, void* userdata1, void* userdata2) { + _SOKOL_UNUSED(msg); + _SOKOL_UNUSED(userdata1); + _SOKOL_UNUSED(userdata2); + SOKOL_ASSERT(!_sapp.wgpu.init_done); + if (status != WGPURequestDeviceStatus_Success) { + if (status == WGPURequestDeviceStatus_Error) { + _SAPP_PANIC(WGPU_REQUEST_DEVICE_STATUS_ERROR); + } else { + _SAPP_PANIC(WGPU_REQUEST_DEVICE_STATUS_UNKNOWN); + } + } + SOKOL_ASSERT(device); + _sapp.wgpu.device = device; + #if !defined(_SAPP_EMSCRIPTEN) + WGPULoggingCallbackInfo cb_info; + _sapp_clear(&cb_info, sizeof(cb_info)); + cb_info.callback = _sapp_wgpu_device_logging_cb; + wgpuDeviceSetLoggingCallback(_sapp.wgpu.device, cb_info); + #endif + _sapp_wgpu_create_swapchain(false); + _sapp.wgpu.init_done = true; +} + +_SOKOL_PRIVATE void _sapp_wgpu_create_device_and_swapchain(void) { + SOKOL_ASSERT(_sapp.wgpu.adapter); + size_t cur_feature_index = 1; + #define _SAPP_WGPU_MAX_REQUESTED_FEATURES (8) + WGPUFeatureName requiredFeatures[_SAPP_WGPU_MAX_REQUESTED_FEATURES] = { + WGPUFeatureName_Depth32FloatStencil8, + }; + // check for optional features we're interested in + if (wgpuAdapterHasFeature(_sapp.wgpu.adapter, WGPUFeatureName_TextureCompressionBC)) { + SOKOL_ASSERT(cur_feature_index < _SAPP_WGPU_MAX_REQUESTED_FEATURES); + requiredFeatures[cur_feature_index++] = WGPUFeatureName_TextureCompressionBC; + } + if (wgpuAdapterHasFeature(_sapp.wgpu.adapter, WGPUFeatureName_TextureCompressionETC2)) { + SOKOL_ASSERT(cur_feature_index < _SAPP_WGPU_MAX_REQUESTED_FEATURES); + requiredFeatures[cur_feature_index++] = WGPUFeatureName_TextureCompressionETC2; + } + if (wgpuAdapterHasFeature(_sapp.wgpu.adapter, WGPUFeatureName_TextureCompressionASTC)) { + SOKOL_ASSERT(cur_feature_index < _SAPP_WGPU_MAX_REQUESTED_FEATURES); + requiredFeatures[cur_feature_index++] = WGPUFeatureName_TextureCompressionASTC; + } + if (wgpuAdapterHasFeature(_sapp.wgpu.adapter, WGPUFeatureName_Float32Filterable)) { + SOKOL_ASSERT(cur_feature_index < _SAPP_WGPU_MAX_REQUESTED_FEATURES); + requiredFeatures[cur_feature_index++] = WGPUFeatureName_Float32Filterable; + } + #undef _SAPP_WGPU_MAX_REQUESTED_FEATURES + + WGPULimits adapterLimits = WGPU_LIMITS_INIT; + wgpuAdapterGetLimits(_sapp.wgpu.adapter, &adapterLimits); + + WGPULimits requiredLimits = WGPU_LIMITS_INIT; + requiredLimits.maxColorAttachments = adapterLimits.maxColorAttachments; + requiredLimits.maxSampledTexturesPerShaderStage = adapterLimits.maxSampledTexturesPerShaderStage; + requiredLimits.maxStorageBuffersPerShaderStage = adapterLimits.maxStorageBuffersPerShaderStage; + requiredLimits.maxStorageTexturesPerShaderStage = adapterLimits.maxStorageTexturesPerShaderStage; + + WGPURequestDeviceCallbackInfo cb_info; + _sapp_clear(&cb_info, sizeof(cb_info)); + cb_info.mode = _sapp_wgpu_callbackmode(); + cb_info.callback = _sapp_wgpu_request_device_cb; + + WGPUDeviceDescriptor dev_desc; + _sapp_clear(&dev_desc, sizeof(dev_desc)); + dev_desc.requiredFeatureCount = cur_feature_index; + dev_desc.requiredFeatures = requiredFeatures; + dev_desc.requiredLimits = &requiredLimits; + dev_desc.deviceLostCallbackInfo.mode = WGPUCallbackMode_AllowProcessEvents; + dev_desc.deviceLostCallbackInfo.callback = _sapp_wgpu_device_lost_cb; + dev_desc.uncapturedErrorCallbackInfo.callback = _sapp_wgpu_uncaptured_error_cb; + WGPUFuture future = wgpuAdapterRequestDevice(_sapp.wgpu.adapter, &dev_desc, cb_info); + #if defined(_SAPP_WGPU_HAS_WAIT) + _sapp_wgpu_await(future); + #else + _SOKOL_UNUSED(future); + #endif +} + +_SOKOL_PRIVATE void _sapp_wgpu_request_adapter_cb(WGPURequestAdapterStatus status, WGPUAdapter adapter, WGPUStringView msg, void* userdata1, void* userdata2) { + _SOKOL_UNUSED(msg); + _SOKOL_UNUSED(userdata1); + _SOKOL_UNUSED(userdata2); + if (status != WGPURequestAdapterStatus_Success) { + switch (status) { + case WGPURequestAdapterStatus_Unavailable: _SAPP_PANIC(WGPU_REQUEST_ADAPTER_STATUS_UNAVAILABLE); break; + case WGPURequestAdapterStatus_Error: _SAPP_PANIC(WGPU_REQUEST_ADAPTER_STATUS_ERROR); break; + default: _SAPP_PANIC(WGPU_REQUEST_ADAPTER_STATUS_UNKNOWN); break; + } + } + SOKOL_ASSERT(adapter); + _sapp.wgpu.adapter = adapter; + #if !defined(_SAPP_WGPU_HAS_WAIT) + // chain device creation + _sapp_wgpu_create_device_and_swapchain(); + #endif +} + +_SOKOL_PRIVATE void _sapp_wgpu_create_adapter(void) { + SOKOL_ASSERT(_sapp.wgpu.instance); + // FIXME: power preference? + WGPURequestAdapterCallbackInfo cb_info; + _sapp_clear(&cb_info, sizeof(cb_info)); + cb_info.mode = _sapp_wgpu_callbackmode(); + cb_info.callback = _sapp_wgpu_request_adapter_cb; + WGPUFuture future = wgpuInstanceRequestAdapter(_sapp.wgpu.instance, 0, cb_info); + #if defined(_SAPP_WGPU_HAS_WAIT) + _sapp_wgpu_await(future); + #else + _SOKOL_UNUSED(future); + #endif +} + +_SOKOL_PRIVATE void _sapp_wgpu_init(void) { + SOKOL_ASSERT(0 == _sapp.wgpu.instance); + SOKOL_ASSERT(!_sapp.wgpu.init_done); + + WGPUInstanceDescriptor desc; + _sapp_clear(&desc, sizeof(desc)); + #if defined(_SAPP_WGPU_HAS_WAIT) + WGPUInstanceFeatureName inst_features[1] = { + WGPUInstanceFeatureName_TimedWaitAny, + }; + desc.requiredFeatureCount = 1; + desc.requiredFeatures = inst_features; + #endif + _sapp.wgpu.instance = wgpuCreateInstance(&desc); + if (0 == _sapp.wgpu.instance) { + _SAPP_PANIC(WGPU_CREATE_INSTANCE_FAILED); + } + // NOTE: on Emscripten, device and swapchain creation are chained in the callacks + _sapp_wgpu_create_adapter(); + #if defined(_SAPP_WGPU_HAS_WAIT) + _sapp_wgpu_create_device_and_swapchain(); + SOKOL_ASSERT(_sapp.wgpu.init_done); + #endif +} + +_SOKOL_PRIVATE void _sapp_wgpu_discard(void) { + _sapp_wgpu_discard_swapchain(false); + if (_sapp.wgpu.device) { + wgpuDeviceRelease(_sapp.wgpu.device); + _sapp.wgpu.device = 0; + } + if (_sapp.wgpu.adapter) { + wgpuAdapterRelease(_sapp.wgpu.adapter); + _sapp.wgpu.adapter = 0; + } + if (_sapp.wgpu.instance) { + wgpuInstanceRelease(_sapp.wgpu.instance); + _sapp.wgpu.instance = 0; + } +} + +_SOKOL_PRIVATE void _sapp_wgpu_frame(void) { + wgpuInstanceProcessEvents(_sapp.wgpu.instance); + if (_sapp.wgpu.init_done) { + _sapp.wgpu.swapchain_view = _sapp_wgpu_swapchain_next(); + _sapp_frame(); + wgpuTextureViewRelease(_sapp.wgpu.swapchain_view); + _sapp.wgpu.swapchain_view = 0; + #if !defined(_SAPP_EMSCRIPTEN) + wgpuSurfacePresent(_sapp.wgpu.surface); + #endif + } +} +#endif // SOKOL_WGPU + +// █████ ██████ ██████ ██ ███████ +// ██ ██ ██ ██ ██ ██ ██ ██ +// ███████ ██████ ██████ ██ █████ +// ██ ██ ██ ██ ██ ██ +// ██ ██ ██ ██ ███████ ███████ +// +// >>apple +#if defined(_SAPP_APPLE) + +#if __has_feature(objc_arc) +#define _SAPP_OBJC_RELEASE(obj) { obj = nil; } +#else +#define _SAPP_OBJC_RELEASE(obj) { [obj release]; obj = nil; } +#endif + +// ███ ███ █████ ██████ ██████ ███████ +// ████ ████ ██ ██ ██ ██ ██ ██ +// ██ ████ ██ ███████ ██ ██ ██ ███████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██ ██ ██ ██ ██████ ██████ ███████ +// +// >>macos +#if defined(_SAPP_MACOS) + +NSInteger _sapp_macos_max_fps(void) { + NSInteger max_fps = 60; + #if (__MAC_OS_X_VERSION_MAX_ALLOWED >= 120000) + if (@available(macOS 12.0, *)) { + max_fps = [NSScreen.mainScreen maximumFramesPerSecond]; + } + #endif + return max_fps; +} + +#if defined(SOKOL_METAL) +_SOKOL_PRIVATE void _sapp_macos_mtl_init(void) { + NSInteger max_fps = _sapp_macos_max_fps(); + // NOTE: when eventually switching to CAMetalLayer, use the specialized + // CAMetalDisplayLink instead of CADisplayLink! + _sapp.macos.mtl_device = MTLCreateSystemDefaultDevice(); + _sapp.macos.view = [[_sapp_macos_view alloc] init]; + [_sapp.macos.view updateTrackingAreas]; + _sapp.macos.view.preferredFramesPerSecond = max_fps / _sapp.swap_interval; + _sapp.macos.view.device = _sapp.macos.mtl_device; + _sapp.macos.view.colorPixelFormat = MTLPixelFormatBGRA8Unorm; + _sapp.macos.view.depthStencilPixelFormat = MTLPixelFormatDepth32Float_Stencil8; + _sapp.macos.view.sampleCount = (NSUInteger) _sapp.sample_count; + _sapp.macos.view.autoResizeDrawable = false; + _sapp.macos.view.layer.magnificationFilter = kCAFilterNearest; +} + +_SOKOL_PRIVATE void _sapp_macos_mtl_discard_state(void) { + _SAPP_OBJC_RELEASE(_sapp.macos.mtl_device); +} + +_SOKOL_PRIVATE bool _sapp_macos_mtl_update_framebuffer_dimensions(NSRect view_bounds) { + _sapp.framebuffer_width = _sapp_roundf_gzero(view_bounds.size.width * _sapp.dpi_scale); + _sapp.framebuffer_height = _sapp_roundf_gzero(view_bounds.size.height * _sapp.dpi_scale); + const CGSize cur_fb_size = _sapp.macos.view.drawableSize; + int cur_fb_width = _sapp_roundf_gzero(cur_fb_size.width); + int cur_fb_height = _sapp_roundf_gzero(cur_fb_size.height); + bool dim_changed = (_sapp.framebuffer_width != cur_fb_width) || (_sapp.framebuffer_height != cur_fb_height); + if (dim_changed) { + const CGSize drawable_size = { (CGFloat) _sapp.framebuffer_width, (CGFloat) _sapp.framebuffer_height }; + _sapp.macos.view.drawableSize = drawable_size; + } + return dim_changed; +} +#endif + +#if defined(SOKOL_GLCORE) +_SOKOL_PRIVATE void _sapp_macos_gl_init(NSRect window_rect) { + NSOpenGLPixelFormatAttribute attrs[32]; + int i = 0; + attrs[i++] = NSOpenGLPFAAccelerated; + attrs[i++] = NSOpenGLPFADoubleBuffer; + attrs[i++] = NSOpenGLPFAOpenGLProfile; + const int glVersion = _sapp.desc.gl_major_version * 10 + _sapp.desc.gl_minor_version; + switch(glVersion) { + case 10: attrs[i++] = NSOpenGLProfileVersionLegacy; break; + case 32: attrs[i++] = NSOpenGLProfileVersion3_2Core; break; + case 41: attrs[i++] = NSOpenGLProfileVersion4_1Core; break; + default: + _SAPP_PANIC(MACOS_INVALID_NSOPENGL_PROFILE); + } + attrs[i++] = NSOpenGLPFAColorSize; attrs[i++] = 24; + attrs[i++] = NSOpenGLPFAAlphaSize; attrs[i++] = 8; + attrs[i++] = NSOpenGLPFADepthSize; attrs[i++] = 24; + attrs[i++] = NSOpenGLPFAStencilSize; attrs[i++] = 8; + if (_sapp.sample_count > 1) { + attrs[i++] = NSOpenGLPFAMultisample; + attrs[i++] = NSOpenGLPFASampleBuffers; attrs[i++] = 1; + attrs[i++] = NSOpenGLPFASamples; attrs[i++] = (NSOpenGLPixelFormatAttribute)_sapp.sample_count; + } else { + attrs[i++] = NSOpenGLPFASampleBuffers; attrs[i++] = 0; + } + attrs[i++] = 0; + NSOpenGLPixelFormat* glpixelformat_obj = [[NSOpenGLPixelFormat alloc] initWithAttributes:attrs]; + SOKOL_ASSERT(glpixelformat_obj != nil); + + _sapp.macos.view = [[_sapp_macos_view alloc] + initWithFrame:window_rect + pixelFormat:glpixelformat_obj]; + _SAPP_OBJC_RELEASE(glpixelformat_obj); + [_sapp.macos.view updateTrackingAreas]; + if (_sapp.desc.high_dpi) { + [_sapp.macos.view setWantsBestResolutionOpenGLSurface:YES]; + } else { + [_sapp.macos.view setWantsBestResolutionOpenGLSurface:NO]; + } + + NSTimer* timer_obj = [NSTimer timerWithTimeInterval:0.001 + target:_sapp.macos.view + selector:@selector(timerFired:) + userInfo:nil + repeats:YES]; + [[NSRunLoop currentRunLoop] addTimer:timer_obj forMode:NSDefaultRunLoopMode]; + timer_obj = nil; +} + +_SOKOL_PRIVATE void _sapp_macos_gl_discard_state(void) { + // nothing to do here +} + +_SOKOL_PRIVATE bool _sapp_macos_gl_update_framebuffer_dimensions(NSRect view_bounds) { + const int cur_fb_width = _sapp_roundf_gzero(view_bounds.size.width * _sapp.dpi_scale); + const int cur_fb_height = _sapp_roundf_gzero(view_bounds.size.height * _sapp.dpi_scale); + const bool dim_changed = (_sapp.framebuffer_width != cur_fb_width) || (_sapp.framebuffer_height != cur_fb_height); + _sapp.framebuffer_width = cur_fb_width; + _sapp.framebuffer_height = cur_fb_height; + return dim_changed; +} +#endif + +#if defined(SOKOL_WGPU) +_SOKOL_PRIVATE void _sapp_macos_wgpu_init(void) { + NSInteger max_fps = _sapp_macos_max_fps(); + _sapp.macos.wgpu.mtl_layer = [CAMetalLayer layer]; + _sapp.macos.wgpu.mtl_layer.magnificationFilter = kCAFilterNearest; + _sapp.macos.wgpu.mtl_layer.opaque = true; + // NOTE: might experiment with this, valid values are 2 or 3 (default: 3), I don't see any difference tbh + // _sapp.macos.wgpu.mtl_layer.maximumDrawableCount = 2; + _sapp.macos.view = [[_sapp_macos_view alloc] init]; + [_sapp.macos.view updateTrackingAreas]; + _sapp.macos.view.wantsLayer = YES; + _sapp.macos.view.layer = _sapp.macos.wgpu.mtl_layer; + _sapp.macos.wgpu.display_link = [_sapp.macos.view displayLinkWithTarget:_sapp.macos.view selector:@selector(displayLinkFired:)]; + float preferred_fps = max_fps / _sapp.swap_interval; + CAFrameRateRange frame_rate_range = { preferred_fps, preferred_fps, preferred_fps }; + _sapp.macos.wgpu.display_link.preferredFrameRateRange = frame_rate_range; + [_sapp.macos.wgpu.display_link addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes]; + _sapp_wgpu_init(); +} + +_SOKOL_PRIVATE void _sapp_macos_wgpu_discard_state(void) { + _SAPP_OBJC_RELEASE(_sapp.macos.wgpu.display_link); + _SAPP_OBJC_RELEASE(_sapp.macos.wgpu.mtl_layer); + _sapp_wgpu_discard(); +} + +_SOKOL_PRIVATE bool _sapp_macos_wgpu_update_framebuffer_dimensions(NSRect view_bounds) { + _sapp.framebuffer_width = _sapp_roundf_gzero(view_bounds.size.width * _sapp.dpi_scale); + _sapp.framebuffer_height = _sapp_roundf_gzero(view_bounds.size.height * _sapp.dpi_scale); + const CGSize cur_fb_size = _sapp.macos.wgpu.mtl_layer.drawableSize; + int cur_fb_width = _sapp_roundf_gzero(cur_fb_size.width); + int cur_fb_height = _sapp_roundf_gzero(cur_fb_size.height); + bool dim_changed = (_sapp.framebuffer_width != cur_fb_width) || (_sapp.framebuffer_height != cur_fb_height); + if (dim_changed) { + const CGSize drawable_size = { (CGFloat) _sapp.framebuffer_width, (CGFloat) _sapp.framebuffer_height }; + _sapp.macos.wgpu.mtl_layer.drawableSize = drawable_size; + _sapp_wgpu_swapchain_size_changed(); + } + return dim_changed; +} +#endif + +_SOKOL_PRIVATE void _sapp_macos_init_keytable(void) { + _sapp.keycodes[0x1D] = SAPP_KEYCODE_0; + _sapp.keycodes[0x12] = SAPP_KEYCODE_1; + _sapp.keycodes[0x13] = SAPP_KEYCODE_2; + _sapp.keycodes[0x14] = SAPP_KEYCODE_3; + _sapp.keycodes[0x15] = SAPP_KEYCODE_4; + _sapp.keycodes[0x17] = SAPP_KEYCODE_5; + _sapp.keycodes[0x16] = SAPP_KEYCODE_6; + _sapp.keycodes[0x1A] = SAPP_KEYCODE_7; + _sapp.keycodes[0x1C] = SAPP_KEYCODE_8; + _sapp.keycodes[0x19] = SAPP_KEYCODE_9; + _sapp.keycodes[0x00] = SAPP_KEYCODE_A; + _sapp.keycodes[0x0B] = SAPP_KEYCODE_B; + _sapp.keycodes[0x08] = SAPP_KEYCODE_C; + _sapp.keycodes[0x02] = SAPP_KEYCODE_D; + _sapp.keycodes[0x0E] = SAPP_KEYCODE_E; + _sapp.keycodes[0x03] = SAPP_KEYCODE_F; + _sapp.keycodes[0x05] = SAPP_KEYCODE_G; + _sapp.keycodes[0x04] = SAPP_KEYCODE_H; + _sapp.keycodes[0x22] = SAPP_KEYCODE_I; + _sapp.keycodes[0x26] = SAPP_KEYCODE_J; + _sapp.keycodes[0x28] = SAPP_KEYCODE_K; + _sapp.keycodes[0x25] = SAPP_KEYCODE_L; + _sapp.keycodes[0x2E] = SAPP_KEYCODE_M; + _sapp.keycodes[0x2D] = SAPP_KEYCODE_N; + _sapp.keycodes[0x1F] = SAPP_KEYCODE_O; + _sapp.keycodes[0x23] = SAPP_KEYCODE_P; + _sapp.keycodes[0x0C] = SAPP_KEYCODE_Q; + _sapp.keycodes[0x0F] = SAPP_KEYCODE_R; + _sapp.keycodes[0x01] = SAPP_KEYCODE_S; + _sapp.keycodes[0x11] = SAPP_KEYCODE_T; + _sapp.keycodes[0x20] = SAPP_KEYCODE_U; + _sapp.keycodes[0x09] = SAPP_KEYCODE_V; + _sapp.keycodes[0x0D] = SAPP_KEYCODE_W; + _sapp.keycodes[0x07] = SAPP_KEYCODE_X; + _sapp.keycodes[0x10] = SAPP_KEYCODE_Y; + _sapp.keycodes[0x06] = SAPP_KEYCODE_Z; + _sapp.keycodes[0x27] = SAPP_KEYCODE_APOSTROPHE; + _sapp.keycodes[0x2A] = SAPP_KEYCODE_BACKSLASH; + _sapp.keycodes[0x2B] = SAPP_KEYCODE_COMMA; + _sapp.keycodes[0x18] = SAPP_KEYCODE_EQUAL; + _sapp.keycodes[0x32] = SAPP_KEYCODE_GRAVE_ACCENT; + _sapp.keycodes[0x21] = SAPP_KEYCODE_LEFT_BRACKET; + _sapp.keycodes[0x1B] = SAPP_KEYCODE_MINUS; + _sapp.keycodes[0x2F] = SAPP_KEYCODE_PERIOD; + _sapp.keycodes[0x1E] = SAPP_KEYCODE_RIGHT_BRACKET; + _sapp.keycodes[0x29] = SAPP_KEYCODE_SEMICOLON; + _sapp.keycodes[0x2C] = SAPP_KEYCODE_SLASH; + _sapp.keycodes[0x0A] = SAPP_KEYCODE_WORLD_1; + _sapp.keycodes[0x33] = SAPP_KEYCODE_BACKSPACE; + _sapp.keycodes[0x39] = SAPP_KEYCODE_CAPS_LOCK; + _sapp.keycodes[0x75] = SAPP_KEYCODE_DELETE; + _sapp.keycodes[0x7D] = SAPP_KEYCODE_DOWN; + _sapp.keycodes[0x77] = SAPP_KEYCODE_END; + _sapp.keycodes[0x24] = SAPP_KEYCODE_ENTER; + _sapp.keycodes[0x35] = SAPP_KEYCODE_ESCAPE; + _sapp.keycodes[0x7A] = SAPP_KEYCODE_F1; + _sapp.keycodes[0x78] = SAPP_KEYCODE_F2; + _sapp.keycodes[0x63] = SAPP_KEYCODE_F3; + _sapp.keycodes[0x76] = SAPP_KEYCODE_F4; + _sapp.keycodes[0x60] = SAPP_KEYCODE_F5; + _sapp.keycodes[0x61] = SAPP_KEYCODE_F6; + _sapp.keycodes[0x62] = SAPP_KEYCODE_F7; + _sapp.keycodes[0x64] = SAPP_KEYCODE_F8; + _sapp.keycodes[0x65] = SAPP_KEYCODE_F9; + _sapp.keycodes[0x6D] = SAPP_KEYCODE_F10; + _sapp.keycodes[0x67] = SAPP_KEYCODE_F11; + _sapp.keycodes[0x6F] = SAPP_KEYCODE_F12; + _sapp.keycodes[0x69] = SAPP_KEYCODE_F13; + _sapp.keycodes[0x6B] = SAPP_KEYCODE_F14; + _sapp.keycodes[0x71] = SAPP_KEYCODE_F15; + _sapp.keycodes[0x6A] = SAPP_KEYCODE_F16; + _sapp.keycodes[0x40] = SAPP_KEYCODE_F17; + _sapp.keycodes[0x4F] = SAPP_KEYCODE_F18; + _sapp.keycodes[0x50] = SAPP_KEYCODE_F19; + _sapp.keycodes[0x5A] = SAPP_KEYCODE_F20; + _sapp.keycodes[0x73] = SAPP_KEYCODE_HOME; + _sapp.keycodes[0x72] = SAPP_KEYCODE_INSERT; + _sapp.keycodes[0x7B] = SAPP_KEYCODE_LEFT; + _sapp.keycodes[0x3A] = SAPP_KEYCODE_LEFT_ALT; + _sapp.keycodes[0x3B] = SAPP_KEYCODE_LEFT_CONTROL; + _sapp.keycodes[0x38] = SAPP_KEYCODE_LEFT_SHIFT; + _sapp.keycodes[0x37] = SAPP_KEYCODE_LEFT_SUPER; + _sapp.keycodes[0x6E] = SAPP_KEYCODE_MENU; + _sapp.keycodes[0x47] = SAPP_KEYCODE_NUM_LOCK; + _sapp.keycodes[0x79] = SAPP_KEYCODE_PAGE_DOWN; + _sapp.keycodes[0x74] = SAPP_KEYCODE_PAGE_UP; + _sapp.keycodes[0x7C] = SAPP_KEYCODE_RIGHT; + _sapp.keycodes[0x3D] = SAPP_KEYCODE_RIGHT_ALT; + _sapp.keycodes[0x3E] = SAPP_KEYCODE_RIGHT_CONTROL; + _sapp.keycodes[0x3C] = SAPP_KEYCODE_RIGHT_SHIFT; + _sapp.keycodes[0x36] = SAPP_KEYCODE_RIGHT_SUPER; + _sapp.keycodes[0x31] = SAPP_KEYCODE_SPACE; + _sapp.keycodes[0x30] = SAPP_KEYCODE_TAB; + _sapp.keycodes[0x7E] = SAPP_KEYCODE_UP; + _sapp.keycodes[0x52] = SAPP_KEYCODE_KP_0; + _sapp.keycodes[0x53] = SAPP_KEYCODE_KP_1; + _sapp.keycodes[0x54] = SAPP_KEYCODE_KP_2; + _sapp.keycodes[0x55] = SAPP_KEYCODE_KP_3; + _sapp.keycodes[0x56] = SAPP_KEYCODE_KP_4; + _sapp.keycodes[0x57] = SAPP_KEYCODE_KP_5; + _sapp.keycodes[0x58] = SAPP_KEYCODE_KP_6; + _sapp.keycodes[0x59] = SAPP_KEYCODE_KP_7; + _sapp.keycodes[0x5B] = SAPP_KEYCODE_KP_8; + _sapp.keycodes[0x5C] = SAPP_KEYCODE_KP_9; + _sapp.keycodes[0x45] = SAPP_KEYCODE_KP_ADD; + _sapp.keycodes[0x41] = SAPP_KEYCODE_KP_DECIMAL; + _sapp.keycodes[0x4B] = SAPP_KEYCODE_KP_DIVIDE; + _sapp.keycodes[0x4C] = SAPP_KEYCODE_KP_ENTER; + _sapp.keycodes[0x51] = SAPP_KEYCODE_KP_EQUAL; + _sapp.keycodes[0x43] = SAPP_KEYCODE_KP_MULTIPLY; + _sapp.keycodes[0x4E] = SAPP_KEYCODE_KP_SUBTRACT; +} + +_SOKOL_PRIVATE void _sapp_macos_discard_state(void) { + // NOTE: it's safe to call [release] on a nil object + if (_sapp.macos.keyup_monitor != nil) { + [NSEvent removeMonitor:_sapp.macos.keyup_monitor]; + // NOTE: removeMonitor also releases the object + _sapp.macos.keyup_monitor = nil; + } + _SAPP_OBJC_RELEASE(_sapp.macos.tracking_area); + _SAPP_OBJC_RELEASE(_sapp.macos.app_dlg); + _SAPP_OBJC_RELEASE(_sapp.macos.win_dlg); + _SAPP_OBJC_RELEASE(_sapp.macos.view); + #if defined(SOKOL_METAL) + _sapp_macos_mtl_discard_state(); + #elif defined(SOKOL_GLCORE) + _sapp_macos_gl_discard_state(); + #elif defined(SOKOL_WGPU) + _sapp_macos_wgpu_discard_state(); + #endif + _SAPP_OBJC_RELEASE(_sapp.macos.window); +} + +// undocumented methods for creating cursors (see GLFW 3.4 and imgui_impl_osx.mm) +@interface NSCursor() ++ (id)_windowResizeNorthWestSouthEastCursor; ++ (id)_windowResizeNorthEastSouthWestCursor; ++ (id)_windowResizeNorthSouthCursor; ++ (id)_windowResizeEastWestCursor; +@end + +_SOKOL_PRIVATE void _sapp_macos_init_cursors(void) { + for (size_t i = 0; i < _SAPP_MOUSECURSOR_NUM; i++) { + _sapp.macos.standard_cursors[i] = nil; + _sapp.macos.custom_cursors[i] = nil; + } + _sapp.macos.standard_cursors[SAPP_MOUSECURSOR_ARROW] = [NSCursor arrowCursor]; + _sapp.macos.standard_cursors[SAPP_MOUSECURSOR_IBEAM] = [NSCursor IBeamCursor]; + _sapp.macos.standard_cursors[SAPP_MOUSECURSOR_CROSSHAIR] = [NSCursor crosshairCursor]; + _sapp.macos.standard_cursors[SAPP_MOUSECURSOR_POINTING_HAND] = [NSCursor pointingHandCursor]; + _sapp.macos.standard_cursors[SAPP_MOUSECURSOR_RESIZE_EW] = [NSCursor respondsToSelector:@selector(_windowResizeEastWestCursor)] ? [NSCursor _windowResizeEastWestCursor] : [NSCursor resizeLeftRightCursor]; + _sapp.macos.standard_cursors[SAPP_MOUSECURSOR_RESIZE_NS] = [NSCursor respondsToSelector:@selector(_windowResizeNorthSouthCursor)] ? [NSCursor _windowResizeNorthSouthCursor] : [NSCursor resizeUpDownCursor]; + _sapp.macos.standard_cursors[SAPP_MOUSECURSOR_RESIZE_NWSE] = [NSCursor respondsToSelector:@selector(_windowResizeNorthWestSouthEastCursor)] ? [NSCursor _windowResizeNorthWestSouthEastCursor] : [NSCursor closedHandCursor]; + _sapp.macos.standard_cursors[SAPP_MOUSECURSOR_RESIZE_NESW] = [NSCursor respondsToSelector:@selector(_windowResizeNorthEastSouthWestCursor)] ? [NSCursor _windowResizeNorthEastSouthWestCursor] : [NSCursor closedHandCursor]; + _sapp.macos.standard_cursors[SAPP_MOUSECURSOR_RESIZE_ALL] = [NSCursor closedHandCursor]; + _sapp.macos.standard_cursors[SAPP_MOUSECURSOR_NOT_ALLOWED] = [NSCursor operationNotAllowedCursor]; +} + +_SOKOL_PRIVATE void _sapp_macos_run(const sapp_desc* desc) { + _sapp_init_state(desc); + _sapp_macos_init_keytable(); + [NSApplication sharedApplication]; + + // set the application dock icon as early as possible, otherwise + // the dummy icon will be visible for a short time + sapp_set_icon(&_sapp.desc.icon); + _sapp.macos.app_dlg = [[_sapp_macos_app_delegate alloc] init]; + NSApp.delegate = _sapp.macos.app_dlg; + + // workaround for "no key-up sent while Cmd is pressed" taken from GLFW: + NSEvent* (^keyup_monitor)(NSEvent*) = ^NSEvent* (NSEvent* event) { + if ([event modifierFlags] & NSEventModifierFlagCommand) { + [[NSApp keyWindow] sendEvent:event]; + } + return event; + }; + _sapp.macos.keyup_monitor = [NSEvent addLocalMonitorForEventsMatchingMask:NSEventMaskKeyUp handler:keyup_monitor]; + + [NSApp run]; + // NOTE: [NSApp run] never returns, instead cleanup code + // must be put into applicationWillTerminate +} + +/* MacOS entry function */ +#if !defined(SOKOL_NO_ENTRY) +int main(int argc, char* argv[]) { + sapp_desc desc = sokol_main(argc, argv); + _sapp_macos_run(&desc); + return 0; +} +#endif /* SOKOL_NO_ENTRY */ + +_SOKOL_PRIVATE uint32_t _sapp_macos_mods(NSEvent* ev) { + const NSEventModifierFlags f = (ev == nil) ? NSEvent.modifierFlags : ev.modifierFlags; + const NSUInteger b = NSEvent.pressedMouseButtons; + uint32_t m = 0; + if (f & NSEventModifierFlagShift) { + m |= SAPP_MODIFIER_SHIFT; + } + if (f & NSEventModifierFlagControl) { + m |= SAPP_MODIFIER_CTRL; + } + if (f & NSEventModifierFlagOption) { + m |= SAPP_MODIFIER_ALT; + } + if (f & NSEventModifierFlagCommand) { + m |= SAPP_MODIFIER_SUPER; + } + if (0 != (b & (1<<0))) { + m |= SAPP_MODIFIER_LMB; + } + if (0 != (b & (1<<1))) { + m |= SAPP_MODIFIER_RMB; + } + if (0 != (b & (1<<2))) { + m |= SAPP_MODIFIER_MMB; + } + return m; +} + +_SOKOL_PRIVATE void _sapp_macos_mouse_event(sapp_event_type type, sapp_mousebutton btn, uint32_t mod) { + if (_sapp_events_enabled()) { + _sapp_init_event(type); + _sapp.event.mouse_button = btn; + _sapp.event.modifiers = mod; + _sapp_call_event(&_sapp.event); + } +} + +_SOKOL_PRIVATE void _sapp_macos_key_event(sapp_event_type type, sapp_keycode key, bool repeat, uint32_t mod) { + if (_sapp_events_enabled()) { + _sapp_init_event(type); + _sapp.event.key_code = key; + _sapp.event.key_repeat = repeat; + _sapp.event.modifiers = mod; + _sapp_call_event(&_sapp.event); + } +} + +_SOKOL_PRIVATE void _sapp_macos_app_event(sapp_event_type type) { + if (_sapp_events_enabled()) { + _sapp_init_event(type); + _sapp_call_event(&_sapp.event); + } +} + +/* NOTE: unlike the iOS version of this function, the macOS version + can dynamically update the DPI scaling factor when a window is moved + between HighDPI / LowDPI screens. +*/ +_SOKOL_PRIVATE void _sapp_macos_update_dimensions(void) { + if (_sapp.desc.high_dpi) { + _sapp.dpi_scale = [_sapp.macos.window screen].backingScaleFactor; + } else { + _sapp.dpi_scale = 1.0f; + } + // NOTE: needed because we set layerContentsPlacement to a non-scaling value in windowWillStartLiveResize. + _sapp.macos.view.layer.contentsScale = _sapp.dpi_scale; + const NSRect bounds = [_sapp.macos.view bounds]; + _sapp.window_width = _sapp_roundf_gzero(bounds.size.width); + _sapp.window_height = _sapp_roundf_gzero(bounds.size.height); + #if defined(SOKOL_METAL) + bool dim_changed = _sapp_macos_mtl_update_framebuffer_dimensions(bounds); + #elif defined(SOKOL_GLCORE) + bool dim_changed = _sapp_macos_gl_update_framebuffer_dimensions(bounds); + #elif defined(SOKOL_WGPU) + bool dim_changed = _sapp_macos_wgpu_update_framebuffer_dimensions(bounds); + #endif + if (dim_changed && !_sapp.first_frame) { + _sapp_macos_app_event(SAPP_EVENTTYPE_RESIZED); + } +} + +_SOKOL_PRIVATE void _sapp_macos_toggle_fullscreen(void) { + /* NOTE: the _sapp.fullscreen flag is also notified by the + windowDidEnterFullscreen / windowDidExitFullscreen + event handlers + */ + _sapp.fullscreen = !_sapp.fullscreen; + [_sapp.macos.window toggleFullScreen:nil]; +} + +_SOKOL_PRIVATE void _sapp_macos_set_clipboard_string(const char* str) { + @autoreleasepool { + NSPasteboard* pasteboard = [NSPasteboard generalPasteboard]; + [pasteboard declareTypes:@[NSPasteboardTypeString] owner:nil]; + [pasteboard setString:@(str) forType:NSPasteboardTypeString]; + } +} + +_SOKOL_PRIVATE const char* _sapp_macos_get_clipboard_string(void) { + SOKOL_ASSERT(_sapp.clipboard.buffer); + @autoreleasepool { + _sapp.clipboard.buffer[0] = 0; + NSPasteboard* pasteboard = [NSPasteboard generalPasteboard]; + if (![[pasteboard types] containsObject:NSPasteboardTypeString]) { + return _sapp.clipboard.buffer; + } + NSString* str = [pasteboard stringForType:NSPasteboardTypeString]; + if (!str) { + return _sapp.clipboard.buffer; + } + _sapp_strcpy([str UTF8String], _sapp.clipboard.buffer, (size_t)_sapp.clipboard.buf_size); + } + return _sapp.clipboard.buffer; +} + +_SOKOL_PRIVATE void _sapp_macos_update_window_title(void) { + [_sapp.macos.window setTitle: [NSString stringWithUTF8String:_sapp.window_title]]; +} + +_SOKOL_PRIVATE void _sapp_macos_mouse_update_from_nspoint(NSPoint mouse_pos, bool clear_dxdy) { + if (!_sapp.mouse.locked) { + float new_x = mouse_pos.x * _sapp.dpi_scale; + float new_y = _sapp.framebuffer_height - (mouse_pos.y * _sapp.dpi_scale) - 1; + if (clear_dxdy) { + _sapp.mouse.dx = 0.0f; + _sapp.mouse.dy = 0.0f; + } else if (_sapp.mouse.pos_valid) { + // don't update dx/dy in the very first update + _sapp.mouse.dx = new_x - _sapp.mouse.x; + _sapp.mouse.dy = new_y - _sapp.mouse.y; + } + _sapp.mouse.x = new_x; + _sapp.mouse.y = new_y; + _sapp.mouse.pos_valid = true; + } +} + +_SOKOL_PRIVATE void _sapp_macos_mouse_update_from_nsevent(NSEvent* event, bool clear_dxdy) { + _sapp_macos_mouse_update_from_nspoint(event.locationInWindow, clear_dxdy); +} + +_SOKOL_PRIVATE void _sapp_macos_show_mouse(bool visible) { + /* NOTE: this function is only called when the mouse visibility actually changes */ + if (visible) { + CGDisplayShowCursor(kCGDirectMainDisplay); + } else { + CGDisplayHideCursor(kCGDirectMainDisplay); + } +} + +_SOKOL_PRIVATE void _sapp_macos_lock_mouse(bool lock) { + if (lock == _sapp.mouse.locked) { + return; + } + _sapp.mouse.dx = 0.0f; + _sapp.mouse.dy = 0.0f; + _sapp.mouse.locked = lock; + /* + NOTE that this code doesn't warp the mouse cursor to the window + center as everybody else does it. This lead to a spike in the + *second* mouse-moved event after the warp happened. The + mouse centering doesn't seem to be required (mouse-moved events + are reported correctly even when the cursor is at an edge of the screen). + + NOTE also that the hide/show of the mouse cursor should properly + stack with calls to sapp_show_mouse() + */ + if (_sapp.mouse.locked) { + CGAssociateMouseAndMouseCursorPosition(NO); + [NSCursor hide]; + } else { + [NSCursor unhide]; + CGAssociateMouseAndMouseCursorPosition(YES); + } +} + +_SOKOL_PRIVATE void _sapp_macos_update_cursor(sapp_mouse_cursor cursor, bool shown) { + // show/hide cursor only if visibility status has changed (required because show/hide stacks) + if (shown != _sapp.mouse.shown) { + if (shown) { + [NSCursor unhide]; + } else { + [NSCursor hide]; + } + } + + // update cursor + SOKOL_ASSERT((cursor >= 0) && (cursor < _SAPP_MOUSECURSOR_NUM)); + NSCursor* ns_cursor = 0; + if (_sapp.custom_cursor_bound[cursor]) { + SOKOL_ASSERT(_sapp.macos.custom_cursors[cursor]); + ns_cursor = _sapp.macos.custom_cursors[cursor]; + } else if (_sapp.macos.standard_cursors[cursor]) { + ns_cursor = _sapp.macos.standard_cursors[cursor]; + } else { + ns_cursor = [NSCursor arrowCursor]; + } + [ns_cursor set]; +} + +_SOKOL_PRIVATE bool _sapp_macos_make_custom_mouse_cursor(sapp_mouse_cursor cursor, const sapp_image_desc* desc) { + SOKOL_ASSERT((cursor >= 0) && (cursor < _SAPP_MOUSECURSOR_NUM)); + SOKOL_ASSERT(_sapp.macos.custom_cursors[cursor] == nil); + + // NOTE: see glfw for reference https://github.com/glfw/glfw/blob/ac10768495837eb98da27d01fe706073d6d251c2/src/cocoa_window.m#L1712 + NSBitmapImageRep* rep = [[NSBitmapImageRep alloc] + initWithBitmapDataPlanes:NULL + pixelsWide:desc->width + pixelsHigh:desc->height + bitsPerSample:8 + samplesPerPixel:4 + hasAlpha:YES + isPlanar:NO + colorSpaceName:NSCalibratedRGBColorSpace + bitmapFormat:NSBitmapFormatAlphaNonpremultiplied + bytesPerRow:desc->width * 4 + bitsPerPixel:32]; + if (rep != nil) { + memcpy([rep bitmapData], desc->pixels.ptr, (size_t) (desc->width * desc->height * 4)); + + NSImage* native = [[NSImage alloc] initWithSize:NSMakeSize(desc->width, desc->height)]; + SOKOL_ASSERT(native); + [native addRepresentation:rep]; + + _sapp.macos.custom_cursors[cursor] = [[NSCursor alloc] + initWithImage:native + hotSpot:NSMakePoint(desc->cursor_hotspot_x, desc->cursor_hotspot_y)]; + SOKOL_ASSERT(_sapp.macos.custom_cursors[cursor] != nil); + + _SAPP_OBJC_RELEASE(native); + _SAPP_OBJC_RELEASE(rep); + return true; + } + return false; +} + +_SOKOL_PRIVATE void _sapp_macos_destroy_custom_mouse_cursor(sapp_mouse_cursor cursor) { + SOKOL_ASSERT((cursor >= 0) && (cursor < _SAPP_MOUSECURSOR_NUM)); + SOKOL_ASSERT(_sapp.macos.custom_cursors[cursor] != nil); + _SAPP_OBJC_RELEASE(_sapp.macos.custom_cursors[cursor]); +} + +_SOKOL_PRIVATE void _sapp_macos_set_icon(const sapp_icon_desc* icon_desc, int num_images) { + NSDockTile* dock_tile = NSApp.dockTile; + const int wanted_width = (int) dock_tile.size.width; + const int wanted_height = (int) dock_tile.size.height; + const int img_index = _sapp_image_bestmatch(icon_desc->images, num_images, wanted_width, wanted_height); + const sapp_image_desc* img_desc = &icon_desc->images[img_index]; + + CGColorSpaceRef cg_color_space = CGColorSpaceCreateDeviceRGB(); + CFDataRef cf_data = CFDataCreate(kCFAllocatorDefault, (const UInt8*)img_desc->pixels.ptr, (CFIndex)img_desc->pixels.size); + CGDataProviderRef cg_data_provider = CGDataProviderCreateWithCFData(cf_data); + CGImageRef cg_img = CGImageCreate( + (size_t)img_desc->width, // width + (size_t)img_desc->height, // height + 8, // bitsPerComponent + 32, // bitsPerPixel + (size_t)img_desc->width * 4,// bytesPerRow + cg_color_space, // space + kCGImageAlphaLast | kCGImageByteOrderDefault, // bitmapInfo + cg_data_provider, // provider + NULL, // decode + false, // shouldInterpolate + kCGRenderingIntentDefault); + CFRelease(cf_data); + CGDataProviderRelease(cg_data_provider); + CGColorSpaceRelease(cg_color_space); + + NSImage* ns_image = [[NSImage alloc] initWithCGImage:cg_img size:dock_tile.size]; + dock_tile.contentView = [NSImageView imageViewWithImage:ns_image]; + [dock_tile display]; + _SAPP_OBJC_RELEASE(ns_image); + CGImageRelease(cg_img); +} + +_SOKOL_PRIVATE void _sapp_macos_frame(void) { + // NOTE: DO NOT call _sapp_macos_update_dimensions() function from within the + // frame callback (at least when called from MTKView's drawRect function). + // This will trigger a chicken-egg situation that triggers a + // Metal validation layer error about different render target sizes. + _sapp_timing_measure(&_sapp.timing); + #if defined(_SAPP_ANY_GL) + glGetIntegerv(GL_FRAMEBUFFER_BINDING, (GLint*)&_sapp.gl.framebuffer); + #endif + @autoreleasepool { + #if defined(SOKOL_WGPU) + _sapp_wgpu_frame(); + #else + _sapp_frame(); + #endif + } + #if defined(_SAPP_ANY_GL) + [[_sapp.macos.view openGLContext] flushBuffer]; + #endif + if (_sapp.quit_requested || _sapp.quit_ordered) { + [_sapp.macos.window performClose:nil]; + } +} + +@implementation _sapp_macos_app_delegate +- (void)applicationDidFinishLaunching:(NSNotification*)aNotification { + _SOKOL_UNUSED(aNotification); + _sapp_macos_init_cursors(); + if ((_sapp.window_width == 0) || (_sapp.window_height == 0)) { + // use 4/5 of screen size as default size + NSRect screen_rect = NSScreen.mainScreen.frame; + if (_sapp.window_width == 0) { + _sapp.window_width = _sapp_roundf_gzero((screen_rect.size.width * 4.0f) / 5.0f); + } + if (_sapp.window_height == 0) { + _sapp.window_height = _sapp_roundf_gzero((screen_rect.size.height * 4.0f) / 5.0f); + } + } + const NSUInteger style = + NSWindowStyleMaskTitled | + NSWindowStyleMaskClosable | + NSWindowStyleMaskMiniaturizable | + NSWindowStyleMaskResizable; + NSRect window_rect = NSMakeRect(0, 0, _sapp.window_width, _sapp.window_height); + _sapp.macos.window = [[_sapp_macos_window alloc] + initWithContentRect:window_rect + styleMask:style + backing:NSBackingStoreBuffered + defer:NO]; + _sapp.macos.window.releasedWhenClosed = NO; // this is necessary for proper cleanup in applicationWillTerminate + _sapp.macos.window.title = [NSString stringWithUTF8String:_sapp.window_title]; + _sapp.macos.window.acceptsMouseMovedEvents = YES; + _sapp.macos.window.restorable = YES; + + _sapp.macos.win_dlg = [[_sapp_macos_window_delegate alloc] init]; + _sapp.macos.window.delegate = _sapp.macos.win_dlg; + #if defined(SOKOL_METAL) + _sapp_macos_mtl_init(); + #elif defined(SOKOL_GLCORE) + _sapp_macos_gl_init(window_rect); + #elif defined(SOKOL_WGPU) + _sapp_macos_wgpu_init(); + #endif + _sapp.macos.window.contentView = _sapp.macos.view; + [_sapp.macos.window makeFirstResponder:_sapp.macos.view]; + [_sapp.macos.window center]; + _sapp.valid = true; + NSApp.activationPolicy = NSApplicationActivationPolicyRegular; + if (_sapp.fullscreen) { + /* ^^^ on GL, this already toggles a rendered frame, so set the valid flag before */ + [_sapp.macos.window toggleFullScreen:self]; + } + [NSApp activateIgnoringOtherApps:YES]; + [_sapp.macos.window makeKeyAndOrderFront:nil]; + _sapp_macos_update_dimensions(); + [NSEvent setMouseCoalescingEnabled:NO]; + + // workaround for window not being focused during a long init callback + // for details see: https://github.com/floooh/sokol/pull/982 + // also see: https://gitlab.gnome.org/GNOME/gtk/-/issues/2342 + NSEvent *focusevent = [NSEvent otherEventWithType:NSEventTypeAppKitDefined + location:NSZeroPoint + modifierFlags:0x40 + timestamp:0 + windowNumber:0 + context:nil + subtype:NSEventSubtypeApplicationActivated + data1:0 + data2:0]; + [NSApp postEvent:focusevent atStart:YES]; +} + +- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication*)sender { + _SOKOL_UNUSED(sender); + return YES; +} + +- (void)applicationWillTerminate:(NSNotification*)notification { + _SOKOL_UNUSED(notification); + _sapp_call_cleanup(); + _sapp_macos_discard_state(); + _sapp_discard_state(); +} +@end + +@implementation _sapp_macos_window_delegate +- (BOOL)windowShouldClose:(id)sender { + _SOKOL_UNUSED(sender); + // only give user-code a chance to intervene when sapp_quit() wasn't already called + if (!_sapp.quit_ordered) { + // if window should be closed and event handling is enabled, give user code + // a chance to intervene via sapp_cancel_quit() + _sapp.quit_requested = true; + _sapp_macos_app_event(SAPP_EVENTTYPE_QUIT_REQUESTED); + /* user code hasn't intervened, quit the app */ + if (_sapp.quit_requested) { + _sapp.quit_ordered = true; + } + } + if (_sapp.quit_ordered) { + return YES; + } else { + return NO; + } +} + +- (void)windowWillStartLiveResize:(NSNotification *)notification { + #if defined(SOKOL_METAL) || defined(SOKOL_WGPU) + // Work around the MTKView/CAMetalLayer resizing glitch by "anchoring" the layer to the window corner opposite + // to the currently manipulated corner (or edge). This prevents the content stretching back and + // forth during resizing. This is a workaround for this issue: https://github.com/floooh/sokol/issues/700 + // Can be removed if/when migrating to CAMetalLayer: https://github.com/floooh/sokol/issues/727 + bool resizing_from_left = _sapp.mouse.x < _sapp.window_width/2; + bool resizing_from_top = _sapp.mouse.y < _sapp.window_height/2; + NSViewLayerContentsPlacement placement; + if (resizing_from_left) { + placement = resizing_from_top ? NSViewLayerContentsPlacementBottomRight : NSViewLayerContentsPlacementTopRight; + } else { + placement = resizing_from_top ? NSViewLayerContentsPlacementBottomLeft : NSViewLayerContentsPlacementTopLeft; + } + _sapp.macos.view.layerContentsPlacement = placement; + #endif +} + +- (void)windowDidResize:(NSNotification*)notification { + _SOKOL_UNUSED(notification); + _sapp_macos_update_dimensions(); +} + +- (void)windowDidChangeScreen:(NSNotification*)notification { + _SOKOL_UNUSED(notification); + _sapp_timing_reset(&_sapp.timing); + _sapp_macos_update_dimensions(); +} + +- (void)windowDidMiniaturize:(NSNotification*)notification { + _SOKOL_UNUSED(notification); + _sapp_macos_app_event(SAPP_EVENTTYPE_ICONIFIED); +} + +- (void)windowDidDeminiaturize:(NSNotification*)notification { + _SOKOL_UNUSED(notification); + _sapp_macos_app_event(SAPP_EVENTTYPE_RESTORED); +} + +- (void)windowDidBecomeKey:(NSNotification*)notification { + _SOKOL_UNUSED(notification); + _sapp_macos_app_event(SAPP_EVENTTYPE_FOCUSED); +} + +- (void)windowDidResignKey:(NSNotification*)notification { + _SOKOL_UNUSED(notification); + _sapp_macos_app_event(SAPP_EVENTTYPE_UNFOCUSED); +} + +- (void)windowDidEnterFullScreen:(NSNotification*)notification { + _SOKOL_UNUSED(notification); + _sapp.fullscreen = true; +} + +- (void)windowDidExitFullScreen:(NSNotification*)notification { + _SOKOL_UNUSED(notification); + _sapp.fullscreen = false; +} +@end + +@implementation _sapp_macos_window +- (instancetype)initWithContentRect:(NSRect)contentRect + styleMask:(NSWindowStyleMask)style + backing:(NSBackingStoreType)backingStoreType + defer:(BOOL)flag { + if (self = [super initWithContentRect:contentRect styleMask:style backing:backingStoreType defer:flag]) { + #if __MAC_OS_X_VERSION_MAX_ALLOWED >= 101300 + [self registerForDraggedTypes:[NSArray arrayWithObject:NSPasteboardTypeFileURL]]; + #endif + } + return self; +} + +- (NSDragOperation)draggingEntered:(id)sender { + return NSDragOperationCopy; +} + +- (NSDragOperation)draggingUpdated:(id)sender { + return NSDragOperationCopy; +} + +- (BOOL)performDragOperation:(id)sender { + #if __MAC_OS_X_VERSION_MAX_ALLOWED >= 101300 + NSPasteboard *pboard = [sender draggingPasteboard]; + if ([pboard.types containsObject:NSPasteboardTypeFileURL]) { + _sapp_clear_drop_buffer(); + _sapp.drop.num_files = ((int)pboard.pasteboardItems.count > _sapp.drop.max_files) ? _sapp.drop.max_files : (int)pboard.pasteboardItems.count; + bool drop_failed = false; + for (int i = 0; i < _sapp.drop.num_files; i++) { + NSURL *fileUrl = [NSURL fileURLWithPath:[pboard.pasteboardItems[(NSUInteger)i] stringForType:NSPasteboardTypeFileURL]]; + if (!_sapp_strcpy(fileUrl.standardizedURL.path.UTF8String, _sapp_dropped_file_path_ptr(i), (size_t)_sapp.drop.max_path_length)) { + _SAPP_ERROR(DROPPED_FILE_PATH_TOO_LONG); + drop_failed = true; + break; + } + } + if (!drop_failed) { + if (_sapp_events_enabled()) { + _sapp_macos_mouse_update_from_nspoint(sender.draggingLocation, true); + _sapp_init_event(SAPP_EVENTTYPE_FILES_DROPPED); + _sapp.event.modifiers = _sapp_macos_mods(nil); + _sapp_call_event(&_sapp.event); + } + } else { + _sapp_clear_drop_buffer(); + _sapp.drop.num_files = 0; + } + return YES; + } + #endif + return NO; +} +@end + +@implementation _sapp_macos_view +#if defined(SOKOL_GLCORE) +- (void)timerFired:(id)sender { + _SOKOL_UNUSED(sender); + [self setNeedsDisplay:YES]; +} +- (void)prepareOpenGL { + [super prepareOpenGL]; + GLint swapInt = 1; + NSOpenGLContext* ctx = [_sapp.macos.view openGLContext]; + [ctx setValues:&swapInt forParameter:NSOpenGLContextParameterSwapInterval]; + [ctx makeCurrentContext]; +} +#endif + +#if defined(SOKOL_WGPU) +- (void)displayLinkFired:(id)sender { + _SOKOL_UNUSED(sender); + _sapp_macos_frame(); +} +#else +- (void)drawRect:(NSRect)rect { + _SOKOL_UNUSED(rect); + _sapp_macos_frame(); +} +#endif + +- (BOOL)isOpaque { + return YES; +} +- (BOOL)canBecomeKeyView { + return YES; +} +- (BOOL)acceptsFirstResponder { + return YES; +} +- (void)updateTrackingAreas { + if (_sapp.macos.tracking_area != nil) { + [self removeTrackingArea:_sapp.macos.tracking_area]; + _SAPP_OBJC_RELEASE(_sapp.macos.tracking_area); + } + const NSTrackingAreaOptions options = NSTrackingMouseEnteredAndExited | + NSTrackingActiveInKeyWindow | + NSTrackingEnabledDuringMouseDrag | + NSTrackingCursorUpdate | + NSTrackingInVisibleRect | + NSTrackingAssumeInside; + _sapp.macos.tracking_area = [[NSTrackingArea alloc] initWithRect:[self bounds] options:options owner:self userInfo:nil]; + [self addTrackingArea:_sapp.macos.tracking_area]; + [super updateTrackingAreas]; +} + +// helper function to make GL context active +static void _sapp_gl_make_current(void) { + #if defined(SOKOL_GLCORE) + [[_sapp.macos.view openGLContext] makeCurrentContext]; + #endif +} + +- (void)mouseEntered:(NSEvent*)event { + _sapp_gl_make_current(); + _sapp_macos_mouse_update_from_nsevent(event, true); + /* don't send mouse enter/leave while dragging (so that it behaves the same as + on Windows while SetCapture is active + */ + if (0 == _sapp.macos.mouse_buttons) { + _sapp_macos_mouse_event(SAPP_EVENTTYPE_MOUSE_ENTER, SAPP_MOUSEBUTTON_INVALID, _sapp_macos_mods(event)); + } +} +- (void)mouseExited:(NSEvent*)event { + _sapp_gl_make_current(); + _sapp_macos_mouse_update_from_nsevent(event, true); + if (0 == _sapp.macos.mouse_buttons) { + _sapp_macos_mouse_event(SAPP_EVENTTYPE_MOUSE_LEAVE, SAPP_MOUSEBUTTON_INVALID, _sapp_macos_mods(event)); + } +} +- (void)mouseDown:(NSEvent*)event { + _sapp_gl_make_current(); + _sapp_macos_mouse_update_from_nsevent(event, false); + _sapp_macos_mouse_event(SAPP_EVENTTYPE_MOUSE_DOWN, SAPP_MOUSEBUTTON_LEFT, _sapp_macos_mods(event)); + _sapp.macos.mouse_buttons |= (1< 0.0f) || (_sapp_absf(dy) > 0.0f)) { + _sapp_init_event(SAPP_EVENTTYPE_MOUSE_SCROLL); + _sapp.event.modifiers = _sapp_macos_mods(event); + _sapp.event.scroll_x = dx; + _sapp.event.scroll_y = dy; + _sapp_call_event(&_sapp.event); + } + } +} +- (void)keyDown:(NSEvent*)event { + if (_sapp_events_enabled()) { + _sapp_gl_make_current(); + const uint32_t mods = _sapp_macos_mods(event); + const sapp_keycode key_code = _sapp_translate_key(event.keyCode); + _sapp_macos_key_event(SAPP_EVENTTYPE_KEY_DOWN, key_code, event.isARepeat, mods); + const NSString* chars = event.characters; + const NSUInteger len = chars.length; + if (len > 0) { + _sapp_init_event(SAPP_EVENTTYPE_CHAR); + _sapp.event.modifiers = mods; + for (NSUInteger i = 0; i < len; i++) { + const unichar codepoint = [chars characterAtIndex:i]; + if ((codepoint & 0xFF00) == 0xF700) { + continue; + } + _sapp.event.char_code = codepoint; + _sapp.event.key_repeat = event.isARepeat; + _sapp_call_event(&_sapp.event); + } + } + /* if this is a Cmd+V (paste), also send a CLIPBOARD_PASTE event */ + if (_sapp.clipboard.enabled && (mods == SAPP_MODIFIER_SUPER) && (key_code == SAPP_KEYCODE_V)) { + _sapp_init_event(SAPP_EVENTTYPE_CLIPBOARD_PASTED); + _sapp_call_event(&_sapp.event); + } + } +} + +- (BOOL)performKeyEquivalent:(NSEvent*)event { + // fixes Ctrl-Tab keydown not triggering a keyDown event + // + // NOTE: it seems that Ctrl-F1 cannot be intercepted the same way, but since + // this enabled critical accessibility features that's probably a good thing. + switch (_sapp_translate_key(event.keyCode)) { + case SAPP_KEYCODE_TAB: + [_sapp.macos.view keyDown:event]; + return YES; + default: + return NO; + } +} + +- (void)keyUp:(NSEvent*)event { + _sapp_gl_make_current(); + _sapp_macos_key_event(SAPP_EVENTTYPE_KEY_UP, + _sapp_translate_key(event.keyCode), + event.isARepeat, + _sapp_macos_mods(event)); +} + +- (void)flagsChanged:(NSEvent*)event { + const uint32_t old_f = _sapp.macos.flags_changed_store; + const uint32_t new_f = (uint32_t)event.modifierFlags; + _sapp.macos.flags_changed_store = new_f; + sapp_keycode key_code = SAPP_KEYCODE_INVALID; + bool down = false; + if ((new_f ^ old_f) & NSEventModifierFlagShift) { + key_code = SAPP_KEYCODE_LEFT_SHIFT; + down = 0 != (new_f & NSEventModifierFlagShift); + } + if ((new_f ^ old_f) & NSEventModifierFlagControl) { + key_code = SAPP_KEYCODE_LEFT_CONTROL; + down = 0 != (new_f & NSEventModifierFlagControl); + } + if ((new_f ^ old_f) & NSEventModifierFlagOption) { + key_code = SAPP_KEYCODE_LEFT_ALT; + down = 0 != (new_f & NSEventModifierFlagOption); + } + if ((new_f ^ old_f) & NSEventModifierFlagCommand) { + key_code = SAPP_KEYCODE_LEFT_SUPER; + down = 0 != (new_f & NSEventModifierFlagCommand); + } + if (key_code != SAPP_KEYCODE_INVALID) { + _sapp_macos_key_event(down ? SAPP_EVENTTYPE_KEY_DOWN : SAPP_EVENTTYPE_KEY_UP, + key_code, + false, + _sapp_macos_mods(event)); + } +} +- (void)cursorUpdate:(NSEvent *)event { + _sapp_macos_update_cursor(_sapp.mouse.current_cursor, _sapp.mouse.shown); +} +@end + +#endif // macOS + +// ██ ██████ ███████ +// ██ ██ ██ ██ +// ██ ██ ██ ███████ +// ██ ██ ██ ██ +// ██ ██████ ███████ +// +// >>ios +#if defined(_SAPP_IOS) + +#if defined(SOKOL_METAL) +_SOKOL_PRIVATE void _sapp_ios_mtl_init(void) { + const NSInteger max_fps = UIScreen.mainScreen.maximumFramesPerSecond; + _sapp.ios.mtl_device = MTLCreateSystemDefaultDevice(); + _sapp.ios.view = [[_sapp_ios_view alloc] init]; + _sapp.ios.view.preferredFramesPerSecond = max_fps / _sapp.swap_interval; + _sapp.ios.view.device = _sapp.ios.mtl_device; + _sapp.ios.view.colorPixelFormat = MTLPixelFormatBGRA8Unorm; + _sapp.ios.view.depthStencilPixelFormat = MTLPixelFormatDepth32Float_Stencil8; + _sapp.ios.view.sampleCount = (NSUInteger)_sapp.sample_count; + /* NOTE: iOS MTKView seems to ignore thew view's contentScaleFactor + and automatically renders at Retina resolution. We'll disable + autoResize and instead do the resizing in _sapp_ios_update_dimensions() + */ + _sapp.ios.view.autoResizeDrawable = false; + _sapp.ios.view.userInteractionEnabled = YES; +#if !defined(_SAPP_TVOS) + _sapp.ios.view.multipleTouchEnabled = YES; +#endif + _sapp.ios.view_ctrl = [[UIViewController alloc] init]; + _sapp.ios.view_ctrl.modalPresentationStyle = UIModalPresentationFullScreen; + _sapp.ios.view_ctrl.view = _sapp.ios.view; + _sapp.ios.window.rootViewController = _sapp.ios.view_ctrl; +} + +_SOKOL_PRIVATE void _sapp_ios_mtl_discard_state(void) { + _SAPP_OBJC_RELEASE(_sapp.ios.view_ctrl); + _SAPP_OBJC_RELEASE(_sapp.ios.mtl_device); +} + +_SOKOL_PRIVATE bool _sapp_ios_mtl_update_framebuffer_dimensions(CGRect screen_rect) { + _sapp.framebuffer_width = _sapp_roundf_gzero(screen_rect.size.width * _sapp.dpi_scale); + _sapp.framebuffer_height = _sapp_roundf_gzero(screen_rect.size.height * _sapp.dpi_scale); + const CGSize fb_size = _sapp.ios.view.drawableSize; + int cur_fb_width = _sapp_roundf_gzero(fb_size.width); + int cur_fb_height = _sapp_roundf_gzero(fb_size.height); + bool dim_changed = (_sapp.framebuffer_width != cur_fb_width) || (_sapp.framebuffer_height != cur_fb_height); + if (dim_changed) { + const CGSize drawable_size = { (CGFloat) _sapp.framebuffer_width, (CGFloat) _sapp.framebuffer_height }; + _sapp.ios.view.drawableSize = drawable_size; + } + return dim_changed; +} +#endif + +#if defined(SOKOL_GLES3) +_SOKOL_PRIVATE void _sapp_ios_gles3_init(CGRect screen_rect) { + const NSInteger max_fps = UIScreen.mainScreen.maximumFramesPerSecond; + _sapp.ios.eagl_ctx = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3]; + _sapp.ios.view = [[_sapp_ios_view alloc] initWithFrame:screen_rect]; + _sapp.ios.view.drawableColorFormat = GLKViewDrawableColorFormatRGBA8888; + _sapp.ios.view.drawableDepthFormat = GLKViewDrawableDepthFormat24; + _sapp.ios.view.drawableStencilFormat = GLKViewDrawableStencilFormatNone; + GLKViewDrawableMultisample msaa = _sapp.sample_count > 1 ? GLKViewDrawableMultisample4X : GLKViewDrawableMultisampleNone; + _sapp.ios.view.drawableMultisample = msaa; + _sapp.ios.view.context = _sapp.ios.eagl_ctx; + _sapp.ios.view.enableSetNeedsDisplay = NO; + _sapp.ios.view.userInteractionEnabled = YES; + _sapp.ios.view.multipleTouchEnabled = YES; + // on GLKView, contentScaleFactor appears to work just fine! + if (_sapp.desc.high_dpi) { + _sapp.ios.view.contentScaleFactor = _sapp.dpi_scale; + } else { + _sapp.ios.view.contentScaleFactor = 1.0; + } + _sapp.ios.view_ctrl = [[GLKViewController alloc] init]; + _sapp.ios.view_ctrl.view = _sapp.ios.view; + _sapp.ios.view_ctrl.preferredFramesPerSecond = max_fps / _sapp.swap_interval; + _sapp.ios.window.rootViewController = _sapp.ios.view_ctrl; +} + +_SOKOL_PRIVATE void _sapp_ios_gles3_discard_state(void) { + _SAPP_OBJC_RELEASE(_sapp.ios.view_ctrl); + _SAPP_OBJC_RELEASE(_sapp.ios.eagl_ctx); +} + +_SOKOL_PRIVATE bool _sapp_ios_gles3_update_framebuffer_dimensions(CGRect screen_rect) { + _sapp.framebuffer_width = _sapp_roundf_gzero(screen_rect.size.width * _sapp.dpi_scale); + _sapp.framebuffer_height = _sapp_roundf_gzero(screen_rect.size.height * _sapp.dpi_scale); + int cur_fb_width = _sapp_roundf_gzero(_sapp.ios.view.drawableWidth); + int cur_fb_height = _sapp_roundf_gzero(_sapp.ios.view.drawableHeight); + return (_sapp.framebuffer_width != cur_fb_width) || (_sapp.framebuffer_height != cur_fb_height); +} +#endif + +_SOKOL_PRIVATE void _sapp_ios_discard_state(void) { + // NOTE: it's safe to call [release] on a nil object + _SAPP_OBJC_RELEASE(_sapp.ios.textfield_dlg); + _SAPP_OBJC_RELEASE(_sapp.ios.textfield); + #if defined(SOKOL_METAL) + _sapp_ios_mtl_discard_state(); + #else + _sapp_ios_gles3_discard_state(); + #endif + _SAPP_OBJC_RELEASE(_sapp.ios.view); + _SAPP_OBJC_RELEASE(_sapp.ios.window); +} + +_SOKOL_PRIVATE void _sapp_ios_run(const sapp_desc* desc) { + _sapp_init_state(desc); + static int argc = 1; + static char* argv[] = { (char*)"sokol_app" }; + UIApplicationMain(argc, argv, nil, NSStringFromClass([_sapp_app_delegate class])); +} + +/* iOS entry function */ +#if !defined(SOKOL_NO_ENTRY) +int main(int argc, char* argv[]) { + sapp_desc desc = sokol_main(argc, argv); + _sapp_ios_run(&desc); + return 0; +} +#endif /* SOKOL_NO_ENTRY */ + +_SOKOL_PRIVATE void _sapp_ios_app_event(sapp_event_type type) { + if (_sapp_events_enabled()) { + _sapp_init_event(type); + _sapp_call_event(&_sapp.event); + } +} + +_SOKOL_PRIVATE void _sapp_tvos_press_event(sapp_event_type type, NSSet* presses) { + if (_sapp_events_enabled()) { + for (UIPress *press in presses) { + sapp_keycode key = SAPP_KEYCODE_INVALID; + switch (press.type) { + case UIPressTypeUpArrow: key = SAPP_KEYCODE_UP; break; + case UIPressTypeDownArrow: key = SAPP_KEYCODE_DOWN; break; + case UIPressTypeLeftArrow: key = SAPP_KEYCODE_LEFT; break; + case UIPressTypeRightArrow: key = SAPP_KEYCODE_RIGHT; break; + case UIPressTypeSelect: key = SAPP_KEYCODE_ENTER; break; + case UIPressTypeMenu: key = SAPP_KEYCODE_MENU; break; + case UIPressTypePlayPause: key = SAPP_KEYCODE_PAUSE; break; + default: break; + } + if (key != SAPP_KEYCODE_INVALID) { + _sapp_init_event(type); + _sapp.event.key_code = key; + _sapp.event.key_repeat = false; + _sapp.event.modifiers = 0; + _sapp_call_event(&_sapp.event); + } + } + } +} + +_SOKOL_PRIVATE void _sapp_ios_touch_event(sapp_event_type type, NSSet* touches, UIEvent* event) { + if (_sapp_events_enabled()) { + _sapp_init_event(type); + NSEnumerator* enumerator = event.allTouches.objectEnumerator; + UITouch* ios_touch; + while ((ios_touch = [enumerator nextObject])) { + if ((_sapp.event.num_touches + 1) < SAPP_MAX_TOUCHPOINTS) { + CGPoint ios_pos = [ios_touch locationInView:_sapp.ios.view]; + sapp_touchpoint* cur_point = &_sapp.event.touches[_sapp.event.num_touches++]; + cur_point->identifier = (uintptr_t) ios_touch; + cur_point->pos_x = ios_pos.x * _sapp.dpi_scale; + cur_point->pos_y = ios_pos.y * _sapp.dpi_scale; + cur_point->changed = [touches containsObject:ios_touch]; + } + } + if (_sapp.event.num_touches > 0) { + _sapp_call_event(&_sapp.event); + } + } +} + +_SOKOL_PRIVATE void _sapp_ios_update_dimensions(void) { + CGRect screen_rect = UIScreen.mainScreen.bounds; + _sapp.window_width = _sapp_roundf_gzero(screen_rect.size.width); + _sapp.window_height = _sapp_roundf_gzero(screen_rect.size.height); + #if defined(SOKOL_METAL) + bool dim_changed = _sapp_ios_mtl_update_framebuffer_dimensions(screen_rect); + #else + bool dim_changed = _sapp_ios_gles3_update_framebuffer_dimensions(screen_rect); + #endif + if (dim_changed && !_sapp.first_frame) { + _sapp_ios_app_event(SAPP_EVENTTYPE_RESIZED); + } +} + +_SOKOL_PRIVATE void _sapp_ios_frame(void) { + _sapp_ios_update_dimensions(); + _sapp_frame(); +} + +_SOKOL_PRIVATE void _sapp_ios_show_keyboard(bool shown) { + /* if not happened yet, create an invisible text field */ + if (nil == _sapp.ios.textfield) { + _sapp.ios.textfield_dlg = [[_sapp_textfield_dlg alloc] init]; + _sapp.ios.textfield = [[UITextField alloc] initWithFrame:CGRectMake(10, 10, 100, 50)]; + _sapp.ios.textfield.keyboardType = UIKeyboardTypeDefault; + _sapp.ios.textfield.returnKeyType = UIReturnKeyDefault; + _sapp.ios.textfield.autocapitalizationType = UITextAutocapitalizationTypeNone; + _sapp.ios.textfield.autocorrectionType = UITextAutocorrectionTypeNo; + _sapp.ios.textfield.spellCheckingType = UITextSpellCheckingTypeNo; + _sapp.ios.textfield.hidden = YES; + _sapp.ios.textfield.text = @"x"; + _sapp.ios.textfield.delegate = _sapp.ios.textfield_dlg; + [_sapp.ios.view_ctrl.view addSubview:_sapp.ios.textfield]; + +#if !defined(_SAPP_TVOS) + [[NSNotificationCenter defaultCenter] addObserver:_sapp.ios.textfield_dlg + selector:@selector(keyboardWasShown:) + name:UIKeyboardDidShowNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:_sapp.ios.textfield_dlg + selector:@selector(keyboardWillBeHidden:) + name:UIKeyboardWillHideNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:_sapp.ios.textfield_dlg + selector:@selector(keyboardDidChangeFrame:) + name:UIKeyboardDidChangeFrameNotification object:nil]; +#endif + } + if (shown) { + // setting the text field as first responder brings up the onscreen keyboard + [_sapp.ios.textfield becomeFirstResponder]; + } else { + [_sapp.ios.textfield resignFirstResponder]; + } +} + +@implementation _sapp_app_delegate +- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions { + CGRect screen_rect = UIScreen.mainScreen.bounds; + _sapp.ios.window = [[UIWindow alloc] initWithFrame:screen_rect]; + _sapp.window_width = _sapp_roundf_gzero(screen_rect.size.width); + _sapp.window_height = _sapp_roundf_gzero(screen_rect.size.height); + if (_sapp.desc.high_dpi) { + _sapp.dpi_scale = (float) UIScreen.mainScreen.nativeScale; + } else { + _sapp.dpi_scale = 1.0f; + } + _sapp.framebuffer_width = _sapp_roundf_gzero(_sapp.window_width * _sapp.dpi_scale); + _sapp.framebuffer_height = _sapp_roundf_gzero(_sapp.window_height * _sapp.dpi_scale); + #if defined(SOKOL_METAL) + _sapp_ios_mtl_init(); + #else + _sapp_ios_gles3_init(screen_rect); + #endif + [_sapp.ios.window makeKeyAndVisible]; + _sapp.valid = true; + return YES; +} + +- (void)applicationWillResignActive:(UIApplication *)application { + if (!_sapp.ios.suspended) { + _sapp.ios.suspended = true; + _sapp_ios_app_event(SAPP_EVENTTYPE_SUSPENDED); + } +} + +- (void)applicationDidBecomeActive:(UIApplication *)application { + if (_sapp.ios.suspended) { + _sapp.ios.suspended = false; + _sapp_ios_app_event(SAPP_EVENTTYPE_RESUMED); + } +} + +/* NOTE: this method will rarely ever be called, iOS application + which are terminated by the user are usually killed via signal 9 + by the operating system. +*/ +- (void)applicationWillTerminate:(UIApplication *)application { + _SOKOL_UNUSED(application); + _sapp_call_cleanup(); + _sapp_ios_discard_state(); + _sapp_discard_state(); +} +@end + +@implementation _sapp_textfield_dlg +- (void)keyboardWasShown:(NSNotification*)notif { + _sapp.onscreen_keyboard_shown = true; + /* query the keyboard's size, and modify the content view's size */ +#if !defined(_SAPP_TVOS) + if (_sapp.desc.ios_keyboard_resizes_canvas) { + NSDictionary* info = notif.userInfo; + CGFloat kbd_h = [[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue].size.height; + CGRect view_frame = UIScreen.mainScreen.bounds; + view_frame.size.height -= kbd_h; + _sapp.ios.view.frame = view_frame; + } +#endif +} +- (void)keyboardWillBeHidden:(NSNotification*)notif { + _sapp.onscreen_keyboard_shown = false; + if (_sapp.desc.ios_keyboard_resizes_canvas) { + _sapp.ios.view.frame = UIScreen.mainScreen.bounds; + } +} +- (void)keyboardDidChangeFrame:(NSNotification*)notif { + /* this is for the case when the screen rotation changes while the keyboard is open */ +#if !defined(_SAPP_TVOS) + if (_sapp.onscreen_keyboard_shown && _sapp.desc.ios_keyboard_resizes_canvas) { + NSDictionary* info = notif.userInfo; + CGFloat kbd_h = [[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue].size.height; + CGRect view_frame = UIScreen.mainScreen.bounds; + view_frame.size.height -= kbd_h; + _sapp.ios.view.frame = view_frame; + } +#endif +} +- (BOOL)textField:(UITextField*)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString*)string { + if (_sapp_events_enabled()) { + const NSUInteger len = string.length; + if (len > 0) { + for (NSUInteger i = 0; i < len; i++) { + unichar c = [string characterAtIndex:i]; + if (c >= 32) { + /* ignore surrogates for now */ + if ((c < 0xD800) || (c > 0xDFFF)) { + _sapp_init_event(SAPP_EVENTTYPE_CHAR); + _sapp.event.char_code = c; + _sapp_call_event(&_sapp.event); + } + } + if (c <= 32) { + sapp_keycode k = SAPP_KEYCODE_INVALID; + switch (c) { + case 10: k = SAPP_KEYCODE_ENTER; break; + case 32: k = SAPP_KEYCODE_SPACE; break; + default: break; + } + if (k != SAPP_KEYCODE_INVALID) { + _sapp_init_event(SAPP_EVENTTYPE_KEY_DOWN); + _sapp.event.key_code = k; + _sapp_call_event(&_sapp.event); + _sapp_init_event(SAPP_EVENTTYPE_KEY_UP); + _sapp.event.key_code = k; + _sapp_call_event(&_sapp.event); + } + } + } + } else { + // this was a backspace + _sapp_init_event(SAPP_EVENTTYPE_KEY_DOWN); + _sapp.event.key_code = SAPP_KEYCODE_BACKSPACE; + _sapp_call_event(&_sapp.event); + _sapp_init_event(SAPP_EVENTTYPE_KEY_UP); + _sapp.event.key_code = SAPP_KEYCODE_BACKSPACE; + _sapp_call_event(&_sapp.event); + } + } + return NO; +} +@end + +@implementation _sapp_ios_view +- (void)drawRect:(CGRect)rect { + _SOKOL_UNUSED(rect); + #if defined(_SAPP_ANY_GL) + glGetIntegerv(GL_FRAMEBUFFER_BINDING, (GLint*)&_sapp.gl.framebuffer); + #endif + _sapp_timing_measure(&_sapp.timing); + @autoreleasepool { + _sapp_ios_frame(); + } +} +- (BOOL)isOpaque { + return YES; +} +- (void)pressesBegan:(NSSet *)presses withEvent:(UIPressesEvent *)event { + _sapp_tvos_press_event(SAPP_EVENTTYPE_KEY_DOWN, presses); +} +- (void)pressesChanged:(NSSet *)presses withEvent:(UIPressesEvent *)event { +} +- (void)pressesEnded:(NSSet *)presses withEvent:(UIPressesEvent *)event { + _sapp_tvos_press_event(SAPP_EVENTTYPE_KEY_UP, presses); +} +- (void)pressesCancelled:(NSSet *)presses withEvent:(UIPressesEvent *)event { + _sapp_tvos_press_event(SAPP_EVENTTYPE_KEY_UP, presses); +} +- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent*)event { + _sapp_ios_touch_event(SAPP_EVENTTYPE_TOUCHES_BEGAN, touches, event); +} +- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent*)event { + _sapp_ios_touch_event(SAPP_EVENTTYPE_TOUCHES_MOVED, touches, event); +} +- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent*)event { + _sapp_ios_touch_event(SAPP_EVENTTYPE_TOUCHES_ENDED, touches, event); +} +- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent*)event { + _sapp_ios_touch_event(SAPP_EVENTTYPE_TOUCHES_CANCELLED, touches, event); +} +@end +#endif /* TARGET_OS_IPHONE */ + +#endif /* _SAPP_APPLE */ + +// ███████ ███ ███ ███████ ██████ ██████ ██ ██████ ████████ ███████ ███ ██ +// ██ ████ ████ ██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██ +// █████ ██ ████ ██ ███████ ██ ██████ ██ ██████ ██ █████ ██ ██ ██ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ███████ ██ ██ ███████ ██████ ██ ██ ██ ██ ██ ███████ ██ ████ +// +// >>emscripten +#if defined(_SAPP_EMSCRIPTEN) + +#if defined(EM_JS_DEPS) +EM_JS_DEPS(sokol_app, "$withStackSave,$stringToUTF8OnStack,$findCanvasEventTarget") +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +typedef void (*_sapp_html5_fetch_callback) (const sapp_html5_fetch_response*); + +EMSCRIPTEN_KEEPALIVE void _sapp_emsc_onpaste(const char* str) { + if (_sapp.clipboard.enabled) { + _sapp_strcpy(str, _sapp.clipboard.buffer, (size_t)_sapp.clipboard.buf_size); + if (_sapp_events_enabled()) { + _sapp_init_event(SAPP_EVENTTYPE_CLIPBOARD_PASTED); + _sapp_call_event(&_sapp.event); + } + } +} + +/* https://developer.mozilla.org/en-US/docs/Web/API/WindowEventHandlers/onbeforeunload */ +EMSCRIPTEN_KEEPALIVE int _sapp_html5_get_ask_leave_site(void) { + return _sapp.html5_ask_leave_site ? 1 : 0; +} + +EMSCRIPTEN_KEEPALIVE void _sapp_emsc_begin_drop(int num) { + if (!_sapp.drop.enabled) { + return; + } + if (num < 0) { + num = 0; + } + if (num > _sapp.drop.max_files) { + num = _sapp.drop.max_files; + } + _sapp.drop.num_files = num; + _sapp_clear_drop_buffer(); +} + +EMSCRIPTEN_KEEPALIVE void _sapp_emsc_drop(int i, const char* name) { + /* NOTE: name is only the filename part, not a path */ + if (!_sapp.drop.enabled) { + return; + } + if (0 == name) { + return; + } + SOKOL_ASSERT(_sapp.drop.num_files <= _sapp.drop.max_files); + if ((i < 0) || (i >= _sapp.drop.num_files)) { + return; + } + if (!_sapp_strcpy(name, _sapp_dropped_file_path_ptr(i), (size_t)_sapp.drop.max_path_length)) { + _SAPP_ERROR(DROPPED_FILE_PATH_TOO_LONG); + _sapp.drop.num_files = 0; + } +} + +EMSCRIPTEN_KEEPALIVE void _sapp_emsc_end_drop(int x, int y, int mods) { + if (!_sapp.drop.enabled) { + return; + } + if (0 == _sapp.drop.num_files) { + /* there was an error copying the filenames */ + _sapp_clear_drop_buffer(); + return; + + } + if (_sapp_events_enabled()) { + _sapp.mouse.x = (float)x * _sapp.dpi_scale; + _sapp.mouse.y = (float)y * _sapp.dpi_scale; + _sapp.mouse.dx = 0.0f; + _sapp.mouse.dy = 0.0f; + _sapp_init_event(SAPP_EVENTTYPE_FILES_DROPPED); + // see sapp_js_add_dragndrop_listeners for mods constants + if (mods & 1) { _sapp.event.modifiers |= SAPP_MODIFIER_SHIFT; } + if (mods & 2) { _sapp.event.modifiers |= SAPP_MODIFIER_CTRL; } + if (mods & 4) { _sapp.event.modifiers |= SAPP_MODIFIER_ALT; } + if (mods & 8) { _sapp.event.modifiers |= SAPP_MODIFIER_SUPER; } + _sapp_call_event(&_sapp.event); + } +} + +EMSCRIPTEN_KEEPALIVE void _sapp_emsc_invoke_fetch_cb(int index, int success, int error_code, _sapp_html5_fetch_callback callback, uint32_t fetched_size, void* buf_ptr, uint32_t buf_size, void* user_data) { + sapp_html5_fetch_response response; + _sapp_clear(&response, sizeof(response)); + response.succeeded = (0 != success); + response.error_code = (sapp_html5_fetch_error) error_code; + response.file_index = index; + response.data.ptr = buf_ptr; + response.data.size = fetched_size; + response.buffer.ptr = buf_ptr; + response.buffer.size = buf_size; + response.user_data = user_data; + callback(&response); +} + +// will be called after the request/exitFullscreen promise rejects +// to restore the _sapp.fullscreen flag to the actual fullscreen state +EMSCRIPTEN_KEEPALIVE void _sapp_emsc_set_fullscreen_flag(int f) { + _sapp.fullscreen = (bool)f; +} + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +EM_JS(void, sapp_js_add_beforeunload_listener, (void), { + Module.sokol_beforeunload = (event) => { + if (__sapp_html5_get_ask_leave_site() != 0) { + event.preventDefault(); + event.returnValue = ' '; + } + }; + window.addEventListener('beforeunload', Module.sokol_beforeunload); +}) + +EM_JS(void, sapp_js_remove_beforeunload_listener, (void), { + window.removeEventListener('beforeunload', Module.sokol_beforeunload); +}) + +EM_JS(void, sapp_js_add_clipboard_listener, (void), { + Module.sokol_paste = (event) => { + const pasted_str = event.clipboardData.getData('text'); + withStackSave(() => { + const cstr = stringToUTF8OnStack(pasted_str); + __sapp_emsc_onpaste(cstr); + }); + }; + window.addEventListener('paste', Module.sokol_paste); +}) + +EM_JS(void, sapp_js_remove_clipboard_listener, (void), { + window.removeEventListener('paste', Module.sokol_paste); +}) + +EM_JS(void, sapp_js_write_clipboard, (const char* c_str), { + const str = UTF8ToString(c_str); + const ta = document.createElement('textarea'); + ta.setAttribute('autocomplete', 'off'); + ta.setAttribute('autocorrect', 'off'); + ta.setAttribute('autocapitalize', 'off'); + ta.setAttribute('spellcheck', 'false'); + ta.style.left = -100 + 'px'; + ta.style.top = -100 + 'px'; + ta.style.height = 1; + ta.style.width = 1; + ta.value = str; + document.body.appendChild(ta); + ta.select(); + document.execCommand('copy'); + document.body.removeChild(ta); +}) + +_SOKOL_PRIVATE void _sapp_emsc_set_clipboard_string(const char* str) { + sapp_js_write_clipboard(str); +} + +EM_JS(void, sapp_js_add_dragndrop_listeners, (void), { + Module.sokol_drop_files = []; + Module.sokol_dragenter = (event) => { + event.stopPropagation(); + event.preventDefault(); + }; + Module.sokol_dragleave = (event) => { + event.stopPropagation(); + event.preventDefault(); + }; + Module.sokol_dragover = (event) => { + event.stopPropagation(); + event.preventDefault(); + }; + Module.sokol_drop = (event) => { + event.stopPropagation(); + event.preventDefault(); + const files = event.dataTransfer.files; + Module.sokol_dropped_files = files; + __sapp_emsc_begin_drop(files.length); + for (let i = 0; i < files.length; i++) { + withStackSave(() => { + const cstr = stringToUTF8OnStack(files[i].name); + __sapp_emsc_drop(i, cstr); + }); + } + let mods = 0; + if (event.shiftKey) { mods |= 1; } + if (event.ctrlKey) { mods |= 2; } + if (event.altKey) { mods |= 4; } + if (event.metaKey) { mods |= 8; } + // FIXME? see computation of targetX/targetY in emscripten via getClientBoundingRect + __sapp_emsc_end_drop(event.clientX, event.clientY, mods); + }; + \x2F\x2A\x2A @suppress {missingProperties} \x2A\x2F + const canvas = Module.sapp_emsc_target; + canvas.addEventListener('dragenter', Module.sokol_dragenter, false); + canvas.addEventListener('dragleave', Module.sokol_dragleave, false); + canvas.addEventListener('dragover', Module.sokol_dragover, false); + canvas.addEventListener('drop', Module.sokol_drop, false); +}) + +EM_JS(uint32_t, sapp_js_dropped_file_size, (int index), { + \x2F\x2A\x2A @suppress {missingProperties} \x2A\x2F + const files = Module.sokol_dropped_files; + if ((index < 0) || (index >= files.length)) { + return 0; + } else { + return files[index].size; + } +}) + +EM_JS(void, sapp_js_fetch_dropped_file, (int index, _sapp_html5_fetch_callback callback, void* buf_ptr, uint32_t buf_size, void* user_data), { + const reader = new FileReader(); + reader.onload = (loadEvent) => { + const content = loadEvent.target.result; + if (content.byteLength > buf_size) { + // SAPP_HTML5_FETCH_ERROR_BUFFER_TOO_SMALL + __sapp_emsc_invoke_fetch_cb(index, 0, 1, callback, 0, buf_ptr, buf_size, user_data); + } else { + HEAPU8.set(new Uint8Array(content), buf_ptr); + __sapp_emsc_invoke_fetch_cb(index, 1, 0, callback, content.byteLength, buf_ptr, buf_size, user_data); + } + }; + reader.onerror = () => { + // SAPP_HTML5_FETCH_ERROR_OTHER + __sapp_emsc_invoke_fetch_cb(index, 0, 2, callback, 0, buf_ptr, buf_size, user_data); + }; + \x2F\x2A\x2A @suppress {missingProperties} \x2A\x2F + const files = Module.sokol_dropped_files; + reader.readAsArrayBuffer(files[index]); +}) + +EM_JS(void, sapp_js_remove_dragndrop_listeners, (void), { + \x2F\x2A\x2A @suppress {missingProperties} \x2A\x2F + const canvas = Module.sapp_emsc_target; + canvas.removeEventListener('dragenter', Module.sokol_dragenter); + canvas.removeEventListener('dragleave', Module.sokol_dragleave); + canvas.removeEventListener('dragover', Module.sokol_dragover); + canvas.removeEventListener('drop', Module.sokol_drop); +}) + +EM_JS(void, sapp_js_init, (const char* c_str_target_selector, const char* c_str_document_title), { + if (c_str_document_title !== 0) { + document.title = UTF8ToString(c_str_document_title); + } + const target_selector_str = UTF8ToString(c_str_target_selector); + if (Module['canvas'] !== undefined) { + if (typeof Module['canvas'] === 'object') { + specialHTMLTargets[target_selector_str] = Module['canvas']; + } else { + console.warn("sokol_app.h: Module['canvas'] is set but is not an object"); + } + } + Module.sapp_emsc_target = findCanvasEventTarget(target_selector_str); + if (!Module.sapp_emsc_target) { + console.warn("sokol_app.h: can't find html5_canvas_selector ", target_selector_str); + } + if (!Module.sapp_emsc_target.requestPointerLock) { + console.warn("sokol_app.h: target doesn't support requestPointerLock: ", target_selector_str); + } +}) + +_SOKOL_PRIVATE EM_BOOL _sapp_emsc_pointerlockchange_cb(int emsc_type, const EmscriptenPointerlockChangeEvent* emsc_event, void* user_data) { + _SOKOL_UNUSED(emsc_type); + _SOKOL_UNUSED(user_data); + _sapp.mouse.locked = emsc_event->isActive; + return EM_TRUE; +} + +_SOKOL_PRIVATE EM_BOOL _sapp_emsc_pointerlockerror_cb(int emsc_type, const void* reserved, void* user_data) { + _SOKOL_UNUSED(emsc_type); + _SOKOL_UNUSED(reserved); + _SOKOL_UNUSED(user_data); + _sapp.mouse.locked = false; + _sapp.emsc.mouse_lock_requested = false; + return true; +} + +EM_JS(void, sapp_js_request_pointerlock, (void), { + if (Module.sapp_emsc_target) { + if (Module.sapp_emsc_target.requestPointerLock) { + Module.sapp_emsc_target.requestPointerLock(); + } + } +}) + +EM_JS(void, sapp_js_exit_pointerlock, (void), { + if (document.exitPointerLock) { + document.exitPointerLock(); + } +}) + +_SOKOL_PRIVATE void _sapp_emsc_lock_mouse(bool lock) { + if (lock) { + /* request mouse-lock during event handler invocation (see _sapp_emsc_update_mouse_lock_state) */ + _sapp.emsc.mouse_lock_requested = true; + } else { + /* NOTE: the _sapp.mouse_locked state will be set in the pointerlockchange callback */ + _sapp.emsc.mouse_lock_requested = false; + sapp_js_exit_pointerlock(); + } +} + +/* called from inside event handlers to check if mouse lock had been requested, + and if yes, actually enter mouse lock. +*/ +_SOKOL_PRIVATE void _sapp_emsc_update_mouse_lock_state(void) { + if (_sapp.emsc.mouse_lock_requested) { + _sapp.emsc.mouse_lock_requested = false; + sapp_js_request_pointerlock(); + } +} + +// set mouse cursor type +EM_JS(void, sapp_js_set_cursor, (int cursor_type, int shown, int use_custom_cursor_image), { + if (Module.sapp_emsc_target) { + let cursor; + if (shown === 0) { + cursor = "none"; + } else if (use_custom_cursor_image != 0) { + cursor = Module.__sapp_custom_cursors[cursor_type].css_property; + } else switch (cursor_type) { + case 0: cursor = "auto"; break; // SAPP_MOUSECURSOR_DEFAULT + case 1: cursor = "default"; break; // SAPP_MOUSECURSOR_ARROW + case 2: cursor = "text"; break; // SAPP_MOUSECURSOR_IBEAM + case 3: cursor = "crosshair"; break; // SAPP_MOUSECURSOR_CROSSHAIR + case 4: cursor = "pointer"; break; // SAPP_MOUSECURSOR_POINTING_HAND + case 5: cursor = "ew-resize"; break; // SAPP_MOUSECURSOR_RESIZE_EW + case 6: cursor = "ns-resize"; break; // SAPP_MOUSECURSOR_RESIZE_NS + case 7: cursor = "nwse-resize"; break; // SAPP_MOUSECURSOR_RESIZE_NWSE + case 8: cursor = "nesw-resize"; break; // SAPP_MOUSECURSOR_RESIZE_NESW + case 9: cursor = "all-scroll"; break; // SAPP_MOUSECURSOR_RESIZE_ALL + case 10: cursor = "not-allowed"; break; // SAPP_MOUSECURSOR_NOT_ALLOWED + default: cursor = "auto"; break; + } + Module.sapp_emsc_target.style.cursor = cursor; + } +}) + +_SOKOL_PRIVATE void _sapp_emsc_update_cursor(sapp_mouse_cursor cursor, bool shown) { + SOKOL_ASSERT((cursor >= 0) && (cursor < _SAPP_MOUSECURSOR_NUM)); + bool custom_cursor = _sapp.custom_cursor_bound[cursor]; + sapp_js_set_cursor((int)cursor, shown ? 1 : 0, custom_cursor ? 1 : 0); +} + +EM_JS(void, sapp_js_make_custom_mouse_cursor, (int cursor_slot_idx, int width, int height, const void* pixels_ptr, int hotspot_x, int hotspot_y), { + // encode the cursor pixels into a BMP which then is encoded into an 'object url' + const bmp_hdr_size = 14; + const dib_hdr_size = 124; // common values are 56, I saw 124 for the rgba32-1.bmp file of the test suite included in firefox, and 108 from wikipedia example 2 (transparent) + const pixels_size = width * height * 4; + const bmp_size = bmp_hdr_size + dib_hdr_size + pixels_size; + const bmp = new Uint8Array(bmp_size); + let idx = 0; + const w8 = (val) => { + bmp[idx++] = val & 255; + }; + const w16 = (val) => { + bmp[idx++] = val & 255; + bmp[idx++] = (val >> 8) & 255; + }; + const w32 = (val) => { + bmp[idx++] = val & 255; + bmp[idx++] = (val >> 8) & 255; + bmp[idx++] = (val >> 16) & 255; + bmp[idx++] = (val >> 24) & 255; + }; + + // bmp file header + w8(66); // 'B' + w8(77); // 'M' + w32(bmp_size); + w32(0); // reserved + w32(bmp_hdr_size + dib_hdr_size); // offset to pixel data + assert(idx == bmp_hdr_size); + + // DIB header + w32(dib_hdr_size); // header size + w32(width); + w32(height); + w16(1); // planes + w16(32); // bits per pixel + w32(3); // compression method. 3 = BI_BITFIELDS + w32(pixels_size); // image size + w32(2835); // pixel per metre horizontal + w32(2835); // pixel per metre vertical + w32(0); // colors number + w32(0); // important colors + w32(0x000000ff); // red channel bit mask (big endian) + w32(0x0000ff00); // green channel bit mask (big endian) + w32(0x00ff0000); // blue channel bit mask (big endian) + w32(0xff000000); // alpha channel bit mask (big endian) + w8(66); w8(71); w8(82); w8(115); // color space type: 'sRGB' + idx += 64; // color space stuff, unused for 'Win ' or 'sRGB' + assert(idx == bmp_hdr_size + dib_hdr_size); + const row_pitch = width * 4; + for (let y = 0; y < height; y++) { + const src_idx = pixels_ptr + y * row_pitch; + const dst_idx = idx + (height - y - 1) * row_pitch; + const row_data = HEAPU8.slice(src_idx, src_idx + row_pitch); + bmp.set(row_data, dst_idx); + } + const blob = new Blob([bmp.buffer], { type: 'image/bmp' }); + const url = URL.createObjectURL(blob); + + const cursor_slot = { + css_property: `url('${url}') ${hotspot_x} ${hotspot_y}, auto`, + blob_url: url // so we can release it later + }; + + // Store a reference to the js cursor object in a global table, indexed by its sapp_mouse_cursor + if (!Module.__sapp_custom_cursors) { + Module.__sapp_custom_cursors = Array().fill(null); + } + Module.__sapp_custom_cursors[cursor_slot_idx] = cursor_slot; +}) + +EM_JS(void, sapp_js_destroy_custom_mouse_cursor, (int cursor_slot_idx), { + if (Module.__sapp_custom_cursors) { + const cursor = Module.__sapp_custom_cursors[cursor_slot_idx]; + URL.revokeObjectURL(cursor.blob_url); // release the url, which should allow the blob to be garbage collected. + Module.__sapp_custom_cursors[cursor_slot_idx] = null; // clear this array entry + } +}) + +_SOKOL_PRIVATE bool _sapp_emsc_make_custom_mouse_cursor(sapp_mouse_cursor cursor, const sapp_image_desc* desc) { + sapp_js_make_custom_mouse_cursor((int)cursor, desc->width, desc->height, desc->pixels.ptr, desc->cursor_hotspot_x, desc->cursor_hotspot_y); + return true; +} + +_SOKOL_PRIVATE void _sapp_emsc_destroy_custom_mouse_cursor(sapp_mouse_cursor cursor) { + sapp_js_destroy_custom_mouse_cursor((int) cursor); +} + +// NOTE: this callback is needed to react to the user actively leaving fullscreen mode via Esc +_SOKOL_PRIVATE EM_BOOL _sapp_emsc_fullscreenchange_cb(int emsc_type, const EmscriptenFullscreenChangeEvent* emsc_event, void* user_data) { + _SOKOL_UNUSED(emsc_type); + _SOKOL_UNUSED(user_data); + _sapp.fullscreen = emsc_event->isFullscreen; + return true; +} + +EM_JS(void, sapp_js_toggle_fullscreen, (void), { + const canvas = Module.sapp_emsc_target; + if (canvas) { + // NOTE: Safari had the prefix until 2023, Firefox until 2018 + const fullscreenElement = document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement; + let p = undefined; + if (!fullscreenElement) { + if (canvas.requestFullscreen) { + p = canvas.requestFullscreen(); + } else if (canvas.webkitRequestFullscreen) { + p = canvas.webkitRequestFullscreen(); + } else if (canvas.mozRequestFullScreen) { + p = canvas.mozRequestFullScreen(); + } + if (p) { + p.catch((err) => { + console.warn('sapp_js_toggle_fullscreen(): failed to enter fullscreen mode with', err); + __sapp_emsc_set_fullscreen_flag(0); + }); + } else { + console.warn('sapp_js_toogle_fullscreen(): browser has no [webkit|moz]requestFullscreen function'); + __sapp_emsc_set_fullscreen_flag(0); + } + } else { + if (document.exitFullscreen) { + p = document.exitFullscreen(); + } else if (document.webkitExitFullscreen) { + p = document.webkitExitFullscreen(); + } else if (document.mozCancelFullScreen) { + p = document.mozCancelFullScreen(); + } + if (p) { + p.catch((err) => { + console.warn('sapp_js_toggle_fullscreen(): failed to exit fullscreen mode with', err); + __sapp_emsc_set_fullscreen_flag(1); + }); + } else { + console.warn('sapp_js_toggle_fullscreen(): browser has no [wekbit|moz]exitFullscreen'); + // NOTE: don't need to explicitly set the fullscreen flag here + } + } + } +}) + +_SOKOL_PRIVATE void _sapp_emsc_toggle_fullscreen(void) { + // toggle the fullscreen flag preliminary, this may be undone + // when requesting/exiting fullscreen mode actually fails + _sapp.fullscreen = !_sapp.fullscreen; + sapp_js_toggle_fullscreen(); +} + +/* JS helper functions to update browser tab favicon */ +EM_JS(void, sapp_js_clear_favicon, (void), { + const link = document.getElementById('sokol-app-favicon'); + if (link) { + document.head.removeChild(link); + } +}) + +EM_JS(void, sapp_js_set_favicon, (int w, int h, const uint8_t* pixels), { + const canvas = document.createElement('canvas'); + canvas.width = w; + canvas.height = h; + const ctx = canvas.getContext('2d'); + const img_data = ctx.createImageData(w, h); + img_data.data.set(HEAPU8.subarray(pixels, pixels + w*h*4)); + ctx.putImageData(img_data, 0, 0); + const new_link = document.createElement('link'); + new_link.id = 'sokol-app-favicon'; + new_link.rel = 'shortcut icon'; + new_link.href = canvas.toDataURL(); + document.head.appendChild(new_link); +}) + +_SOKOL_PRIVATE void _sapp_emsc_set_icon(const sapp_icon_desc* icon_desc, int num_images) { + SOKOL_ASSERT((num_images > 0) && (num_images <= SAPP_MAX_ICONIMAGES)); + sapp_js_clear_favicon(); + // find the best matching image candidate for 16x16 pixels + int img_index = _sapp_image_bestmatch(icon_desc->images, num_images, 16, 16); + const sapp_image_desc* img_desc = &icon_desc->images[img_index]; + sapp_js_set_favicon(img_desc->width, img_desc->height, (const uint8_t*) img_desc->pixels.ptr); +} + +_SOKOL_PRIVATE uint32_t _sapp_emsc_mouse_button_mods(uint16_t buttons) { + uint32_t m = 0; + if (0 != (buttons & (1<<0))) { m |= SAPP_MODIFIER_LMB; } + if (0 != (buttons & (1<<1))) { m |= SAPP_MODIFIER_RMB; } // not a bug + if (0 != (buttons & (1<<2))) { m |= SAPP_MODIFIER_MMB; } // not a bug + return m; +} + +_SOKOL_PRIVATE uint32_t _sapp_emsc_mouse_event_mods(const EmscriptenMouseEvent* ev) { + uint32_t m = 0; + if (ev->ctrlKey) { m |= SAPP_MODIFIER_CTRL; } + if (ev->shiftKey) { m |= SAPP_MODIFIER_SHIFT; } + if (ev->altKey) { m |= SAPP_MODIFIER_ALT; } + if (ev->metaKey) { m |= SAPP_MODIFIER_SUPER; } + m |= _sapp_emsc_mouse_button_mods(_sapp.emsc.mouse_buttons); + return m; +} + +_SOKOL_PRIVATE uint32_t _sapp_emsc_key_event_mods(const EmscriptenKeyboardEvent* ev) { + uint32_t m = 0; + if (ev->ctrlKey) { m |= SAPP_MODIFIER_CTRL; } + if (ev->shiftKey) { m |= SAPP_MODIFIER_SHIFT; } + if (ev->altKey) { m |= SAPP_MODIFIER_ALT; } + if (ev->metaKey) { m |= SAPP_MODIFIER_SUPER; } + m |= _sapp_emsc_mouse_button_mods(_sapp.emsc.mouse_buttons); + return m; +} + +_SOKOL_PRIVATE uint32_t _sapp_emsc_touch_event_mods(const EmscriptenTouchEvent* ev) { + uint32_t m = 0; + if (ev->ctrlKey) { m |= SAPP_MODIFIER_CTRL; } + if (ev->shiftKey) { m |= SAPP_MODIFIER_SHIFT; } + if (ev->altKey) { m |= SAPP_MODIFIER_ALT; } + if (ev->metaKey) { m |= SAPP_MODIFIER_SUPER; } + m |= _sapp_emsc_mouse_button_mods(_sapp.emsc.mouse_buttons); + return m; +} + +_SOKOL_PRIVATE EM_BOOL _sapp_emsc_size_changed(int event_type, const EmscriptenUiEvent* ui_event, void* user_data) { + _SOKOL_UNUSED(event_type); + _SOKOL_UNUSED(user_data); + double w, h; + emscripten_get_element_css_size(_sapp.html5_canvas_selector, &w, &h); + /* The above method might report zero when toggling HTML5 fullscreen, + in that case use the window's inner width reported by the + emscripten event. This works ok when toggling *into* fullscreen + but doesn't properly restore the previous canvas size when switching + back from fullscreen. + + In general, due to the HTML5's fullscreen API's flaky nature it is + recommended to use 'soft fullscreen' (stretching the WebGL canvas + over the browser windows client rect) with a CSS definition like this: + + position: absolute; + top: 0px; + left: 0px; + margin: 0px; + border: 0; + width: 100%; + height: 100%; + overflow: hidden; + display: block; + */ + if (w < 1.0) { + w = ui_event->windowInnerWidth; + } else { + _sapp.window_width = _sapp_roundf_gzero(w); + } + if (h < 1.0) { + h = ui_event->windowInnerHeight; + } else { + _sapp.window_height = _sapp_roundf_gzero(h); + } + if (_sapp.desc.high_dpi) { + _sapp.dpi_scale = emscripten_get_device_pixel_ratio(); + } + _sapp.framebuffer_width = _sapp_roundf_gzero(w * _sapp.dpi_scale); + _sapp.framebuffer_height = _sapp_roundf_gzero(h * _sapp.dpi_scale); + emscripten_set_canvas_element_size(_sapp.html5_canvas_selector, _sapp.framebuffer_width, _sapp.framebuffer_height); + #if defined(SOKOL_WGPU) + // on WebGPU: recreate size-dependent rendering surfaces + _sapp_wgpu_swapchain_size_changed(); + #endif + if (_sapp_events_enabled()) { + _sapp_init_event(SAPP_EVENTTYPE_RESIZED); + _sapp_call_event(&_sapp.event); + } + return true; +} + +_SOKOL_PRIVATE EM_BOOL _sapp_emsc_mouse_cb(int emsc_type, const EmscriptenMouseEvent* emsc_event, void* user_data) { + _SOKOL_UNUSED(user_data); + bool consume_event = !_sapp.desc.html5_bubble_mouse_events; + _sapp.emsc.mouse_buttons = emsc_event->buttons; + if (_sapp.mouse.locked) { + _sapp.mouse.dx = (float) emsc_event->movementX; + _sapp.mouse.dy = (float) emsc_event->movementY; + } else { + float new_x = emsc_event->targetX * _sapp.dpi_scale; + float new_y = emsc_event->targetY * _sapp.dpi_scale; + if (_sapp.mouse.pos_valid) { + _sapp.mouse.dx = new_x - _sapp.mouse.x; + _sapp.mouse.dy = new_y - _sapp.mouse.y; + } + _sapp.mouse.x = new_x; + _sapp.mouse.y = new_y; + _sapp.mouse.pos_valid = true; + } + if (_sapp_events_enabled() && (emsc_event->button >= 0) && (emsc_event->button < SAPP_MAX_MOUSEBUTTONS)) { + sapp_event_type type; + bool is_button_event = false; + bool clear_dxdy = false; + switch (emsc_type) { + case EMSCRIPTEN_EVENT_MOUSEDOWN: + type = SAPP_EVENTTYPE_MOUSE_DOWN; + is_button_event = true; + break; + case EMSCRIPTEN_EVENT_MOUSEUP: + type = SAPP_EVENTTYPE_MOUSE_UP; + is_button_event = true; + break; + case EMSCRIPTEN_EVENT_MOUSEMOVE: + type = SAPP_EVENTTYPE_MOUSE_MOVE; + break; + case EMSCRIPTEN_EVENT_MOUSEENTER: + type = SAPP_EVENTTYPE_MOUSE_ENTER; + clear_dxdy = true; + break; + case EMSCRIPTEN_EVENT_MOUSELEAVE: + type = SAPP_EVENTTYPE_MOUSE_LEAVE; + clear_dxdy = true; + break; + default: + type = SAPP_EVENTTYPE_INVALID; + break; + } + if (clear_dxdy) { + _sapp.mouse.dx = 0.0f; + _sapp.mouse.dy = 0.0f; + } + if (type != SAPP_EVENTTYPE_INVALID) { + _sapp_init_event(type); + _sapp.event.modifiers = _sapp_emsc_mouse_event_mods(emsc_event); + if (is_button_event) { + switch (emsc_event->button) { + case 0: _sapp.event.mouse_button = SAPP_MOUSEBUTTON_LEFT; break; + case 1: _sapp.event.mouse_button = SAPP_MOUSEBUTTON_MIDDLE; break; + case 2: _sapp.event.mouse_button = SAPP_MOUSEBUTTON_RIGHT; break; + default: _sapp.event.mouse_button = (sapp_mousebutton)emsc_event->button; break; + } + } else { + _sapp.event.mouse_button = SAPP_MOUSEBUTTON_INVALID; + } + consume_event |= _sapp_call_event(&_sapp.event); + } + // mouse lock can only be activated in mouse button events (not in move, enter or leave) + if (is_button_event) { + _sapp_emsc_update_mouse_lock_state(); + } + } + return consume_event; +} + +_SOKOL_PRIVATE EM_BOOL _sapp_emsc_wheel_cb(int emsc_type, const EmscriptenWheelEvent* emsc_event, void* user_data) { + _SOKOL_UNUSED(emsc_type); + _SOKOL_UNUSED(user_data); + bool consume_event = !_sapp.desc.html5_bubble_wheel_events; + _sapp.emsc.mouse_buttons = emsc_event->mouse.buttons; + if (_sapp_events_enabled()) { + _sapp_init_event(SAPP_EVENTTYPE_MOUSE_SCROLL); + _sapp.event.modifiers = _sapp_emsc_mouse_event_mods(&emsc_event->mouse); + /* see https://github.com/floooh/sokol/issues/339 */ + float scale; + switch (emsc_event->deltaMode) { + case DOM_DELTA_PIXEL: scale = -0.04f; break; + case DOM_DELTA_LINE: scale = -1.33f; break; + case DOM_DELTA_PAGE: scale = -10.0f; break; // FIXME: this is a guess + default: scale = -0.1f; break; // shouldn't happen + } + _sapp.event.scroll_x = scale * (float)emsc_event->deltaX; + _sapp.event.scroll_y = scale * (float)emsc_event->deltaY; + consume_event |= _sapp_call_event(&_sapp.event); + } + _sapp_emsc_update_mouse_lock_state(); + return consume_event; +} + +static struct { + const char* str; + sapp_keycode code; +} _sapp_emsc_keymap[] = { + { "Backspace", SAPP_KEYCODE_BACKSPACE }, + { "Tab", SAPP_KEYCODE_TAB }, + { "Enter", SAPP_KEYCODE_ENTER }, + { "ShiftLeft", SAPP_KEYCODE_LEFT_SHIFT }, + { "ShiftRight", SAPP_KEYCODE_RIGHT_SHIFT }, + { "ControlLeft", SAPP_KEYCODE_LEFT_CONTROL }, + { "ControlRight", SAPP_KEYCODE_RIGHT_CONTROL }, + { "AltLeft", SAPP_KEYCODE_LEFT_ALT }, + { "AltRight", SAPP_KEYCODE_RIGHT_ALT }, + { "Pause", SAPP_KEYCODE_PAUSE }, + { "CapsLock", SAPP_KEYCODE_CAPS_LOCK }, + { "Escape", SAPP_KEYCODE_ESCAPE }, + { "Space", SAPP_KEYCODE_SPACE }, + { "PageUp", SAPP_KEYCODE_PAGE_UP }, + { "PageDown", SAPP_KEYCODE_PAGE_DOWN }, + { "End", SAPP_KEYCODE_END }, + { "Home", SAPP_KEYCODE_HOME }, + { "ArrowLeft", SAPP_KEYCODE_LEFT }, + { "ArrowUp", SAPP_KEYCODE_UP }, + { "ArrowRight", SAPP_KEYCODE_RIGHT }, + { "ArrowDown", SAPP_KEYCODE_DOWN }, + { "PrintScreen", SAPP_KEYCODE_PRINT_SCREEN }, + { "Insert", SAPP_KEYCODE_INSERT }, + { "Delete", SAPP_KEYCODE_DELETE }, + { "Digit0", SAPP_KEYCODE_0 }, + { "Digit1", SAPP_KEYCODE_1 }, + { "Digit2", SAPP_KEYCODE_2 }, + { "Digit3", SAPP_KEYCODE_3 }, + { "Digit4", SAPP_KEYCODE_4 }, + { "Digit5", SAPP_KEYCODE_5 }, + { "Digit6", SAPP_KEYCODE_6 }, + { "Digit7", SAPP_KEYCODE_7 }, + { "Digit8", SAPP_KEYCODE_8 }, + { "Digit9", SAPP_KEYCODE_9 }, + { "KeyA", SAPP_KEYCODE_A }, + { "KeyB", SAPP_KEYCODE_B }, + { "KeyC", SAPP_KEYCODE_C }, + { "KeyD", SAPP_KEYCODE_D }, + { "KeyE", SAPP_KEYCODE_E }, + { "KeyF", SAPP_KEYCODE_F }, + { "KeyG", SAPP_KEYCODE_G }, + { "KeyH", SAPP_KEYCODE_H }, + { "KeyI", SAPP_KEYCODE_I }, + { "KeyJ", SAPP_KEYCODE_J }, + { "KeyK", SAPP_KEYCODE_K }, + { "KeyL", SAPP_KEYCODE_L }, + { "KeyM", SAPP_KEYCODE_M }, + { "KeyN", SAPP_KEYCODE_N }, + { "KeyO", SAPP_KEYCODE_O }, + { "KeyP", SAPP_KEYCODE_P }, + { "KeyQ", SAPP_KEYCODE_Q }, + { "KeyR", SAPP_KEYCODE_R }, + { "KeyS", SAPP_KEYCODE_S }, + { "KeyT", SAPP_KEYCODE_T }, + { "KeyU", SAPP_KEYCODE_U }, + { "KeyV", SAPP_KEYCODE_V }, + { "KeyW", SAPP_KEYCODE_W }, + { "KeyX", SAPP_KEYCODE_X }, + { "KeyY", SAPP_KEYCODE_Y }, + { "KeyZ", SAPP_KEYCODE_Z }, + { "MetaLeft", SAPP_KEYCODE_LEFT_SUPER }, + { "MetaRight", SAPP_KEYCODE_RIGHT_SUPER }, + { "Numpad0", SAPP_KEYCODE_KP_0 }, + { "Numpad1", SAPP_KEYCODE_KP_1 }, + { "Numpad2", SAPP_KEYCODE_KP_2 }, + { "Numpad3", SAPP_KEYCODE_KP_3 }, + { "Numpad4", SAPP_KEYCODE_KP_4 }, + { "Numpad5", SAPP_KEYCODE_KP_5 }, + { "Numpad6", SAPP_KEYCODE_KP_6 }, + { "Numpad7", SAPP_KEYCODE_KP_7 }, + { "Numpad8", SAPP_KEYCODE_KP_8 }, + { "Numpad9", SAPP_KEYCODE_KP_9 }, + { "NumpadMultiply", SAPP_KEYCODE_KP_MULTIPLY }, + { "NumpadAdd", SAPP_KEYCODE_KP_ADD }, + { "NumpadSubtract", SAPP_KEYCODE_KP_SUBTRACT }, + { "NumpadDecimal", SAPP_KEYCODE_KP_DECIMAL }, + { "NumpadDivide", SAPP_KEYCODE_KP_DIVIDE }, + { "F1", SAPP_KEYCODE_F1 }, + { "F2", SAPP_KEYCODE_F2 }, + { "F3", SAPP_KEYCODE_F3 }, + { "F4", SAPP_KEYCODE_F4 }, + { "F5", SAPP_KEYCODE_F5 }, + { "F6", SAPP_KEYCODE_F6 }, + { "F7", SAPP_KEYCODE_F7 }, + { "F8", SAPP_KEYCODE_F8 }, + { "F9", SAPP_KEYCODE_F9 }, + { "F10", SAPP_KEYCODE_F10 }, + { "F11", SAPP_KEYCODE_F11 }, + { "F12", SAPP_KEYCODE_F12 }, + { "NumLock", SAPP_KEYCODE_NUM_LOCK }, + { "ScrollLock", SAPP_KEYCODE_SCROLL_LOCK }, + { "Semicolon", SAPP_KEYCODE_SEMICOLON }, + { "Equal", SAPP_KEYCODE_EQUAL }, + { "Comma", SAPP_KEYCODE_COMMA }, + { "Minus", SAPP_KEYCODE_MINUS }, + { "Period", SAPP_KEYCODE_PERIOD }, + { "Slash", SAPP_KEYCODE_SLASH }, + { "Backquote", SAPP_KEYCODE_GRAVE_ACCENT }, + { "BracketLeft", SAPP_KEYCODE_LEFT_BRACKET }, + { "Backslash", SAPP_KEYCODE_BACKSLASH }, + { "BracketRight", SAPP_KEYCODE_RIGHT_BRACKET }, + { "Quote", SAPP_KEYCODE_GRAVE_ACCENT }, // FIXME: ??? + { 0, SAPP_KEYCODE_INVALID }, +}; + +_SOKOL_PRIVATE sapp_keycode _sapp_emsc_translate_key(const char* str) { + int i = 0; + const char* keystr; + while (( keystr = _sapp_emsc_keymap[i].str )) { + if (0 == strcmp(str, keystr)) { + return _sapp_emsc_keymap[i].code; + } + i += 1; + } + return SAPP_KEYCODE_INVALID; +} + +// returns true if the key code is a 'character key', this is used to decide +// if a key event needs to bubble up to create a char event +_SOKOL_PRIVATE bool _sapp_emsc_is_char_key(sapp_keycode key_code) { + return key_code < SAPP_KEYCODE_WORLD_1; +} + +_SOKOL_PRIVATE EM_BOOL _sapp_emsc_key_cb(int emsc_type, const EmscriptenKeyboardEvent* emsc_event, void* user_data) { + _SOKOL_UNUSED(user_data); + bool consume_event = false; + if (_sapp_events_enabled()) { + sapp_event_type type; + switch (emsc_type) { + case EMSCRIPTEN_EVENT_KEYDOWN: + type = SAPP_EVENTTYPE_KEY_DOWN; + break; + case EMSCRIPTEN_EVENT_KEYUP: + type = SAPP_EVENTTYPE_KEY_UP; + break; + case EMSCRIPTEN_EVENT_KEYPRESS: + type = SAPP_EVENTTYPE_CHAR; + break; + default: + type = SAPP_EVENTTYPE_INVALID; + break; + } + if (type != SAPP_EVENTTYPE_INVALID) { + bool send_keyup_followup = false; + _sapp_init_event(type); + _sapp.event.key_repeat = emsc_event->repeat; + _sapp.event.modifiers = _sapp_emsc_key_event_mods(emsc_event); + if (type == SAPP_EVENTTYPE_CHAR) { + // NOTE: charCode doesn't appear to be supported on Android Chrome + _sapp.event.char_code = emsc_event->charCode; + consume_event |= !_sapp.desc.html5_bubble_char_events; + } else { + if (0 != emsc_event->code[0]) { + // This code path is for desktop browsers which send untranslated 'physical' key code strings + // (which is what we actually want for key events) + _sapp.event.key_code = _sapp_emsc_translate_key(emsc_event->code); + } else { + // This code path is for mobile browsers which only send localized key code + // strings. Note that the translation will only work for a small subset + // of localization-agnostic keys (like Enter, arrow keys, etc...), but + // regular alpha-numeric keys will all result in an SAPP_KEYCODE_INVALID) + _sapp.event.key_code = _sapp_emsc_translate_key(emsc_event->key); + } + + // Special hack for macOS: if the Super key is pressed, macOS doesn't + // send keyUp events. As a workaround, to prevent keys from + // "sticking", we'll send a keyup event following a keydown + // when the SUPER key is pressed + if ((type == SAPP_EVENTTYPE_KEY_DOWN) && + (_sapp.event.key_code != SAPP_KEYCODE_LEFT_SUPER) && + (_sapp.event.key_code != SAPP_KEYCODE_RIGHT_SUPER) && + (_sapp.event.modifiers & SAPP_MODIFIER_SUPER)) + { + send_keyup_followup = true; + } + + // 'character key events' will always need to bubble up, otherwise the browser + // wouldn't be able to generate character events. + if (!_sapp_emsc_is_char_key(_sapp.event.key_code)) { + consume_event |= !_sapp.desc.html5_bubble_key_events; + } + } + consume_event |= _sapp_call_event(&_sapp.event); + if (send_keyup_followup) { + _sapp.event.type = SAPP_EVENTTYPE_KEY_UP; + consume_event |= _sapp_call_event(&_sapp.event); + } + } + } + _sapp_emsc_update_mouse_lock_state(); + return consume_event; +} + +_SOKOL_PRIVATE EM_BOOL _sapp_emsc_touch_cb(int emsc_type, const EmscriptenTouchEvent* emsc_event, void* user_data) { + _SOKOL_UNUSED(user_data); + bool consume_event = !_sapp.desc.html5_bubble_touch_events; + if (_sapp_events_enabled()) { + sapp_event_type type; + switch (emsc_type) { + case EMSCRIPTEN_EVENT_TOUCHSTART: + type = SAPP_EVENTTYPE_TOUCHES_BEGAN; + break; + case EMSCRIPTEN_EVENT_TOUCHMOVE: + type = SAPP_EVENTTYPE_TOUCHES_MOVED; + break; + case EMSCRIPTEN_EVENT_TOUCHEND: + type = SAPP_EVENTTYPE_TOUCHES_ENDED; + break; + case EMSCRIPTEN_EVENT_TOUCHCANCEL: + type = SAPP_EVENTTYPE_TOUCHES_CANCELLED; + break; + default: + type = SAPP_EVENTTYPE_INVALID; + break; + } + if (type != SAPP_EVENTTYPE_INVALID) { + _sapp_init_event(type); + _sapp.event.modifiers = _sapp_emsc_touch_event_mods(emsc_event); + _sapp.event.num_touches = emsc_event->numTouches; + if (_sapp.event.num_touches > SAPP_MAX_TOUCHPOINTS) { + _sapp.event.num_touches = SAPP_MAX_TOUCHPOINTS; + } + for (int i = 0; i < _sapp.event.num_touches; i++) { + const EmscriptenTouchPoint* src = &emsc_event->touches[i]; + sapp_touchpoint* dst = &_sapp.event.touches[i]; + dst->identifier = (uintptr_t)src->identifier; + dst->pos_x = src->targetX * _sapp.dpi_scale; + dst->pos_y = src->targetY * _sapp.dpi_scale; + dst->changed = src->isChanged; + } + consume_event |= _sapp_call_event(&_sapp.event); + } + } + return consume_event; +} + +_SOKOL_PRIVATE EM_BOOL _sapp_emsc_focus_cb(int emsc_type, const EmscriptenFocusEvent* emsc_event, void* user_data) { + _SOKOL_UNUSED(emsc_type); + _SOKOL_UNUSED(emsc_event); + _SOKOL_UNUSED(user_data); + if (_sapp_events_enabled()) { + _sapp_init_event(SAPP_EVENTTYPE_FOCUSED); + _sapp_call_event(&_sapp.event); + } + return true; +} + +_SOKOL_PRIVATE EM_BOOL _sapp_emsc_blur_cb(int emsc_type, const EmscriptenFocusEvent* emsc_event, void* user_data) { + _SOKOL_UNUSED(emsc_type); + _SOKOL_UNUSED(emsc_event); + _SOKOL_UNUSED(user_data); + if (_sapp_events_enabled()) { + _sapp_init_event(SAPP_EVENTTYPE_UNFOCUSED); + _sapp_call_event(&_sapp.event); + } + return true; +} + +#if defined(SOKOL_GLES3) +_SOKOL_PRIVATE EM_BOOL _sapp_emsc_webgl_context_cb(int emsc_type, const void* reserved, void* user_data) { + _SOKOL_UNUSED(reserved); + _SOKOL_UNUSED(user_data); + sapp_event_type type; + switch (emsc_type) { + case EMSCRIPTEN_EVENT_WEBGLCONTEXTLOST: type = SAPP_EVENTTYPE_SUSPENDED; break; + case EMSCRIPTEN_EVENT_WEBGLCONTEXTRESTORED: type = SAPP_EVENTTYPE_RESUMED; break; + default: type = SAPP_EVENTTYPE_INVALID; break; + } + if (_sapp_events_enabled() && (SAPP_EVENTTYPE_INVALID != type)) { + _sapp_init_event(type); + _sapp_call_event(&_sapp.event); + } + return true; +} + +_SOKOL_PRIVATE void _sapp_emsc_webgl_init(void) { + EmscriptenWebGLContextAttributes attrs; + emscripten_webgl_init_context_attributes(&attrs); + attrs.alpha = _sapp.desc.alpha; + attrs.depth = true; + attrs.stencil = true; + attrs.antialias = _sapp.sample_count > 1; + attrs.premultipliedAlpha = _sapp.desc.html5_premultiplied_alpha; + attrs.preserveDrawingBuffer = _sapp.desc.html5_preserve_drawing_buffer; + attrs.enableExtensionsByDefault = true; + attrs.majorVersion = 2; + EMSCRIPTEN_WEBGL_CONTEXT_HANDLE ctx = emscripten_webgl_create_context(_sapp.html5_canvas_selector, &attrs); + // FIXME: error message? + emscripten_webgl_make_context_current(ctx); + glGetIntegerv(GL_FRAMEBUFFER_BINDING, (GLint*)&_sapp.gl.framebuffer); +} +#endif + +_SOKOL_PRIVATE void _sapp_emsc_register_eventhandlers(void) { + // NOTE: HTML canvas doesn't receive input focus, this is why key event handlers are added + // to the window object (this could be worked around by adding a "tab index" to the + // canvas) + emscripten_set_mousedown_callback(_sapp.html5_canvas_selector, 0, true, _sapp_emsc_mouse_cb); + emscripten_set_mouseup_callback(_sapp.html5_canvas_selector, 0, true, _sapp_emsc_mouse_cb); + emscripten_set_mousemove_callback(_sapp.html5_canvas_selector, 0, true, _sapp_emsc_mouse_cb); + emscripten_set_mouseenter_callback(_sapp.html5_canvas_selector, 0, true, _sapp_emsc_mouse_cb); + emscripten_set_mouseleave_callback(_sapp.html5_canvas_selector, 0, true, _sapp_emsc_mouse_cb); + emscripten_set_wheel_callback(_sapp.html5_canvas_selector, 0, true, _sapp_emsc_wheel_cb); + emscripten_set_keydown_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, 0, true, _sapp_emsc_key_cb); + emscripten_set_keyup_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, 0, true, _sapp_emsc_key_cb); + emscripten_set_keypress_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, 0, true, _sapp_emsc_key_cb); + emscripten_set_touchstart_callback(_sapp.html5_canvas_selector, 0, true, _sapp_emsc_touch_cb); + emscripten_set_touchmove_callback(_sapp.html5_canvas_selector, 0, true, _sapp_emsc_touch_cb); + emscripten_set_touchend_callback(_sapp.html5_canvas_selector, 0, true, _sapp_emsc_touch_cb); + emscripten_set_touchcancel_callback(_sapp.html5_canvas_selector, 0, true, _sapp_emsc_touch_cb); + emscripten_set_pointerlockchange_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, 0, true, _sapp_emsc_pointerlockchange_cb); + emscripten_set_pointerlockerror_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, 0, true, _sapp_emsc_pointerlockerror_cb); + emscripten_set_focus_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, 0, true, _sapp_emsc_focus_cb); + emscripten_set_blur_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, 0, true, _sapp_emsc_blur_cb); + emscripten_set_fullscreenchange_callback(_sapp.html5_canvas_selector, 0, true, _sapp_emsc_fullscreenchange_cb); + sapp_js_add_beforeunload_listener(); + if (_sapp.clipboard.enabled) { + sapp_js_add_clipboard_listener(); + } + if (_sapp.drop.enabled) { + sapp_js_add_dragndrop_listeners(); + } + #if defined(SOKOL_GLES3) + emscripten_set_webglcontextlost_callback(_sapp.html5_canvas_selector, 0, true, _sapp_emsc_webgl_context_cb); + emscripten_set_webglcontextrestored_callback(_sapp.html5_canvas_selector, 0, true, _sapp_emsc_webgl_context_cb); + #endif +} + +_SOKOL_PRIVATE void _sapp_emsc_unregister_eventhandlers(void) { + emscripten_set_mousedown_callback(_sapp.html5_canvas_selector, 0, true, 0); + emscripten_set_mouseup_callback(_sapp.html5_canvas_selector, 0, true, 0); + emscripten_set_mousemove_callback(_sapp.html5_canvas_selector, 0, true, 0); + emscripten_set_mouseenter_callback(_sapp.html5_canvas_selector, 0, true, 0); + emscripten_set_mouseleave_callback(_sapp.html5_canvas_selector, 0, true, 0); + emscripten_set_wheel_callback(_sapp.html5_canvas_selector, 0, true, 0); + emscripten_set_keydown_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, 0, true, 0); + emscripten_set_keyup_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, 0, true, 0); + emscripten_set_keypress_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, 0, true, 0); + emscripten_set_touchstart_callback(_sapp.html5_canvas_selector, 0, true, 0); + emscripten_set_touchmove_callback(_sapp.html5_canvas_selector, 0, true, 0); + emscripten_set_touchend_callback(_sapp.html5_canvas_selector, 0, true, 0); + emscripten_set_touchcancel_callback(_sapp.html5_canvas_selector, 0, true, 0); + emscripten_set_pointerlockchange_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, 0, true, 0); + emscripten_set_pointerlockerror_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, 0, true, 0); + emscripten_set_focus_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, 0, true, 0); + emscripten_set_blur_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, 0, true, 0); + emscripten_set_fullscreenchange_callback(_sapp.html5_canvas_selector, 0, true, 0); + if (!_sapp.desc.html5_canvas_resize) { + emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, 0, true, 0); + } + sapp_js_remove_beforeunload_listener(); + if (_sapp.clipboard.enabled) { + sapp_js_remove_clipboard_listener(); + } + if (_sapp.drop.enabled) { + sapp_js_remove_dragndrop_listeners(); + } + #if defined(SOKOL_GLES3) + emscripten_set_webglcontextlost_callback(_sapp.html5_canvas_selector, 0, true, 0); + emscripten_set_webglcontextrestored_callback(_sapp.html5_canvas_selector, 0, true, 0); + #endif +} + +_SOKOL_PRIVATE EM_BOOL _sapp_emsc_frame_animation_loop(double time, void* userData) { + _SOKOL_UNUSED(userData); + _sapp_timing_external(&_sapp.timing, time / 1000.0); + + #if defined(SOKOL_WGPU) + _sapp_wgpu_frame(); + #else + _sapp_frame(); + #endif + + // quit-handling + if (_sapp.quit_requested) { + _sapp_init_event(SAPP_EVENTTYPE_QUIT_REQUESTED); + _sapp_call_event(&_sapp.event); + if (_sapp.quit_requested) { + _sapp.quit_ordered = true; + } + } + if (_sapp.quit_ordered) { + _sapp_emsc_unregister_eventhandlers(); + #if defined(SOKOL_WGPU) + _sapp_wgpu_discard(); + #endif + _sapp_call_cleanup(); + _sapp_discard_state(); + return EM_FALSE; + } + return EM_TRUE; +} + +_SOKOL_PRIVATE void _sapp_emsc_frame_main_loop(void) { + const double time = emscripten_performance_now(); + if (!_sapp_emsc_frame_animation_loop(time, 0)) { + emscripten_cancel_main_loop(); + } +} + +_SOKOL_PRIVATE void _sapp_emsc_run(const sapp_desc* desc) { + _sapp_init_state(desc); + _sapp.fullscreen = false; // override user provided fullscreen state: can't start in fullscreen on the web! + const char* document_title = desc->html5_update_document_title ? _sapp.window_title : 0; + sapp_js_init(_sapp.html5_canvas_selector, document_title); + double w, h; + if (_sapp.desc.html5_canvas_resize) { + w = (double) _sapp_def(_sapp.desc.width, _SAPP_FALLBACK_DEFAULT_WINDOW_WIDTH); + h = (double) _sapp_def(_sapp.desc.height, _SAPP_FALLBACK_DEFAULT_WINDOW_HEIGHT); + } else { + emscripten_get_element_css_size(_sapp.html5_canvas_selector, &w, &h); + emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, 0, false, _sapp_emsc_size_changed); + } + if (_sapp.desc.high_dpi) { + _sapp.dpi_scale = emscripten_get_device_pixel_ratio(); + } + _sapp.window_width = _sapp_roundf_gzero(w); + _sapp.window_height = _sapp_roundf_gzero(h); + _sapp.framebuffer_width = _sapp_roundf_gzero(w * _sapp.dpi_scale); + _sapp.framebuffer_height = _sapp_roundf_gzero(h * _sapp.dpi_scale); + emscripten_set_canvas_element_size(_sapp.html5_canvas_selector, _sapp.framebuffer_width, _sapp.framebuffer_height); + #if defined(SOKOL_GLES3) + _sapp_emsc_webgl_init(); + #elif defined(SOKOL_WGPU) + _sapp_wgpu_init(); + #endif + _sapp.valid = true; + _sapp_emsc_register_eventhandlers(); + sapp_set_icon(&desc->icon); + + // start the frame loop + if (_sapp.desc.html5_use_emsc_set_main_loop) { + emscripten_set_main_loop(_sapp_emsc_frame_main_loop, 0, _sapp.desc.html5_emsc_set_main_loop_simulate_infinite_loop); + } else { + emscripten_request_animation_frame_loop(_sapp_emsc_frame_animation_loop, 0); + } + // NOT A BUG: do not call _sapp_discard_state() here, instead this is + // called in _sapp_emsc_frame() when the application is ordered to quit +} + +#if !defined(SOKOL_NO_ENTRY) +int main(int argc, char* argv[]) { + sapp_desc desc = sokol_main(argc, argv); + _sapp_emsc_run(&desc); + return 0; +} +#endif /* SOKOL_NO_ENTRY */ +#endif /* _SAPP_EMSCRIPTEN */ + +// ██████ ██ ██ ██ ███████ ██ ██████ ███████ ██████ ███████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██ ███ ██ ███████ █████ ██ ██████ █████ ██████ ███████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██████ ███████ ██ ██ ███████ ███████ ██ ███████ ██ ██ ███████ +// +// >>gl helpers +#if defined(SOKOL_GLCORE) +typedef struct { + int red_bits; + int green_bits; + int blue_bits; + int alpha_bits; + int depth_bits; + int stencil_bits; + int samples; + bool doublebuffer; + uintptr_t handle; +} _sapp_gl_fbconfig; + +_SOKOL_PRIVATE void _sapp_gl_init_fbconfig(_sapp_gl_fbconfig* fbconfig) { + _sapp_clear(fbconfig, sizeof(_sapp_gl_fbconfig)); + /* -1 means "don't care" */ + fbconfig->red_bits = -1; + fbconfig->green_bits = -1; + fbconfig->blue_bits = -1; + fbconfig->alpha_bits = -1; + fbconfig->depth_bits = -1; + fbconfig->stencil_bits = -1; + fbconfig->samples = -1; +} + +typedef struct { + int least_missing; + int least_color_diff; + int least_extra_diff; + bool best_match; +} _sapp_gl_fbselect; + +_SOKOL_PRIVATE void _sapp_gl_init_fbselect(_sapp_gl_fbselect* fbselect) { + _sapp_clear(fbselect, sizeof(_sapp_gl_fbselect)); + fbselect->least_missing = 1000000; + fbselect->least_color_diff = 10000000; + fbselect->least_extra_diff = 10000000; + fbselect->best_match = false; +} + +// NOTE: this is used only in the WGL code path +_SOKOL_PRIVATE bool _sapp_gl_select_fbconfig(_sapp_gl_fbselect* fbselect, const _sapp_gl_fbconfig* desired, const _sapp_gl_fbconfig* current) { + int missing = 0; + if (desired->doublebuffer != current->doublebuffer) { + return false; + } + + if ((desired->alpha_bits > 0) && (current->alpha_bits == 0)) { + missing++; + } + if ((desired->depth_bits > 0) && (current->depth_bits == 0)) { + missing++; + } + if ((desired->stencil_bits > 0) && (current->stencil_bits == 0)) { + missing++; + } + if ((desired->samples > 0) && (current->samples == 0)) { + /* Technically, several multisampling buffers could be + involved, but that's a lower level implementation detail and + not important to us here, so we count them as one + */ + missing++; + } + + /* These polynomials make many small channel size differences matter + less than one large channel size difference + Calculate color channel size difference value + */ + int color_diff = 0; + if (desired->red_bits != -1) { + color_diff += (desired->red_bits - current->red_bits) * (desired->red_bits - current->red_bits); + } + if (desired->green_bits != -1) { + color_diff += (desired->green_bits - current->green_bits) * (desired->green_bits - current->green_bits); + } + if (desired->blue_bits != -1) { + color_diff += (desired->blue_bits - current->blue_bits) * (desired->blue_bits - current->blue_bits); + } + + /* Calculate non-color channel size difference value */ + int extra_diff = 0; + if (desired->alpha_bits != -1) { + extra_diff += (desired->alpha_bits - current->alpha_bits) * (desired->alpha_bits - current->alpha_bits); + } + if (desired->depth_bits != -1) { + extra_diff += (desired->depth_bits - current->depth_bits) * (desired->depth_bits - current->depth_bits); + } + if (desired->stencil_bits != -1) { + extra_diff += (desired->stencil_bits - current->stencil_bits) * (desired->stencil_bits - current->stencil_bits); + } + if (desired->samples != -1) { + extra_diff += (desired->samples - current->samples) * (desired->samples - current->samples); + } + + /* Figure out if the current one is better than the best one found so far + Least number of missing buffers is the most important heuristic, + then color buffer size match and lastly size match for other buffers + */ + bool new_closest = false; + if (missing < fbselect->least_missing) { + new_closest = true; + } else if (missing == fbselect->least_missing) { + if ((color_diff < fbselect->least_color_diff) || + ((color_diff == fbselect->least_color_diff) && (extra_diff < fbselect->least_extra_diff))) + { + new_closest = true; + } + } + if (new_closest) { + fbselect->least_missing = missing; + fbselect->least_color_diff = color_diff; + fbselect->least_extra_diff = extra_diff; + fbselect->best_match = (missing | color_diff | extra_diff) == 0; + } + return new_closest; +} + +// NOTE: this is used only in the GLX code path +_SOKOL_PRIVATE const _sapp_gl_fbconfig* _sapp_gl_choose_fbconfig(const _sapp_gl_fbconfig* desired, const _sapp_gl_fbconfig* alternatives, int count) { + int missing, least_missing = 1000000; + int color_diff, least_color_diff = 10000000; + int extra_diff, least_extra_diff = 10000000; + const _sapp_gl_fbconfig* current; + const _sapp_gl_fbconfig* closest = 0; + for (int i = 0; i < count; i++) { + current = alternatives + i; + if (desired->doublebuffer != current->doublebuffer) { + continue; + } + missing = 0; + if (desired->alpha_bits > 0 && current->alpha_bits == 0) { + missing++; + } + if (desired->depth_bits > 0 && current->depth_bits == 0) { + missing++; + } + if (desired->stencil_bits > 0 && current->stencil_bits == 0) { + missing++; + } + if (desired->samples > 0 && current->samples == 0) { + /* Technically, several multisampling buffers could be + involved, but that's a lower level implementation detail and + not important to us here, so we count them as one + */ + missing++; + } + + /* These polynomials make many small channel size differences matter + less than one large channel size difference + Calculate color channel size difference value + */ + color_diff = 0; + if (desired->red_bits != -1) { + color_diff += (desired->red_bits - current->red_bits) * (desired->red_bits - current->red_bits); + } + if (desired->green_bits != -1) { + color_diff += (desired->green_bits - current->green_bits) * (desired->green_bits - current->green_bits); + } + if (desired->blue_bits != -1) { + color_diff += (desired->blue_bits - current->blue_bits) * (desired->blue_bits - current->blue_bits); + } + + /* Calculate non-color channel size difference value */ + extra_diff = 0; + if (desired->alpha_bits != -1) { + extra_diff += (desired->alpha_bits - current->alpha_bits) * (desired->alpha_bits - current->alpha_bits); + } + if (desired->depth_bits != -1) { + extra_diff += (desired->depth_bits - current->depth_bits) * (desired->depth_bits - current->depth_bits); + } + if (desired->stencil_bits != -1) { + extra_diff += (desired->stencil_bits - current->stencil_bits) * (desired->stencil_bits - current->stencil_bits); + } + if (desired->samples != -1) { + extra_diff += (desired->samples - current->samples) * (desired->samples - current->samples); + } + + /* Figure out if the current one is better than the best one found so far + Least number of missing buffers is the most important heuristic, + then color buffer size match and lastly size match for other buffers + */ + if (missing < least_missing) { + closest = current; + } else if (missing == least_missing) { + if ((color_diff < least_color_diff) || + (color_diff == least_color_diff && extra_diff < least_extra_diff)) + { + closest = current; + } + } + if (current == closest) { + least_missing = missing; + least_color_diff = color_diff; + least_extra_diff = extra_diff; + } + } + return closest; +} +#endif + +// ██ ██ ██ ███ ██ ██████ ██████ ██ ██ ███████ +// ██ ██ ██ ████ ██ ██ ██ ██ ██ ██ ██ ██ +// ██ █ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ █ ██ ███████ +// ██ ███ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ███ ██ ██ +// ███ ███ ██ ██ ████ ██████ ██████ ███ ███ ███████ +// +// >>windows +#if defined(_SAPP_WIN32) +_SOKOL_PRIVATE bool _sapp_win32_utf8_to_wide(const char* src, wchar_t* dst, int dst_num_bytes) { + SOKOL_ASSERT(src && dst && (dst_num_bytes > 1)); + _sapp_clear(dst, (size_t)dst_num_bytes); + const int dst_chars = dst_num_bytes / (int)sizeof(wchar_t); + const int dst_needed = MultiByteToWideChar(CP_UTF8, 0, src, -1, 0, 0); + if ((dst_needed > 0) && (dst_needed < dst_chars)) { + MultiByteToWideChar(CP_UTF8, 0, src, -1, dst, dst_chars); + return true; + } else { + // input string doesn't fit into destination buffer + return false; + } +} + +_SOKOL_PRIVATE void _sapp_win32_app_event(sapp_event_type type) { + if (_sapp_events_enabled()) { + _sapp_init_event(type); + _sapp_call_event(&_sapp.event); + } +} + +_SOKOL_PRIVATE void _sapp_win32_init_keytable(void) { + /* same as GLFW */ + _sapp.keycodes[0x00B] = SAPP_KEYCODE_0; + _sapp.keycodes[0x002] = SAPP_KEYCODE_1; + _sapp.keycodes[0x003] = SAPP_KEYCODE_2; + _sapp.keycodes[0x004] = SAPP_KEYCODE_3; + _sapp.keycodes[0x005] = SAPP_KEYCODE_4; + _sapp.keycodes[0x006] = SAPP_KEYCODE_5; + _sapp.keycodes[0x007] = SAPP_KEYCODE_6; + _sapp.keycodes[0x008] = SAPP_KEYCODE_7; + _sapp.keycodes[0x009] = SAPP_KEYCODE_8; + _sapp.keycodes[0x00A] = SAPP_KEYCODE_9; + _sapp.keycodes[0x01E] = SAPP_KEYCODE_A; + _sapp.keycodes[0x030] = SAPP_KEYCODE_B; + _sapp.keycodes[0x02E] = SAPP_KEYCODE_C; + _sapp.keycodes[0x020] = SAPP_KEYCODE_D; + _sapp.keycodes[0x012] = SAPP_KEYCODE_E; + _sapp.keycodes[0x021] = SAPP_KEYCODE_F; + _sapp.keycodes[0x022] = SAPP_KEYCODE_G; + _sapp.keycodes[0x023] = SAPP_KEYCODE_H; + _sapp.keycodes[0x017] = SAPP_KEYCODE_I; + _sapp.keycodes[0x024] = SAPP_KEYCODE_J; + _sapp.keycodes[0x025] = SAPP_KEYCODE_K; + _sapp.keycodes[0x026] = SAPP_KEYCODE_L; + _sapp.keycodes[0x032] = SAPP_KEYCODE_M; + _sapp.keycodes[0x031] = SAPP_KEYCODE_N; + _sapp.keycodes[0x018] = SAPP_KEYCODE_O; + _sapp.keycodes[0x019] = SAPP_KEYCODE_P; + _sapp.keycodes[0x010] = SAPP_KEYCODE_Q; + _sapp.keycodes[0x013] = SAPP_KEYCODE_R; + _sapp.keycodes[0x01F] = SAPP_KEYCODE_S; + _sapp.keycodes[0x014] = SAPP_KEYCODE_T; + _sapp.keycodes[0x016] = SAPP_KEYCODE_U; + _sapp.keycodes[0x02F] = SAPP_KEYCODE_V; + _sapp.keycodes[0x011] = SAPP_KEYCODE_W; + _sapp.keycodes[0x02D] = SAPP_KEYCODE_X; + _sapp.keycodes[0x015] = SAPP_KEYCODE_Y; + _sapp.keycodes[0x02C] = SAPP_KEYCODE_Z; + _sapp.keycodes[0x028] = SAPP_KEYCODE_APOSTROPHE; + _sapp.keycodes[0x02B] = SAPP_KEYCODE_BACKSLASH; + _sapp.keycodes[0x033] = SAPP_KEYCODE_COMMA; + _sapp.keycodes[0x00D] = SAPP_KEYCODE_EQUAL; + _sapp.keycodes[0x029] = SAPP_KEYCODE_GRAVE_ACCENT; + _sapp.keycodes[0x01A] = SAPP_KEYCODE_LEFT_BRACKET; + _sapp.keycodes[0x00C] = SAPP_KEYCODE_MINUS; + _sapp.keycodes[0x034] = SAPP_KEYCODE_PERIOD; + _sapp.keycodes[0x01B] = SAPP_KEYCODE_RIGHT_BRACKET; + _sapp.keycodes[0x027] = SAPP_KEYCODE_SEMICOLON; + _sapp.keycodes[0x035] = SAPP_KEYCODE_SLASH; + _sapp.keycodes[0x056] = SAPP_KEYCODE_WORLD_2; + _sapp.keycodes[0x00E] = SAPP_KEYCODE_BACKSPACE; + _sapp.keycodes[0x153] = SAPP_KEYCODE_DELETE; + _sapp.keycodes[0x14F] = SAPP_KEYCODE_END; + _sapp.keycodes[0x01C] = SAPP_KEYCODE_ENTER; + _sapp.keycodes[0x001] = SAPP_KEYCODE_ESCAPE; + _sapp.keycodes[0x147] = SAPP_KEYCODE_HOME; + _sapp.keycodes[0x152] = SAPP_KEYCODE_INSERT; + _sapp.keycodes[0x15D] = SAPP_KEYCODE_MENU; + _sapp.keycodes[0x151] = SAPP_KEYCODE_PAGE_DOWN; + _sapp.keycodes[0x149] = SAPP_KEYCODE_PAGE_UP; + _sapp.keycodes[0x045] = SAPP_KEYCODE_PAUSE; + _sapp.keycodes[0x146] = SAPP_KEYCODE_PAUSE; + _sapp.keycodes[0x039] = SAPP_KEYCODE_SPACE; + _sapp.keycodes[0x00F] = SAPP_KEYCODE_TAB; + _sapp.keycodes[0x03A] = SAPP_KEYCODE_CAPS_LOCK; + _sapp.keycodes[0x145] = SAPP_KEYCODE_NUM_LOCK; + _sapp.keycodes[0x046] = SAPP_KEYCODE_SCROLL_LOCK; + _sapp.keycodes[0x03B] = SAPP_KEYCODE_F1; + _sapp.keycodes[0x03C] = SAPP_KEYCODE_F2; + _sapp.keycodes[0x03D] = SAPP_KEYCODE_F3; + _sapp.keycodes[0x03E] = SAPP_KEYCODE_F4; + _sapp.keycodes[0x03F] = SAPP_KEYCODE_F5; + _sapp.keycodes[0x040] = SAPP_KEYCODE_F6; + _sapp.keycodes[0x041] = SAPP_KEYCODE_F7; + _sapp.keycodes[0x042] = SAPP_KEYCODE_F8; + _sapp.keycodes[0x043] = SAPP_KEYCODE_F9; + _sapp.keycodes[0x044] = SAPP_KEYCODE_F10; + _sapp.keycodes[0x057] = SAPP_KEYCODE_F11; + _sapp.keycodes[0x058] = SAPP_KEYCODE_F12; + _sapp.keycodes[0x064] = SAPP_KEYCODE_F13; + _sapp.keycodes[0x065] = SAPP_KEYCODE_F14; + _sapp.keycodes[0x066] = SAPP_KEYCODE_F15; + _sapp.keycodes[0x067] = SAPP_KEYCODE_F16; + _sapp.keycodes[0x068] = SAPP_KEYCODE_F17; + _sapp.keycodes[0x069] = SAPP_KEYCODE_F18; + _sapp.keycodes[0x06A] = SAPP_KEYCODE_F19; + _sapp.keycodes[0x06B] = SAPP_KEYCODE_F20; + _sapp.keycodes[0x06C] = SAPP_KEYCODE_F21; + _sapp.keycodes[0x06D] = SAPP_KEYCODE_F22; + _sapp.keycodes[0x06E] = SAPP_KEYCODE_F23; + _sapp.keycodes[0x076] = SAPP_KEYCODE_F24; + _sapp.keycodes[0x038] = SAPP_KEYCODE_LEFT_ALT; + _sapp.keycodes[0x01D] = SAPP_KEYCODE_LEFT_CONTROL; + _sapp.keycodes[0x02A] = SAPP_KEYCODE_LEFT_SHIFT; + _sapp.keycodes[0x15B] = SAPP_KEYCODE_LEFT_SUPER; + _sapp.keycodes[0x137] = SAPP_KEYCODE_PRINT_SCREEN; + _sapp.keycodes[0x138] = SAPP_KEYCODE_RIGHT_ALT; + _sapp.keycodes[0x11D] = SAPP_KEYCODE_RIGHT_CONTROL; + _sapp.keycodes[0x036] = SAPP_KEYCODE_RIGHT_SHIFT; + _sapp.keycodes[0x136] = SAPP_KEYCODE_RIGHT_SHIFT; + _sapp.keycodes[0x15C] = SAPP_KEYCODE_RIGHT_SUPER; + _sapp.keycodes[0x150] = SAPP_KEYCODE_DOWN; + _sapp.keycodes[0x14B] = SAPP_KEYCODE_LEFT; + _sapp.keycodes[0x14D] = SAPP_KEYCODE_RIGHT; + _sapp.keycodes[0x148] = SAPP_KEYCODE_UP; + _sapp.keycodes[0x052] = SAPP_KEYCODE_KP_0; + _sapp.keycodes[0x04F] = SAPP_KEYCODE_KP_1; + _sapp.keycodes[0x050] = SAPP_KEYCODE_KP_2; + _sapp.keycodes[0x051] = SAPP_KEYCODE_KP_3; + _sapp.keycodes[0x04B] = SAPP_KEYCODE_KP_4; + _sapp.keycodes[0x04C] = SAPP_KEYCODE_KP_5; + _sapp.keycodes[0x04D] = SAPP_KEYCODE_KP_6; + _sapp.keycodes[0x047] = SAPP_KEYCODE_KP_7; + _sapp.keycodes[0x048] = SAPP_KEYCODE_KP_8; + _sapp.keycodes[0x049] = SAPP_KEYCODE_KP_9; + _sapp.keycodes[0x04E] = SAPP_KEYCODE_KP_ADD; + _sapp.keycodes[0x053] = SAPP_KEYCODE_KP_DECIMAL; + _sapp.keycodes[0x135] = SAPP_KEYCODE_KP_DIVIDE; + _sapp.keycodes[0x11C] = SAPP_KEYCODE_KP_ENTER; + _sapp.keycodes[0x037] = SAPP_KEYCODE_KP_MULTIPLY; + _sapp.keycodes[0x04A] = SAPP_KEYCODE_KP_SUBTRACT; +} +#endif // _SAPP_WIN32 + +#if defined(_SAPP_WIN32) + +#if defined(SOKOL_D3D11) + +#if defined(__cplusplus) +#define _sapp_d3d11_Release(self) (self)->Release() +#define _sapp_win32_refiid(iid) iid +#else +#define _sapp_d3d11_Release(self) (self)->lpVtbl->Release(self) +#define _sapp_win32_refiid(iid) &iid +#endif + +#define _SAPP_SAFE_RELEASE(obj) if (obj) { _sapp_d3d11_Release(obj); obj=0; } + + +static const IID _sapp_IID_ID3D11Texture2D = { 0x6f15aaf2,0xd208,0x4e89, {0x9a,0xb4,0x48,0x95,0x35,0xd3,0x4f,0x9c} }; +static const IID _sapp_IID_IDXGIDevice1 = { 0x77db970f,0x6276,0x48ba, {0xba,0x28,0x07,0x01,0x43,0xb4,0x39,0x2c} }; +static const IID _sapp_IID_IDXGIFactory = { 0x7b7166ec,0x21c7,0x44ae, {0xb2,0x1a,0xc9,0xae,0x32,0x1a,0xe3,0x69} }; + +static inline HRESULT _sapp_dxgi_GetBuffer(IDXGISwapChain* self, UINT Buffer, REFIID riid, void** ppSurface) { + #if defined(__cplusplus) + return self->GetBuffer(Buffer, riid, ppSurface); + #else + return self->lpVtbl->GetBuffer(self, Buffer, riid, ppSurface); + #endif +} + +static inline HRESULT _sapp_d3d11_QueryInterface(ID3D11Device* self, REFIID riid, void** ppvObject) { + #if defined(__cplusplus) + return self->QueryInterface(riid, ppvObject); + #else + return self->lpVtbl->QueryInterface(self, riid, ppvObject); + #endif +} + +static inline HRESULT _sapp_d3d11_CreateRenderTargetView(ID3D11Device* self, ID3D11Resource *pResource, const D3D11_RENDER_TARGET_VIEW_DESC* pDesc, ID3D11RenderTargetView** ppRTView) { + #if defined(__cplusplus) + return self->CreateRenderTargetView(pResource, pDesc, ppRTView); + #else + return self->lpVtbl->CreateRenderTargetView(self, pResource, pDesc, ppRTView); + #endif +} + +static inline HRESULT _sapp_d3d11_CreateTexture2D(ID3D11Device* self, const D3D11_TEXTURE2D_DESC* pDesc, const D3D11_SUBRESOURCE_DATA* pInitialData, ID3D11Texture2D** ppTexture2D) { + #if defined(__cplusplus) + return self->CreateTexture2D(pDesc, pInitialData, ppTexture2D); + #else + return self->lpVtbl->CreateTexture2D(self, pDesc, pInitialData, ppTexture2D); + #endif +} + +static inline HRESULT _sapp_d3d11_CreateDepthStencilView(ID3D11Device* self, ID3D11Resource* pResource, const D3D11_DEPTH_STENCIL_VIEW_DESC* pDesc, ID3D11DepthStencilView** ppDepthStencilView) { + #if defined(__cplusplus) + return self->CreateDepthStencilView(pResource, pDesc, ppDepthStencilView); + #else + return self->lpVtbl->CreateDepthStencilView(self, pResource, pDesc, ppDepthStencilView); + #endif +} + +static inline HRESULT _sapp_dxgi_ResizeBuffers(IDXGISwapChain* self, UINT BufferCount, UINT Width, UINT Height, DXGI_FORMAT NewFormat, UINT SwapChainFlags) { + #if defined(__cplusplus) + return self->ResizeBuffers(BufferCount, Width, Height, NewFormat, SwapChainFlags); + #else + return self->lpVtbl->ResizeBuffers(self, BufferCount, Width, Height, NewFormat, SwapChainFlags); + #endif +} + +static inline HRESULT _sapp_dxgi_Present(IDXGISwapChain* self, UINT SyncInterval, UINT Flags) { + #if defined(__cplusplus) + return self->Present(SyncInterval, Flags); + #else + return self->lpVtbl->Present(self, SyncInterval, Flags); + #endif +} + +static inline HRESULT _sapp_dxgi_GetFrameStatistics(IDXGISwapChain* self, DXGI_FRAME_STATISTICS* pStats) { + #if defined(__cplusplus) + return self->GetFrameStatistics(pStats); + #else + return self->lpVtbl->GetFrameStatistics(self, pStats); + #endif +} + +static inline HRESULT _sapp_dxgi_SetMaximumFrameLatency(IDXGIDevice1* self, UINT MaxLatency) { + #if defined(__cplusplus) + return self->SetMaximumFrameLatency(MaxLatency); + #else + return self->lpVtbl->SetMaximumFrameLatency(self, MaxLatency); + #endif +} + +static inline HRESULT _sapp_dxgi_GetAdapter(IDXGIDevice1* self, IDXGIAdapter** pAdapter) { + #if defined(__cplusplus) + return self->GetAdapter(pAdapter); + #else + return self->lpVtbl->GetAdapter(self, pAdapter); + #endif +} + +static inline HRESULT _sapp_dxgi_GetParent(IDXGIObject* self, REFIID riid, void** ppParent) { + #if defined(__cplusplus) + return self->GetParent(riid, ppParent); + #else + return self->lpVtbl->GetParent(self, riid, ppParent); + #endif +} + +static inline HRESULT _sapp_dxgi_MakeWindowAssociation(IDXGIFactory* self, HWND WindowHandle, UINT Flags) { + #if defined(__cplusplus) + return self->MakeWindowAssociation(WindowHandle, Flags); + #else + return self->lpVtbl->MakeWindowAssociation(self, WindowHandle, Flags); + #endif +} + +_SOKOL_PRIVATE void _sapp_d3d11_create_device_and_swapchain(void) { + DXGI_SWAP_CHAIN_DESC* sc_desc = &_sapp.d3d11.swap_chain_desc; + sc_desc->BufferDesc.Width = (UINT)_sapp.framebuffer_width; + sc_desc->BufferDesc.Height = (UINT)_sapp.framebuffer_height; + sc_desc->BufferDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; + sc_desc->BufferDesc.RefreshRate.Numerator = 60; + sc_desc->BufferDesc.RefreshRate.Denominator = 1; + sc_desc->OutputWindow = _sapp.win32.hwnd; + sc_desc->Windowed = true; + if (_sapp.win32.is_win10_or_greater) { + sc_desc->BufferCount = 2; + sc_desc->SwapEffect = (DXGI_SWAP_EFFECT) _SAPP_DXGI_SWAP_EFFECT_FLIP_DISCARD; + _sapp.d3d11.use_dxgi_frame_stats = true; + } else { + sc_desc->BufferCount = 1; + sc_desc->SwapEffect = DXGI_SWAP_EFFECT_DISCARD; + _sapp.d3d11.use_dxgi_frame_stats = false; + } + sc_desc->SampleDesc.Count = 1; + sc_desc->SampleDesc.Quality = 0; + sc_desc->BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; + UINT create_flags = D3D11_CREATE_DEVICE_SINGLETHREADED | D3D11_CREATE_DEVICE_BGRA_SUPPORT; + #if defined(SOKOL_DEBUG) + create_flags |= D3D11_CREATE_DEVICE_DEBUG; + #endif + D3D_FEATURE_LEVEL requested_feature_levels[] = { D3D_FEATURE_LEVEL_11_1, D3D_FEATURE_LEVEL_11_0 }; + D3D_FEATURE_LEVEL result_feature_level; + HRESULT hr = D3D11CreateDeviceAndSwapChain( + NULL, /* pAdapter (use default) */ + D3D_DRIVER_TYPE_HARDWARE, /* DriverType */ + NULL, /* Software */ + create_flags, /* Flags */ + requested_feature_levels, /* pFeatureLevels */ + 2, /* FeatureLevels */ + D3D11_SDK_VERSION, /* SDKVersion */ + sc_desc, /* pSwapChainDesc */ + &_sapp.d3d11.swap_chain, /* ppSwapChain */ + &_sapp.d3d11.device, /* ppDevice */ + &result_feature_level, /* pFeatureLevel */ + &_sapp.d3d11.device_context); /* ppImmediateContext */ + _SOKOL_UNUSED(hr); + #if defined(SOKOL_DEBUG) + if (!SUCCEEDED(hr)) { + // if initialization with D3D11_CREATE_DEVICE_DEBUG fails, this could be because the + // 'D3D11 debug layer' stopped working, indicated by the error message: + // === + // D3D11CreateDevice: Flags (0x2) were specified which require the D3D11 SDK Layers for Windows 10, but they are not present on the system. + // These flags must be removed, or the Windows 10 SDK must be installed. + // Flags include: D3D11_CREATE_DEVICE_DEBUG + // === + // + // ...just retry with the DEBUG flag switched off + _SAPP_ERROR(WIN32_D3D11_CREATE_DEVICE_AND_SWAPCHAIN_WITH_DEBUG_FAILED); + create_flags &= ~(UINT)D3D11_CREATE_DEVICE_DEBUG; + hr = D3D11CreateDeviceAndSwapChain( + NULL, /* pAdapter (use default) */ + D3D_DRIVER_TYPE_HARDWARE, /* DriverType */ + NULL, /* Software */ + create_flags, /* Flags */ + requested_feature_levels, /* pFeatureLevels */ + 2, /* FeatureLevels */ + D3D11_SDK_VERSION, /* SDKVersion */ + sc_desc, /* pSwapChainDesc */ + &_sapp.d3d11.swap_chain, /* ppSwapChain */ + &_sapp.d3d11.device, /* ppDevice */ + &result_feature_level, /* pFeatureLevel */ + &_sapp.d3d11.device_context); /* ppImmediateContext */ + } + #endif + SOKOL_ASSERT(SUCCEEDED(hr) && _sapp.d3d11.swap_chain && _sapp.d3d11.device && _sapp.d3d11.device_context); + + // minimize frame latency, disable Alt-Enter + hr = _sapp_d3d11_QueryInterface(_sapp.d3d11.device, _sapp_win32_refiid(_sapp_IID_IDXGIDevice1), (void**)&_sapp.d3d11.dxgi_device); + if (SUCCEEDED(hr) && _sapp.d3d11.dxgi_device) { + _sapp_dxgi_SetMaximumFrameLatency(_sapp.d3d11.dxgi_device, 1); + IDXGIAdapter* dxgi_adapter = 0; + hr = _sapp_dxgi_GetAdapter(_sapp.d3d11.dxgi_device, &dxgi_adapter); + if (SUCCEEDED(hr) && dxgi_adapter) { + IDXGIFactory* dxgi_factory = 0; + hr = _sapp_dxgi_GetParent((IDXGIObject*)dxgi_adapter, _sapp_win32_refiid(_sapp_IID_IDXGIFactory), (void**)&dxgi_factory); + if (SUCCEEDED(hr)) { + _sapp_dxgi_MakeWindowAssociation(dxgi_factory, _sapp.win32.hwnd, DXGI_MWA_NO_ALT_ENTER|DXGI_MWA_NO_PRINT_SCREEN); + _SAPP_SAFE_RELEASE(dxgi_factory); + } else { + _SAPP_ERROR(WIN32_D3D11_GET_IDXGIFACTORY_FAILED); + } + _SAPP_SAFE_RELEASE(dxgi_adapter); + } else { + _SAPP_ERROR(WIN32_D3D11_GET_IDXGIADAPTER_FAILED); + } + } else { + _SAPP_PANIC(WIN32_D3D11_QUERY_INTERFACE_IDXGIDEVICE1_FAILED); + } +} + +_SOKOL_PRIVATE void _sapp_d3d11_destroy_device_and_swapchain(void) { + _SAPP_SAFE_RELEASE(_sapp.d3d11.swap_chain); + _SAPP_SAFE_RELEASE(_sapp.d3d11.dxgi_device); + _SAPP_SAFE_RELEASE(_sapp.d3d11.device_context); + _SAPP_SAFE_RELEASE(_sapp.d3d11.device); +} + +_SOKOL_PRIVATE void _sapp_d3d11_create_default_render_target(void) { + SOKOL_ASSERT(0 == _sapp.d3d11.rt); + SOKOL_ASSERT(0 == _sapp.d3d11.rtv); + SOKOL_ASSERT(0 == _sapp.d3d11.msaa_rt); + SOKOL_ASSERT(0 == _sapp.d3d11.msaa_rtv); + SOKOL_ASSERT(0 == _sapp.d3d11.ds); + SOKOL_ASSERT(0 == _sapp.d3d11.dsv); + + HRESULT hr; _SOKOL_UNUSED(hr); + + /* view for the swapchain-created framebuffer */ + hr = _sapp_dxgi_GetBuffer(_sapp.d3d11.swap_chain, 0, _sapp_win32_refiid(_sapp_IID_ID3D11Texture2D), (void**)&_sapp.d3d11.rt); + SOKOL_ASSERT(SUCCEEDED(hr) && _sapp.d3d11.rt); + hr = _sapp_d3d11_CreateRenderTargetView(_sapp.d3d11.device, (ID3D11Resource*)_sapp.d3d11.rt, NULL, &_sapp.d3d11.rtv); + SOKOL_ASSERT(SUCCEEDED(hr) && _sapp.d3d11.rtv); + + /* common desc for MSAA and depth-stencil texture */ + D3D11_TEXTURE2D_DESC tex_desc; + _sapp_clear(&tex_desc, sizeof(tex_desc)); + tex_desc.Width = (UINT)_sapp.framebuffer_width; + tex_desc.Height = (UINT)_sapp.framebuffer_height; + tex_desc.MipLevels = 1; + tex_desc.ArraySize = 1; + tex_desc.Usage = D3D11_USAGE_DEFAULT; + tex_desc.BindFlags = D3D11_BIND_RENDER_TARGET; + tex_desc.SampleDesc.Count = (UINT) _sapp.sample_count; + tex_desc.SampleDesc.Quality = (UINT) (_sapp.sample_count > 1 ? D3D11_STANDARD_MULTISAMPLE_PATTERN : 0); + + /* create MSAA texture and view if antialiasing requested */ + if (_sapp.sample_count > 1) { + tex_desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; + hr = _sapp_d3d11_CreateTexture2D(_sapp.d3d11.device, &tex_desc, NULL, &_sapp.d3d11.msaa_rt); + SOKOL_ASSERT(SUCCEEDED(hr) && _sapp.d3d11.msaa_rt); + hr = _sapp_d3d11_CreateRenderTargetView(_sapp.d3d11.device, (ID3D11Resource*)_sapp.d3d11.msaa_rt, NULL, &_sapp.d3d11.msaa_rtv); + SOKOL_ASSERT(SUCCEEDED(hr) && _sapp.d3d11.msaa_rtv); + } + + /* texture and view for the depth-stencil-surface */ + tex_desc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT; + tex_desc.BindFlags = D3D11_BIND_DEPTH_STENCIL; + hr = _sapp_d3d11_CreateTexture2D(_sapp.d3d11.device, &tex_desc, NULL, &_sapp.d3d11.ds); + SOKOL_ASSERT(SUCCEEDED(hr) && _sapp.d3d11.ds); + hr = _sapp_d3d11_CreateDepthStencilView(_sapp.d3d11.device, (ID3D11Resource*)_sapp.d3d11.ds, NULL, &_sapp.d3d11.dsv); + SOKOL_ASSERT(SUCCEEDED(hr) && _sapp.d3d11.dsv); +} + +_SOKOL_PRIVATE void _sapp_d3d11_destroy_default_render_target(void) { + _SAPP_SAFE_RELEASE(_sapp.d3d11.rt); + _SAPP_SAFE_RELEASE(_sapp.d3d11.rtv); + _SAPP_SAFE_RELEASE(_sapp.d3d11.msaa_rt); + _SAPP_SAFE_RELEASE(_sapp.d3d11.msaa_rtv); + _SAPP_SAFE_RELEASE(_sapp.d3d11.ds); + _SAPP_SAFE_RELEASE(_sapp.d3d11.dsv); +} + +_SOKOL_PRIVATE void _sapp_d3d11_resize_default_render_target(void) { + if (_sapp.d3d11.swap_chain) { + _sapp_d3d11_destroy_default_render_target(); + _sapp_dxgi_ResizeBuffers(_sapp.d3d11.swap_chain, _sapp.d3d11.swap_chain_desc.BufferCount, (UINT)_sapp.framebuffer_width, (UINT)_sapp.framebuffer_height, DXGI_FORMAT_B8G8R8A8_UNORM, 0); + _sapp_d3d11_create_default_render_target(); + } +} + +_SOKOL_PRIVATE void _sapp_d3d11_present(bool do_not_wait) { + UINT flags = 0; + if (_sapp.win32.is_win10_or_greater && do_not_wait) { + /* this hack/workaround somewhat improves window-movement and -sizing + responsiveness when rendering is controlled via WM_TIMER during window + move and resize on NVIDIA cards on Win10 with recent drivers. + */ + flags = DXGI_PRESENT_DO_NOT_WAIT; + } + _sapp_dxgi_Present(_sapp.d3d11.swap_chain, (UINT)_sapp.swap_interval, flags); +} + +#endif /* SOKOL_D3D11 */ + +#if defined(SOKOL_GLCORE) +_SOKOL_PRIVATE void _sapp_wgl_init(void) { + _sapp.wgl.opengl32 = LoadLibraryA("opengl32.dll"); + if (!_sapp.wgl.opengl32) { + _SAPP_PANIC(WIN32_LOAD_OPENGL32_DLL_FAILED); + } + SOKOL_ASSERT(_sapp.wgl.opengl32); + _sapp.wgl.CreateContext = (PFN_wglCreateContext)(void*) GetProcAddress(_sapp.wgl.opengl32, "wglCreateContext"); + SOKOL_ASSERT(_sapp.wgl.CreateContext); + _sapp.wgl.DeleteContext = (PFN_wglDeleteContext)(void*) GetProcAddress(_sapp.wgl.opengl32, "wglDeleteContext"); + SOKOL_ASSERT(_sapp.wgl.DeleteContext); + _sapp.wgl.GetProcAddress = (PFN_wglGetProcAddress)(void*) GetProcAddress(_sapp.wgl.opengl32, "wglGetProcAddress"); + SOKOL_ASSERT(_sapp.wgl.GetProcAddress); + _sapp.wgl.GetCurrentDC = (PFN_wglGetCurrentDC)(void*) GetProcAddress(_sapp.wgl.opengl32, "wglGetCurrentDC"); + SOKOL_ASSERT(_sapp.wgl.GetCurrentDC); + _sapp.wgl.MakeCurrent = (PFN_wglMakeCurrent)(void*) GetProcAddress(_sapp.wgl.opengl32, "wglMakeCurrent"); + SOKOL_ASSERT(_sapp.wgl.MakeCurrent); + _sapp.wgl.GetIntegerv = (void(WINAPI*)(uint32_t, int32_t*)) GetProcAddress(_sapp.wgl.opengl32, "glGetIntegerv"); + SOKOL_ASSERT(_sapp.wgl.GetIntegerv); + + _sapp.wgl.msg_hwnd = CreateWindowExW(WS_EX_OVERLAPPEDWINDOW, + L"SOKOLAPP", + L"sokol-app message window", + WS_CLIPSIBLINGS|WS_CLIPCHILDREN, + 0, 0, 1, 1, + NULL, NULL, + GetModuleHandleW(NULL), + NULL); + if (!_sapp.wgl.msg_hwnd) { + _SAPP_PANIC(WIN32_CREATE_HELPER_WINDOW_FAILED); + } + SOKOL_ASSERT(_sapp.wgl.msg_hwnd); + ShowWindow(_sapp.wgl.msg_hwnd, SW_HIDE); + MSG msg; + while (PeekMessageW(&msg, _sapp.wgl.msg_hwnd, 0, 0, PM_REMOVE)) { + TranslateMessage(&msg); + DispatchMessageW(&msg); + } + _sapp.wgl.msg_dc = GetDC(_sapp.wgl.msg_hwnd); + if (!_sapp.wgl.msg_dc) { + _SAPP_PANIC(WIN32_HELPER_WINDOW_GETDC_FAILED); + } +} + +_SOKOL_PRIVATE void _sapp_wgl_shutdown(void) { + SOKOL_ASSERT(_sapp.wgl.opengl32 && _sapp.wgl.msg_hwnd); + DestroyWindow(_sapp.wgl.msg_hwnd); _sapp.wgl.msg_hwnd = 0; + FreeLibrary(_sapp.wgl.opengl32); _sapp.wgl.opengl32 = 0; +} + +_SOKOL_PRIVATE bool _sapp_wgl_has_ext(const char* ext, const char* extensions) { + SOKOL_ASSERT(ext && extensions); + const char* start = extensions; + while (true) { + const char* where = strstr(start, ext); + if (!where) { + return false; + } + const char* terminator = where + strlen(ext); + if ((where == start) || (*(where - 1) == ' ')) { + if (*terminator == ' ' || *terminator == '\0') { + break; + } + } + start = terminator; + } + return true; +} + +_SOKOL_PRIVATE bool _sapp_wgl_ext_supported(const char* ext) { + SOKOL_ASSERT(ext); + if (_sapp.wgl.GetExtensionsStringEXT) { + const char* extensions = _sapp.wgl.GetExtensionsStringEXT(); + if (extensions) { + if (_sapp_wgl_has_ext(ext, extensions)) { + return true; + } + } + } + if (_sapp.wgl.GetExtensionsStringARB) { + const char* extensions = _sapp.wgl.GetExtensionsStringARB(_sapp.wgl.GetCurrentDC()); + if (extensions) { + if (_sapp_wgl_has_ext(ext, extensions)) { + return true; + } + } + } + return false; +} + +_SOKOL_PRIVATE void _sapp_wgl_load_extensions(void) { + SOKOL_ASSERT(_sapp.wgl.msg_dc); + PIXELFORMATDESCRIPTOR pfd; + _sapp_clear(&pfd, sizeof(pfd)); + pfd.nSize = sizeof(pfd); + pfd.nVersion = 1; + pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER; + pfd.iPixelType = PFD_TYPE_RGBA; + pfd.cColorBits = 24; + if (!SetPixelFormat(_sapp.wgl.msg_dc, ChoosePixelFormat(_sapp.wgl.msg_dc, &pfd), &pfd)) { + _SAPP_PANIC(WIN32_DUMMY_CONTEXT_SET_PIXELFORMAT_FAILED); + } + HGLRC rc = _sapp.wgl.CreateContext(_sapp.wgl.msg_dc); + if (!rc) { + _SAPP_PANIC(WIN32_CREATE_DUMMY_CONTEXT_FAILED); + } + if (!_sapp.wgl.MakeCurrent(_sapp.wgl.msg_dc, rc)) { + _SAPP_PANIC(WIN32_DUMMY_CONTEXT_MAKE_CURRENT_FAILED); + } + _sapp.wgl.GetExtensionsStringEXT = (PFNWGLGETEXTENSIONSSTRINGEXTPROC)(void*) _sapp.wgl.GetProcAddress("wglGetExtensionsStringEXT"); + _sapp.wgl.GetExtensionsStringARB = (PFNWGLGETEXTENSIONSSTRINGARBPROC)(void*) _sapp.wgl.GetProcAddress("wglGetExtensionsStringARB"); + _sapp.wgl.CreateContextAttribsARB = (PFNWGLCREATECONTEXTATTRIBSARBPROC)(void*) _sapp.wgl.GetProcAddress("wglCreateContextAttribsARB"); + _sapp.wgl.SwapIntervalEXT = (PFNWGLSWAPINTERVALEXTPROC)(void*) _sapp.wgl.GetProcAddress("wglSwapIntervalEXT"); + _sapp.wgl.GetPixelFormatAttribivARB = (PFNWGLGETPIXELFORMATATTRIBIVARBPROC)(void*) _sapp.wgl.GetProcAddress("wglGetPixelFormatAttribivARB"); + _sapp.wgl.arb_multisample = _sapp_wgl_ext_supported("WGL_ARB_multisample"); + _sapp.wgl.arb_create_context = _sapp_wgl_ext_supported("WGL_ARB_create_context"); + _sapp.wgl.arb_create_context_profile = _sapp_wgl_ext_supported("WGL_ARB_create_context_profile"); + _sapp.wgl.ext_swap_control = _sapp_wgl_ext_supported("WGL_EXT_swap_control"); + _sapp.wgl.arb_pixel_format = _sapp_wgl_ext_supported("WGL_ARB_pixel_format"); + _sapp.wgl.MakeCurrent(_sapp.wgl.msg_dc, 0); + _sapp.wgl.DeleteContext(rc); +} + +_SOKOL_PRIVATE int _sapp_wgl_attrib(int pixel_format, int attrib) { + SOKOL_ASSERT(_sapp.wgl.arb_pixel_format); + int value = 0; + if (!_sapp.wgl.GetPixelFormatAttribivARB(_sapp.win32.dc, pixel_format, 0, 1, &attrib, &value)) { + _SAPP_PANIC(WIN32_GET_PIXELFORMAT_ATTRIB_FAILED); + } + return value; +} + +_SOKOL_PRIVATE void _sapp_wgl_attribiv(int pixel_format, int num_attribs, const int* attribs, int* results) { + SOKOL_ASSERT(_sapp.wgl.arb_pixel_format); + if (!_sapp.wgl.GetPixelFormatAttribivARB(_sapp.win32.dc, pixel_format, 0, num_attribs, attribs, results)) { + _SAPP_PANIC(WIN32_GET_PIXELFORMAT_ATTRIB_FAILED); + } +} + +_SOKOL_PRIVATE int _sapp_wgl_find_pixel_format(void) { + SOKOL_ASSERT(_sapp.win32.dc); + SOKOL_ASSERT(_sapp.wgl.arb_pixel_format); + + #define _sapp_wgl_num_query_tags (12) + const int query_tags[_sapp_wgl_num_query_tags] = { + WGL_SUPPORT_OPENGL_ARB, + WGL_DRAW_TO_WINDOW_ARB, + WGL_PIXEL_TYPE_ARB, + WGL_ACCELERATION_ARB, + WGL_DOUBLE_BUFFER_ARB, + WGL_RED_BITS_ARB, + WGL_GREEN_BITS_ARB, + WGL_BLUE_BITS_ARB, + WGL_ALPHA_BITS_ARB, + WGL_DEPTH_BITS_ARB, + WGL_STENCIL_BITS_ARB, + WGL_SAMPLES_ARB, + }; + const int result_support_opengl_index = 0; + const int result_draw_to_window_index = 1; + const int result_pixel_type_index = 2; + const int result_acceleration_index = 3; + const int result_double_buffer_index = 4; + const int result_red_bits_index = 5; + const int result_green_bits_index = 6; + const int result_blue_bits_index = 7; + const int result_alpha_bits_index = 8; + const int result_depth_bits_index = 9; + const int result_stencil_bits_index = 10; + const int result_samples_index = 11; + + int query_results[_sapp_wgl_num_query_tags] = {0}; + // Drop the last item if multisample extension is not supported. + // If in future querying with multiple extensions, will have to shuffle index values to have active extensions on the end. + int query_count = _sapp_wgl_num_query_tags; + if (!_sapp.wgl.arb_multisample) { + query_count = _sapp_wgl_num_query_tags - 1; + } + + int native_count = _sapp_wgl_attrib(1, WGL_NUMBER_PIXEL_FORMATS_ARB); + + _sapp_gl_fbconfig desired; + _sapp_gl_init_fbconfig(&desired); + desired.red_bits = 8; + desired.green_bits = 8; + desired.blue_bits = 8; + desired.alpha_bits = 8; + desired.depth_bits = 24; + desired.stencil_bits = 8; + desired.doublebuffer = true; + desired.samples = (_sapp.sample_count > 1) ? _sapp.sample_count : 0; + + int pixel_format = 0; + + _sapp_gl_fbselect fbselect; + _sapp_gl_init_fbselect(&fbselect); + for (int i = 0; i < native_count; i++) { + const int n = i + 1; + _sapp_wgl_attribiv(n, query_count, query_tags, query_results); + + if (query_results[result_support_opengl_index] == 0 + || query_results[result_draw_to_window_index] == 0 + || query_results[result_pixel_type_index] != WGL_TYPE_RGBA_ARB + || query_results[result_acceleration_index] == WGL_NO_ACCELERATION_ARB) + { + continue; + } + + _sapp_gl_fbconfig u; + _sapp_clear(&u, sizeof(u)); + u.red_bits = query_results[result_red_bits_index]; + u.green_bits = query_results[result_green_bits_index]; + u.blue_bits = query_results[result_blue_bits_index]; + u.alpha_bits = query_results[result_alpha_bits_index]; + u.depth_bits = query_results[result_depth_bits_index]; + u.stencil_bits = query_results[result_stencil_bits_index]; + u.doublebuffer = 0 != query_results[result_double_buffer_index]; + u.samples = query_results[result_samples_index]; // NOTE: If arb_multisample is not supported - just takes the default 0 + + // Test if this pixel format is better than the previous one + if (_sapp_gl_select_fbconfig(&fbselect, &desired, &u)) { + pixel_format = (uintptr_t)n; + + // Early exit if matching as good as possible + if (fbselect.best_match) { + break; + } + } + } + + return pixel_format; +} + +_SOKOL_PRIVATE void _sapp_wgl_create_context(void) { + int pixel_format = _sapp_wgl_find_pixel_format(); + if (0 == pixel_format) { + _SAPP_PANIC(WIN32_WGL_FIND_PIXELFORMAT_FAILED); + } + PIXELFORMATDESCRIPTOR pfd; + if (!DescribePixelFormat(_sapp.win32.dc, pixel_format, sizeof(pfd), &pfd)) { + _SAPP_PANIC(WIN32_WGL_DESCRIBE_PIXELFORMAT_FAILED); + } + if (!SetPixelFormat(_sapp.win32.dc, pixel_format, &pfd)) { + _SAPP_PANIC(WIN32_WGL_SET_PIXELFORMAT_FAILED); + } + if (!_sapp.wgl.arb_create_context) { + _SAPP_PANIC(WIN32_WGL_ARB_CREATE_CONTEXT_REQUIRED); + } + if (!_sapp.wgl.arb_create_context_profile) { + _SAPP_PANIC(WIN32_WGL_ARB_CREATE_CONTEXT_PROFILE_REQUIRED); + } + const int attrs[] = { + WGL_CONTEXT_MAJOR_VERSION_ARB, _sapp.desc.gl_major_version, + WGL_CONTEXT_MINOR_VERSION_ARB, _sapp.desc.gl_minor_version, +#if defined(SOKOL_DEBUG) + WGL_CONTEXT_FLAGS_ARB, WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB | WGL_CONTEXT_DEBUG_BIT_ARB, +#else + WGL_CONTEXT_FLAGS_ARB, WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB, +#endif + WGL_CONTEXT_PROFILE_MASK_ARB, WGL_CONTEXT_CORE_PROFILE_BIT_ARB, + 0, 0 + }; + _sapp.wgl.gl_ctx = _sapp.wgl.CreateContextAttribsARB(_sapp.win32.dc, 0, attrs); + if (!_sapp.wgl.gl_ctx) { + const DWORD err = GetLastError(); + if (err == (0xc0070000 | ERROR_INVALID_VERSION_ARB)) { + _SAPP_PANIC(WIN32_WGL_OPENGL_VERSION_NOT_SUPPORTED); + } else if (err == (0xc0070000 | ERROR_INVALID_PROFILE_ARB)) { + _SAPP_PANIC(WIN32_WGL_OPENGL_PROFILE_NOT_SUPPORTED); + } else if (err == (0xc0070000 | ERROR_INCOMPATIBLE_DEVICE_CONTEXTS_ARB)) { + _SAPP_PANIC(WIN32_WGL_INCOMPATIBLE_DEVICE_CONTEXT); + } else { + _SAPP_PANIC(WIN32_WGL_CREATE_CONTEXT_ATTRIBS_FAILED_OTHER); + } + } + _sapp.wgl.MakeCurrent(_sapp.win32.dc, _sapp.wgl.gl_ctx); + if (_sapp.wgl.ext_swap_control) { + /* FIXME: DwmIsCompositionEnabled() (see GLFW) */ + _sapp.wgl.SwapIntervalEXT(_sapp.swap_interval); + } + const uint32_t gl_framebuffer_binding = 0x8CA6; + _sapp.wgl.GetIntegerv(gl_framebuffer_binding, (int32_t*)&_sapp.gl.framebuffer); +} + +_SOKOL_PRIVATE void _sapp_wgl_destroy_context(void) { + SOKOL_ASSERT(_sapp.wgl.gl_ctx); + _sapp.wgl.DeleteContext(_sapp.wgl.gl_ctx); + _sapp.wgl.gl_ctx = 0; +} + +_SOKOL_PRIVATE void _sapp_wgl_swap_buffers(void) { + SOKOL_ASSERT(_sapp.win32.dc); + /* FIXME: DwmIsCompositionEnabled? (see GLFW) */ + SwapBuffers(_sapp.win32.dc); +} +#endif /* SOKOL_GLCORE */ + +_SOKOL_PRIVATE bool _sapp_win32_wide_to_utf8(const wchar_t* src, char* dst, int dst_num_bytes) { + SOKOL_ASSERT(src && dst && (dst_num_bytes > 1)); + _sapp_clear(dst, (size_t)dst_num_bytes); + const int bytes_needed = WideCharToMultiByte(CP_UTF8, 0, src, -1, NULL, 0, NULL, NULL); + if (bytes_needed <= dst_num_bytes) { + WideCharToMultiByte(CP_UTF8, 0, src, -1, dst, dst_num_bytes, NULL, NULL); + return true; + } else { + return false; + } +} + +/* updates current window and framebuffer size from the window's client rect, returns true if size has changed */ +_SOKOL_PRIVATE bool _sapp_win32_update_dimensions(void) { + RECT rect; + if (GetClientRect(_sapp.win32.hwnd, &rect)) { + float window_width = (float)(rect.right - rect.left) / _sapp.win32.dpi.window_scale; + float window_height = (float)(rect.bottom - rect.top) / _sapp.win32.dpi.window_scale; + _sapp.window_width = _sapp_roundf_gzero(window_width); + _sapp.window_height = _sapp_roundf_gzero(window_height); + int fb_width = _sapp_roundf_gzero(window_width * _sapp.win32.dpi.content_scale); + int fb_height = _sapp_roundf_gzero(window_height * _sapp.win32.dpi.content_scale); + if ((fb_width != _sapp.framebuffer_width) || (fb_height != _sapp.framebuffer_height)) { + _sapp.framebuffer_width = fb_width; + _sapp.framebuffer_height = fb_height; + return true; + } + } else { + _sapp.window_width = _sapp.window_height = 1; + _sapp.framebuffer_width = _sapp.framebuffer_height = 1; + } + return false; +} + +_SOKOL_PRIVATE void _sapp_win32_set_fullscreen(bool fullscreen, UINT swp_flags) { + HMONITOR monitor = MonitorFromWindow(_sapp.win32.hwnd, MONITOR_DEFAULTTONEAREST); + MONITORINFO minfo; + _sapp_clear(&minfo, sizeof(minfo)); + minfo.cbSize = sizeof(MONITORINFO); + GetMonitorInfo(monitor, &minfo); + const RECT mr = minfo.rcMonitor; + const int monitor_w = mr.right - mr.left; + const int monitor_h = mr.bottom - mr.top; + + const DWORD win_ex_style = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE; + DWORD win_style; + RECT rect = { 0, 0, 0, 0 }; + + _sapp.fullscreen = fullscreen; + if (!_sapp.fullscreen) { + win_style = WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_SIZEBOX; + rect = _sapp.win32.stored_window_rect; + } else { + GetWindowRect(_sapp.win32.hwnd, &_sapp.win32.stored_window_rect); + win_style = WS_POPUP | WS_SYSMENU | WS_VISIBLE; + rect.left = mr.left; + rect.top = mr.top; + rect.right = rect.left + monitor_w; + rect.bottom = rect.top + monitor_h; + AdjustWindowRectEx(&rect, win_style, FALSE, win_ex_style); + } + const int win_w = rect.right - rect.left; + const int win_h = rect.bottom - rect.top; + const int win_x = rect.left; + const int win_y = rect.top; + SetWindowLongPtr(_sapp.win32.hwnd, GWL_STYLE, win_style); + SetWindowPos(_sapp.win32.hwnd, HWND_TOP, win_x, win_y, win_w, win_h, swp_flags | SWP_FRAMECHANGED); +} + +_SOKOL_PRIVATE void _sapp_win32_toggle_fullscreen(void) { + _sapp_win32_set_fullscreen(!_sapp.fullscreen, SWP_SHOWWINDOW); +} + +_SOKOL_PRIVATE void _sapp_win32_init_cursor(sapp_mouse_cursor cursor) { + SOKOL_ASSERT((cursor >= 0) && (cursor < _SAPP_MOUSECURSOR_NUM)); + // NOTE: the OCR_* constants are only defined if OEMRESOURCE is defined + // before windows.h is included, but we can't guarantee that because + // the sokol_app.h implementation may be included with other implementations + // in the same compilation unit + int id = 0; + switch (cursor) { + case SAPP_MOUSECURSOR_ARROW: id = 32512; break; // OCR_NORMAL + case SAPP_MOUSECURSOR_IBEAM: id = 32513; break; // OCR_IBEAM + case SAPP_MOUSECURSOR_CROSSHAIR: id = 32515; break; // OCR_CROSS + case SAPP_MOUSECURSOR_POINTING_HAND: id = 32649; break; // OCR_HAND + case SAPP_MOUSECURSOR_RESIZE_EW: id = 32644; break; // OCR_SIZEWE + case SAPP_MOUSECURSOR_RESIZE_NS: id = 32645; break; // OCR_SIZENS + case SAPP_MOUSECURSOR_RESIZE_NWSE: id = 32642; break; // OCR_SIZENWSE + case SAPP_MOUSECURSOR_RESIZE_NESW: id = 32643; break; // OCR_SIZENESW + case SAPP_MOUSECURSOR_RESIZE_ALL: id = 32646; break; // OCR_SIZEALL + case SAPP_MOUSECURSOR_NOT_ALLOWED: id = 32648; break; // OCR_NO + default: break; + } + if (id != 0) { + _sapp.win32.standard_cursors[cursor] = (HCURSOR)LoadImageW(NULL, MAKEINTRESOURCEW(id), IMAGE_CURSOR, 0, 0, LR_DEFAULTSIZE|LR_SHARED); + } + // fallback: default cursor + if (0 == _sapp.win32.standard_cursors[cursor]) { + // 32512 => IDC_ARROW + _sapp.win32.standard_cursors[cursor] = LoadCursorW(NULL, MAKEINTRESOURCEW(32512)); + } + SOKOL_ASSERT(0 != _sapp.win32.standard_cursors[cursor]); +} + +_SOKOL_PRIVATE void _sapp_win32_init_cursors(void) { + for (int i = 0; i < _SAPP_MOUSECURSOR_NUM; i++) { + _sapp_win32_init_cursor((sapp_mouse_cursor)i); + } +} + +_SOKOL_PRIVATE bool _sapp_win32_cursor_in_content_area(void) { + POINT pos; + if (!GetCursorPos(&pos)) { + return false; + } + if (WindowFromPoint(pos) != _sapp.win32.hwnd) { + return false; + } + RECT area; + GetClientRect(_sapp.win32.hwnd, &area); + ClientToScreen(_sapp.win32.hwnd, (POINT*)&area.left); + ClientToScreen(_sapp.win32.hwnd, (POINT*)&area.right); + return PtInRect(&area, pos) == TRUE; +} + +_SOKOL_PRIVATE void _sapp_win32_update_cursor(sapp_mouse_cursor cursor, bool shown, bool skip_area_test) { + SOKOL_ASSERT((cursor >= 0) && (cursor < _SAPP_MOUSECURSOR_NUM)); + + // NOTE: when called from WM_SETCURSOR, the area test would be redundant + if (!skip_area_test) { + if (!_sapp_win32_cursor_in_content_area()) { + return; + } + } + HCURSOR cursor_handle = NULL; + if (shown) { + if (_sapp.custom_cursor_bound[cursor]) { + SOKOL_ASSERT(_sapp.win32.custom_cursors[cursor]); + cursor_handle = _sapp.win32.custom_cursors[cursor]; + SOKOL_ASSERT(0 != cursor_handle); + } else { + cursor_handle = _sapp.win32.standard_cursors[cursor]; + SOKOL_ASSERT(0 != cursor_handle); + } + } + SetCursor(cursor_handle); +} + +_SOKOL_PRIVATE void _sapp_win32_capture_mouse(uint8_t btn_mask) { + if (0 == _sapp.win32.mouse.capture_mask) { + SetCapture(_sapp.win32.hwnd); + } + _sapp.win32.mouse.capture_mask |= btn_mask; +} + +_SOKOL_PRIVATE void _sapp_win32_release_mouse(uint8_t btn_mask) { + if (0 != _sapp.win32.mouse.capture_mask) { + _sapp.win32.mouse.capture_mask &= ~btn_mask; + if (0 == _sapp.win32.mouse.capture_mask) { + ReleaseCapture(); + } + } +} + +_SOKOL_PRIVATE bool _sapp_win32_is_foreground_window(void) { + return _sapp.win32.hwnd == GetForegroundWindow(); +} + +_SOKOL_PRIVATE void _sapp_win32_lock_mouse(bool lock) { + _sapp.win32.mouse.requested_lock = lock; +} + +_SOKOL_PRIVATE void _sapp_win32_free_raw_input_data(void) { + if (_sapp.win32.raw_input_data.ptr) { + _sapp_free(_sapp.win32.raw_input_data.ptr); + _sapp.win32.raw_input_data.ptr = 0; + _sapp.win32.raw_input_data.size = 0; + } +} + +_SOKOL_PRIVATE void _sapp_win32_alloc_raw_input_data(size_t size) { + SOKOL_ASSERT(!_sapp.win32.raw_input_data.ptr); + SOKOL_ASSERT(size > 0); + _sapp.win32.raw_input_data.ptr = _sapp_malloc(size); + _sapp.win32.raw_input_data.size = size; + SOKOL_ASSERT(_sapp.win32.raw_input_data.ptr); +} + +_SOKOL_PRIVATE void* _sapp_win32_ensure_raw_input_data(size_t required_size) { + if (required_size > _sapp.win32.raw_input_data.size) { + _sapp_win32_free_raw_input_data(); + _sapp_win32_alloc_raw_input_data(required_size); + } + // we expect that malloc() returns at least 8-byte aligned memory + SOKOL_ASSERT((((uintptr_t)_sapp.win32.raw_input_data.ptr) & 7) == 0); + return _sapp.win32.raw_input_data.ptr; +} + +_SOKOL_PRIVATE void _sapp_win32_do_lock_mouse(void) { + _sapp.mouse.locked = true; + + // hide mouse cursor (NOTE: this maintains a hidden counter, but since + // only mouse-lock uses ShowCursor this doesn't matter) + ShowCursor(FALSE); + + // reset dx/dy and release any active mouse capture + _sapp.mouse.dx = 0.0f; + _sapp.mouse.dy = 0.0f; + _sapp_win32_release_mouse(0xFF); + + // store current mouse position so that it can be restored when unlocked + POINT pos; + if (GetCursorPos(&pos)) { + _sapp.win32.mouse.lock.pos_valid = true; + _sapp.win32.mouse.lock.pos_x = pos.x; + _sapp.win32.mouse.lock.pos_y = pos.y; + } else { + _sapp.win32.mouse.lock.pos_valid = false; + } + + // while mouse is locked, restrict cursor movement to the client + // rectangle so that we don't loose any mouse movement events + RECT client_rect; + GetClientRect(_sapp.win32.hwnd, &client_rect); + POINT mid_point; + mid_point.x = (client_rect.right - client_rect.left) / 2; + mid_point.y = (client_rect.bottom - client_rect.top) / 2; + ClientToScreen(_sapp.win32.hwnd, &mid_point); + RECT clip_rect; + clip_rect.left = clip_rect.right = mid_point.x; + clip_rect.top = clip_rect.bottom = mid_point.y; + ClipCursor(&clip_rect); + + // enable raw input for mouse, starts sending WM_INPUT messages to WinProc (see GLFW) + const RAWINPUTDEVICE rid = { + 0x01, // usUsagePage: HID_USAGE_PAGE_GENERIC + 0x02, // usUsage: HID_USAGE_GENERIC_MOUSE + 0, // dwFlags + _sapp.win32.hwnd // hwndTarget + }; + if (!RegisterRawInputDevices(&rid, 1, sizeof(rid))) { + _SAPP_ERROR(WIN32_REGISTER_RAW_INPUT_DEVICES_FAILED_MOUSE_LOCK); + } + // in case the raw mouse device only supports absolute position reporting, + // we need to skip the dx/dy compution for the first WM_INPUT event + _sapp.win32.mouse.raw_input.pos_valid = false; +} + +_SOKOL_PRIVATE void _sapp_win32_do_unlock_mouse(void) { + _sapp.mouse.locked = false; + + // make mouse cursor visible + ShowCursor(TRUE); + + // reset dx/dy and release any active mouse capture + _sapp.mouse.dx = 0.0f; + _sapp.mouse.dy = 0.0f; + _sapp_win32_release_mouse(0xFF); + + // disable raw input for mouse + const RAWINPUTDEVICE rid = { 0x01, 0x02, RIDEV_REMOVE, NULL }; + if (!RegisterRawInputDevices(&rid, 1, sizeof(rid))) { + _SAPP_ERROR(WIN32_REGISTER_RAW_INPUT_DEVICES_FAILED_MOUSE_UNLOCK); + } + + // unrestrict mouse movement + ClipCursor(NULL); + + // restore the 'pre-locked' mouse position + if (_sapp.win32.mouse.lock.pos_valid) { + SetCursorPos(_sapp.win32.mouse.lock.pos_x, _sapp.win32.mouse.lock.pos_y); + _sapp.win32.mouse.lock.pos_valid = false; + } +} + +_SOKOL_PRIVATE void _sapp_win32_update_mouse_lock(void) { + // mouse lock can only be active when we're the active window + if (!_sapp_win32_is_foreground_window()) { + // unlock mouse if currently locked + if (_sapp.mouse.locked) { + _sapp_win32_do_unlock_mouse(); + } + return; + } + + // nothing to do if requested lock state matches current lock state + const bool lock = _sapp.win32.mouse.requested_lock; + if (lock == _sapp.mouse.locked) { + return; + } + + // otherwise change into desired state + if (lock) { + _sapp_win32_do_lock_mouse(); + } else { + _sapp_win32_do_unlock_mouse(); + } +} + +_SOKOL_PRIVATE bool _sapp_win32_update_monitor(void) { + const HMONITOR cur_monitor = MonitorFromWindow(_sapp.win32.hwnd, MONITOR_DEFAULTTONULL); + if (cur_monitor != _sapp.win32.hmonitor) { + _sapp.win32.hmonitor = cur_monitor; + return true; + } else { + return false; + } +} + +_SOKOL_PRIVATE uint32_t _sapp_win32_mods(void) { + uint32_t mods = 0; + if (GetKeyState(VK_SHIFT) & (1<<15)) { + mods |= SAPP_MODIFIER_SHIFT; + } + if (GetKeyState(VK_CONTROL) & (1<<15)) { + mods |= SAPP_MODIFIER_CTRL; + } + if (GetKeyState(VK_MENU) & (1<<15)) { + mods |= SAPP_MODIFIER_ALT; + } + if ((GetKeyState(VK_LWIN) | GetKeyState(VK_RWIN)) & (1<<15)) { + mods |= SAPP_MODIFIER_SUPER; + } + const bool swapped = (TRUE == GetSystemMetrics(SM_SWAPBUTTON)); + if (GetAsyncKeyState(VK_LBUTTON)) { + mods |= swapped ? SAPP_MODIFIER_RMB : SAPP_MODIFIER_LMB; + } + if (GetAsyncKeyState(VK_RBUTTON)) { + mods |= swapped ? SAPP_MODIFIER_LMB : SAPP_MODIFIER_RMB; + } + if (GetAsyncKeyState(VK_MBUTTON)) { + mods |= SAPP_MODIFIER_MMB; + } + return mods; +} + +_SOKOL_PRIVATE void _sapp_win32_mouse_update(LPARAM lParam) { + if (!_sapp.mouse.locked) { + const float new_x = (float)GET_X_LPARAM(lParam) * _sapp.win32.dpi.mouse_scale; + const float new_y = (float)GET_Y_LPARAM(lParam) * _sapp.win32.dpi.mouse_scale; + if (_sapp.mouse.pos_valid) { + // don't update dx/dy in the very first event + _sapp.mouse.dx = new_x - _sapp.mouse.x; + _sapp.mouse.dy = new_y - _sapp.mouse.y; + } + _sapp.mouse.x = new_x; + _sapp.mouse.y = new_y; + _sapp.mouse.pos_valid = true; + } +} + +_SOKOL_PRIVATE void _sapp_win32_mouse_event(sapp_event_type type, sapp_mousebutton btn) { + if (_sapp_events_enabled()) { + _sapp_init_event(type); + _sapp.event.modifiers = _sapp_win32_mods(); + _sapp.event.mouse_button = btn; + _sapp_call_event(&_sapp.event); + } +} + +_SOKOL_PRIVATE void _sapp_win32_scroll_event(float x, float y) { + if (_sapp_events_enabled()) { + _sapp_init_event(SAPP_EVENTTYPE_MOUSE_SCROLL); + _sapp.event.modifiers = _sapp_win32_mods(); + _sapp.event.scroll_x = -x / 30.0f; + _sapp.event.scroll_y = y / 30.0f; + _sapp_call_event(&_sapp.event); + } +} + +_SOKOL_PRIVATE void _sapp_win32_key_event(sapp_event_type type, int vk, bool repeat) { + if (_sapp_events_enabled() && (vk < SAPP_MAX_KEYCODES)) { + _sapp_init_event(type); + _sapp.event.modifiers = _sapp_win32_mods(); + _sapp.event.key_code = _sapp.keycodes[vk]; + _sapp.event.key_repeat = repeat; + _sapp_call_event(&_sapp.event); + /* check if a CLIPBOARD_PASTED event must be sent too */ + if (_sapp.clipboard.enabled && + (type == SAPP_EVENTTYPE_KEY_DOWN) && + (_sapp.event.modifiers == SAPP_MODIFIER_CTRL) && + (_sapp.event.key_code == SAPP_KEYCODE_V)) + { + _sapp_init_event(SAPP_EVENTTYPE_CLIPBOARD_PASTED); + _sapp_call_event(&_sapp.event); + } + } +} + +_SOKOL_PRIVATE void _sapp_win32_char_event(uint32_t c, bool repeat) { + if (_sapp_events_enabled() && (c >= 32)) { + if (c >= 0xD800 && c <= 0xDBFF) { + _sapp.win32.surrogate = (WCHAR)c - 0xD800; + } else { + if (c > 0xDC00 && c <= 0xDFFF) { + c = (uint32_t)(_sapp.win32.surrogate) << 10 | (c - 0xDC00); + c += 0x10000; + _sapp.win32.surrogate = 0; + } + _sapp_init_event(SAPP_EVENTTYPE_CHAR); + _sapp.event.modifiers = _sapp_win32_mods(); + _sapp.event.char_code = c; + _sapp.event.key_repeat = repeat; + _sapp_call_event(&_sapp.event); + } + } +} + +_SOKOL_PRIVATE void _sapp_win32_dpi_changed(HWND hWnd, LPRECT proposed_win_rect) { + /* called on WM_DPICHANGED, which will only be sent to the application + if sapp_desc.high_dpi is true and the Windows version is recent enough + to support DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 + */ + SOKOL_ASSERT(_sapp.desc.high_dpi); + HINSTANCE user32 = LoadLibraryA("user32.dll"); + if (!user32) { + return; + } + typedef UINT(WINAPI * GETDPIFORWINDOW_T)(HWND hwnd); + GETDPIFORWINDOW_T fn_getdpiforwindow = (GETDPIFORWINDOW_T)(void*)GetProcAddress(user32, "GetDpiForWindow"); + if (fn_getdpiforwindow) { + UINT dpix = fn_getdpiforwindow(_sapp.win32.hwnd); + // NOTE: for high-dpi apps, mouse_scale remains one + _sapp.win32.dpi.window_scale = (float)dpix / 96.0f; + _sapp.win32.dpi.content_scale = _sapp.win32.dpi.window_scale; + _sapp.dpi_scale = _sapp.win32.dpi.window_scale; + SetWindowPos(hWnd, 0, + proposed_win_rect->left, + proposed_win_rect->top, + proposed_win_rect->right - proposed_win_rect->left, + proposed_win_rect->bottom - proposed_win_rect->top, + SWP_NOZORDER | SWP_NOACTIVATE); + } + FreeLibrary(user32); +} + +_SOKOL_PRIVATE void _sapp_win32_files_dropped(HDROP hdrop) { + if (!_sapp.drop.enabled) { + return; + } + _sapp_clear_drop_buffer(); + bool drop_failed = false; + const int count = (int) DragQueryFileW(hdrop, 0xffffffff, NULL, 0); + _sapp.drop.num_files = (count > _sapp.drop.max_files) ? _sapp.drop.max_files : count; + for (UINT i = 0; i < (UINT)_sapp.drop.num_files; i++) { + const UINT num_chars = DragQueryFileW(hdrop, i, NULL, 0) + 1; + WCHAR* buffer = (WCHAR*) _sapp_malloc_clear(num_chars * sizeof(WCHAR)); + DragQueryFileW(hdrop, i, buffer, num_chars); + if (!_sapp_win32_wide_to_utf8(buffer, _sapp_dropped_file_path_ptr((int)i), _sapp.drop.max_path_length)) { + _SAPP_ERROR(DROPPED_FILE_PATH_TOO_LONG); + drop_failed = true; + } + _sapp_free(buffer); + } + DragFinish(hdrop); + if (!drop_failed) { + if (_sapp_events_enabled()) { + _sapp_init_event(SAPP_EVENTTYPE_FILES_DROPPED); + _sapp.event.modifiers = _sapp_win32_mods(); + _sapp_call_event(&_sapp.event); + } + } else { + _sapp_clear_drop_buffer(); + _sapp.drop.num_files = 0; + } +} + +_SOKOL_PRIVATE void _sapp_win32_timing_measure(void) { + #if defined(SOKOL_D3D11) + // on D3D11, use the more precise DXGI timestamp + if (_sapp.d3d11.use_dxgi_frame_stats) { + DXGI_FRAME_STATISTICS dxgi_stats; + _sapp_clear(&dxgi_stats, sizeof(dxgi_stats)); + HRESULT hr = _sapp_dxgi_GetFrameStatistics(_sapp.d3d11.swap_chain, &dxgi_stats); + if (SUCCEEDED(hr)) { + if (dxgi_stats.SyncRefreshCount != _sapp.d3d11.sync_refresh_count) { + _sapp.d3d11.sync_refresh_count = dxgi_stats.SyncRefreshCount; + LARGE_INTEGER qpc = dxgi_stats.SyncQPCTime; + const uint64_t now = (uint64_t)_sapp_int64_muldiv(qpc.QuadPart - _sapp.timing.timestamp.win.start.QuadPart, 1000000000, _sapp.timing.timestamp.win.freq.QuadPart); + _sapp_timing_external(&_sapp.timing, (double)now / 1000000000.0); + } + return; + } + } + // fallback if swap model isn't "flip-discard" or GetFrameStatistics failed for another reason + _sapp_timing_measure(&_sapp.timing); + #endif + #if defined(SOKOL_GLCORE) + _sapp_timing_measure(&_sapp.timing); + #endif + #if defined(SOKOL_NOAPI) + _sapp_timing_measure(&_sapp.timing); + #endif +} + +_SOKOL_PRIVATE void _sapp_win32_frame(bool from_winproc) { + #if defined(SOKOL_WGPU) + _sapp_wgpu_frame(); + #else + _sapp_frame(); + #endif + #if defined(SOKOL_D3D11) + bool do_not_wait = from_winproc; + _sapp_d3d11_present(do_not_wait); + #endif + #if defined(SOKOL_GLCORE) + _sapp_wgl_swap_buffers(); + #endif + if (!from_winproc) { + if (IsIconic(_sapp.win32.hwnd)) { + Sleep((DWORD)(16 * _sapp.swap_interval)); + } + } +} + +_SOKOL_PRIVATE LRESULT CALLBACK _sapp_win32_wndproc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + if (!_sapp.win32.in_create_window) { + switch (uMsg) { + case WM_CLOSE: + /* only give user a chance to intervene when sapp_quit() wasn't already called */ + if (!_sapp.quit_ordered) { + /* if window should be closed and event handling is enabled, give user code + a change to intervene via sapp_cancel_quit() + */ + _sapp.quit_requested = true; + _sapp_win32_app_event(SAPP_EVENTTYPE_QUIT_REQUESTED); + /* if user code hasn't intervened, quit the app */ + if (_sapp.quit_requested) { + _sapp.quit_ordered = true; + } + } + if (_sapp.quit_ordered) { + PostQuitMessage(0); + } + return 0; + case WM_SYSCOMMAND: + switch (wParam & 0xFFF0) { + case SC_SCREENSAVE: + case SC_MONITORPOWER: + if (_sapp.fullscreen) { + /* disable screen saver and blanking in fullscreen mode */ + return 0; + } + break; + case SC_KEYMENU: + /* user trying to access menu via ALT */ + return 0; + } + break; + case WM_ERASEBKGND: + return 1; + case WM_SIZE: + { + const bool iconified = wParam == SIZE_MINIMIZED; + if (iconified != _sapp.win32.iconified) { + _sapp.win32.iconified = iconified; + if (iconified) { + _sapp_win32_app_event(SAPP_EVENTTYPE_ICONIFIED); + } else { + _sapp_win32_app_event(SAPP_EVENTTYPE_RESTORED); + } + } + } + break; + case WM_SETFOCUS: + _sapp_win32_app_event(SAPP_EVENTTYPE_FOCUSED); + break; + case WM_KILLFOCUS: + _sapp_win32_app_event(SAPP_EVENTTYPE_UNFOCUSED); + break; + case WM_SETCURSOR: + if (LOWORD(lParam) == HTCLIENT) { + _sapp_win32_update_cursor(_sapp.mouse.current_cursor, _sapp.mouse.shown, true); + return TRUE; + } + break; + case WM_DPICHANGED: + { + /* Update window's DPI and size if its moved to another monitor with a different DPI + Only sent if DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 is used. + */ + _sapp_win32_dpi_changed(hWnd, (LPRECT)lParam); + break; + } + case WM_LBUTTONDOWN: + _sapp_win32_mouse_update(lParam); + _sapp_win32_mouse_event(SAPP_EVENTTYPE_MOUSE_DOWN, SAPP_MOUSEBUTTON_LEFT); + _sapp_win32_capture_mouse(1<data.mouse.usFlags & MOUSE_MOVE_ABSOLUTE) { + /* mouse only reports absolute position + NOTE: This code is untested and will most likely behave wrong in Remote Desktop sessions. + (such remote desktop sessions are setting the MOUSE_MOVE_ABSOLUTE flag). + See: https://github.com/floooh/sokol/issues/806 and + https://github.com/microsoft/DirectXTK/commit/ef56b63f3739381e451f7a5a5bd2c9779d2a7555) + */ + LONG new_x = raw_mouse_data->data.mouse.lLastX; + LONG new_y = raw_mouse_data->data.mouse.lLastY; + if (_sapp.win32.mouse.raw_input.pos_valid) { + _sapp.mouse.dx = (float) (new_x - _sapp.win32.mouse.raw_input.pos_x); + _sapp.mouse.dy = (float) (new_y - _sapp.win32.mouse.raw_input.pos_y); + } + _sapp.win32.mouse.raw_input.pos_x = new_x; + _sapp.win32.mouse.raw_input.pos_y = new_y; + _sapp.win32.mouse.raw_input.pos_valid = true; + } else { + /* mouse reports movement delta (this seems to be the common case) */ + _sapp.mouse.dx = (float) raw_mouse_data->data.mouse.lLastX; + _sapp.mouse.dy = (float) raw_mouse_data->data.mouse.lLastY; + } + _sapp_win32_mouse_event(SAPP_EVENTTYPE_MOUSE_MOVE, SAPP_MOUSEBUTTON_INVALID); + } + break; + + case WM_MOUSELEAVE: + if (!_sapp.mouse.locked) { + _sapp.mouse.dx = 0.0f; + _sapp.mouse.dy = 0.0f; + _sapp.win32.mouse.tracked = false; + _sapp_win32_mouse_event(SAPP_EVENTTYPE_MOUSE_LEAVE, SAPP_MOUSEBUTTON_INVALID); + } + break; + case WM_MOUSEWHEEL: + _sapp_win32_scroll_event(0.0f, (float)((SHORT)HIWORD(wParam))); + break; + case WM_MOUSEHWHEEL: + _sapp_win32_scroll_event((float)((SHORT)HIWORD(wParam)), 0.0f); + break; + case WM_CHAR: + _sapp_win32_char_event((uint32_t)wParam, !!(lParam&0x40000000)); + break; + case WM_KEYDOWN: + case WM_SYSKEYDOWN: + _sapp_win32_key_event(SAPP_EVENTTYPE_KEY_DOWN, (int)(HIWORD(lParam)&0x1FF), !!(lParam&0x40000000)); + break; + case WM_KEYUP: + case WM_SYSKEYUP: + _sapp_win32_key_event(SAPP_EVENTTYPE_KEY_UP, (int)(HIWORD(lParam)&0x1FF), false); + break; + case WM_ENTERSIZEMOVE: + SetTimer(_sapp.win32.hwnd, 1, USER_TIMER_MINIMUM, NULL); + break; + case WM_EXITSIZEMOVE: + KillTimer(_sapp.win32.hwnd, 1); + break; + case WM_TIMER: + _sapp_win32_timing_measure(); + _sapp_win32_frame(true); + /* NOTE: resizing the swap-chain during resize leads to a substantial + memory spike (hundreds of megabytes for a few seconds). + + if (_sapp_win32_update_dimensions()) { + #if defined(SOKOL_D3D11) + _sapp_d3d11_resize_default_render_target(); + #endif + _sapp_win32_app_event(SAPP_EVENTTYPE_RESIZED); + } + */ + break; + case WM_NCLBUTTONDOWN: + /* workaround for half-second pause when starting to move window + see: https://gamedev.net/forums/topic/672094-keeping-things-moving-during-win32-moveresize-events/5254386/ + */ + if (SendMessage(_sapp.win32.hwnd, WM_NCHITTEST, wParam, lParam) == HTCAPTION) { + POINT point = { 0, 0 }; + if (GetCursorPos(&point)) { + ScreenToClient(_sapp.win32.hwnd, &point); + PostMessage(_sapp.win32.hwnd, WM_MOUSEMOVE, 0, ((uint32_t)point.x)|(((uint32_t)point.y) << 16)); + } + } + break; + case WM_DROPFILES: + _sapp_win32_files_dropped((HDROP)wParam); + break; + case WM_DISPLAYCHANGE: + // refresh rate might have changed + _sapp_timing_reset(&_sapp.timing); + break; + + default: + break; + } + } + return DefWindowProcW(hWnd, uMsg, wParam, lParam); +} + +_SOKOL_PRIVATE void _sapp_win32_create_window(void) { + WNDCLASSW wndclassw; + _sapp_clear(&wndclassw, sizeof(wndclassw)); + wndclassw.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; + wndclassw.lpfnWndProc = (WNDPROC) _sapp_win32_wndproc; + wndclassw.hInstance = GetModuleHandleW(NULL); + wndclassw.hCursor = LoadCursor(NULL, IDC_ARROW); + wndclassw.hIcon = LoadIcon(NULL, IDI_WINLOGO); + wndclassw.lpszClassName = L"SOKOLAPP"; + RegisterClassW(&wndclassw); + + /* NOTE: regardless whether fullscreen is requested or not, a regular + windowed-mode window will always be created first (however in hidden + mode, so that no windowed-mode window pops up before the fullscreen window) + */ + const DWORD win_ex_style = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE; + RECT rect = { 0, 0, 0, 0 }; + DWORD win_style = WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_SIZEBOX; + rect.right = (int) ((float)_sapp.window_width * _sapp.win32.dpi.window_scale); + rect.bottom = (int) ((float)_sapp.window_height * _sapp.win32.dpi.window_scale); + const bool use_default_width = 0 == _sapp.window_width; + const bool use_default_height = 0 == _sapp.window_height; + AdjustWindowRectEx(&rect, win_style, FALSE, win_ex_style); + const int win_width = rect.right - rect.left; + const int win_height = rect.bottom - rect.top; + _sapp.win32.in_create_window = true; + _sapp.win32.surrogate = 0; + _sapp.win32.hwnd = CreateWindowExW( + win_ex_style, // dwExStyle + L"SOKOLAPP", // lpClassName + _sapp.window_title_wide, // lpWindowName + win_style, // dwStyle + CW_USEDEFAULT, // X + SW_HIDE, // Y (NOTE: CW_USEDEFAULT is not used for position here, but internally calls ShowWindow! + use_default_width ? CW_USEDEFAULT : win_width, // nWidth + use_default_height ? CW_USEDEFAULT : win_height, // nHeight (NOTE: if width is CW_USEDEFAULT, height is actually ignored) + NULL, // hWndParent + NULL, // hMenu + GetModuleHandle(NULL), // hInstance + NULL); // lParam + _sapp.win32.in_create_window = false; + _sapp.win32.dc = GetDC(_sapp.win32.hwnd); + _sapp.win32.hmonitor = MonitorFromWindow(_sapp.win32.hwnd, MONITOR_DEFAULTTONULL); + SOKOL_ASSERT(_sapp.win32.dc); + + /* this will get the actual windowed-mode window size, if fullscreen + is requested, the set_fullscreen function will then capture the + current window rectangle, which then might be used later to + restore the window position when switching back to windowed + */ + _sapp_win32_update_dimensions(); + if (_sapp.fullscreen) { + _sapp_win32_set_fullscreen(_sapp.fullscreen, SWP_HIDEWINDOW); + _sapp_win32_update_dimensions(); + } + ShowWindow(_sapp.win32.hwnd, SW_SHOW); + DragAcceptFiles(_sapp.win32.hwnd, 1); +} + +_SOKOL_PRIVATE void _sapp_win32_destroy_window(void) { + DestroyWindow(_sapp.win32.hwnd); _sapp.win32.hwnd = 0; + UnregisterClassW(L"SOKOLAPP", GetModuleHandleW(NULL)); +} + +_SOKOL_PRIVATE void _sapp_win32_destroy_icons(void) { + if (_sapp.win32.big_icon) { + DestroyIcon(_sapp.win32.big_icon); + _sapp.win32.big_icon = 0; + } + if (_sapp.win32.small_icon) { + DestroyIcon(_sapp.win32.small_icon); + _sapp.win32.small_icon = 0; + } +} + +_SOKOL_PRIVATE void _sapp_win32_init_console(void) { + if (_sapp.desc.win32_console_create || _sapp.desc.win32_console_attach) { + BOOL con_valid = FALSE; + if (_sapp.desc.win32_console_create) { + con_valid = AllocConsole(); + } else if (_sapp.desc.win32_console_attach) { + con_valid = AttachConsole(ATTACH_PARENT_PROCESS); + } + if (con_valid) { + FILE* res_fp = 0; + errno_t err; + err = freopen_s(&res_fp, "CON", "w", stdout); + (void)err; + err = freopen_s(&res_fp, "CON", "w", stderr); + (void)err; + } + } + if (_sapp.desc.win32_console_utf8) { + _sapp.win32.orig_codepage = GetConsoleOutputCP(); + SetConsoleOutputCP(CP_UTF8); + } +} + +_SOKOL_PRIVATE void _sapp_win32_restore_console(void) { + if (_sapp.desc.win32_console_utf8) { + SetConsoleOutputCP(_sapp.win32.orig_codepage); + } +} + +_SOKOL_PRIVATE void _sapp_win32_init_dpi(void) { + + DECLARE_HANDLE(DPI_AWARENESS_CONTEXT_T); + typedef BOOL(WINAPI * SETPROCESSDPIAWARE_T)(void); + typedef bool (WINAPI * SETPROCESSDPIAWARENESSCONTEXT_T)(DPI_AWARENESS_CONTEXT_T); // since Windows 10, version 1703 + typedef HRESULT(WINAPI * SETPROCESSDPIAWARENESS_T)(PROCESS_DPI_AWARENESS); + typedef HRESULT(WINAPI * GETDPIFORMONITOR_T)(HMONITOR, MONITOR_DPI_TYPE, UINT*, UINT*); + + SETPROCESSDPIAWARE_T fn_setprocessdpiaware = 0; + SETPROCESSDPIAWARENESS_T fn_setprocessdpiawareness = 0; + GETDPIFORMONITOR_T fn_getdpiformonitor = 0; + SETPROCESSDPIAWARENESSCONTEXT_T fn_setprocessdpiawarenesscontext =0; + + HINSTANCE user32 = LoadLibraryA("user32.dll"); + if (user32) { + fn_setprocessdpiaware = (SETPROCESSDPIAWARE_T)(void*) GetProcAddress(user32, "SetProcessDPIAware"); + fn_setprocessdpiawarenesscontext = (SETPROCESSDPIAWARENESSCONTEXT_T)(void*) GetProcAddress(user32, "SetProcessDpiAwarenessContext"); + } + HINSTANCE shcore = LoadLibraryA("shcore.dll"); + if (shcore) { + fn_setprocessdpiawareness = (SETPROCESSDPIAWARENESS_T)(void*) GetProcAddress(shcore, "SetProcessDpiAwareness"); + fn_getdpiformonitor = (GETDPIFORMONITOR_T)(void*) GetProcAddress(shcore, "GetDpiForMonitor"); + } + /* + NOTE on SetProcessDpiAware() vs SetProcessDpiAwareness() vs SetProcessDpiAwarenessContext(): + + These are different attempts to get DPI handling on Windows right, from oldest + to newest. SetProcessDpiAwarenessContext() is required for the new + DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 method. + */ + if (fn_setprocessdpiawareness) { + if (_sapp.desc.high_dpi) { + /* app requests HighDPI rendering, first try the Win10 Creator Update per-monitor-dpi awareness, + if that fails, fall back to system-dpi-awareness + */ + _sapp.win32.dpi.aware = true; + DPI_AWARENESS_CONTEXT_T per_monitor_aware_v2 = (DPI_AWARENESS_CONTEXT_T)-4; + if (!(fn_setprocessdpiawarenesscontext && fn_setprocessdpiawarenesscontext(per_monitor_aware_v2))) { + // fallback to system-dpi-aware + fn_setprocessdpiawareness(PROCESS_SYSTEM_DPI_AWARE); + } + } else { + /* if the app didn't request HighDPI rendering, let Windows do the upscaling */ + _sapp.win32.dpi.aware = false; + fn_setprocessdpiawareness(PROCESS_DPI_UNAWARE); + } + } else if (fn_setprocessdpiaware) { + // fallback for Windows 7 + _sapp.win32.dpi.aware = true; + fn_setprocessdpiaware(); + } + /* get dpi scale factor for main monitor */ + if (fn_getdpiformonitor && _sapp.win32.dpi.aware) { + POINT pt = { 1, 1 }; + HMONITOR hm = MonitorFromPoint(pt, MONITOR_DEFAULTTONEAREST); + UINT dpix, dpiy; + HRESULT hr = fn_getdpiformonitor(hm, MDT_EFFECTIVE_DPI, &dpix, &dpiy); + _SOKOL_UNUSED(hr); + SOKOL_ASSERT(SUCCEEDED(hr)); + /* clamp window scale to an integer factor */ + _sapp.win32.dpi.window_scale = (float)dpix / 96.0f; + } else { + _sapp.win32.dpi.window_scale = 1.0f; + } + if (_sapp.desc.high_dpi) { + _sapp.win32.dpi.content_scale = _sapp.win32.dpi.window_scale; + _sapp.win32.dpi.mouse_scale = 1.0f; + } else { + _sapp.win32.dpi.content_scale = 1.0f; + _sapp.win32.dpi.mouse_scale = 1.0f / _sapp.win32.dpi.window_scale; + } + _sapp.dpi_scale = _sapp.win32.dpi.content_scale; + if (user32) { + FreeLibrary(user32); + } + if (shcore) { + FreeLibrary(shcore); + } +} + +_SOKOL_PRIVATE bool _sapp_win32_set_clipboard_string(const char* str) { + SOKOL_ASSERT(str); + SOKOL_ASSERT(_sapp.win32.hwnd); + SOKOL_ASSERT(_sapp.clipboard.enabled && (_sapp.clipboard.buf_size > 0)); + + if (!OpenClipboard(_sapp.win32.hwnd)) { + return false; + } + + HANDLE object = 0; + wchar_t* wchar_buf = 0; + + const SIZE_T wchar_buf_size = (SIZE_T)_sapp.clipboard.buf_size * sizeof(wchar_t); + object = GlobalAlloc(GMEM_MOVEABLE, wchar_buf_size); + if (NULL == object) { + goto error; + } + wchar_buf = (wchar_t*) GlobalLock(object); + if (NULL == wchar_buf) { + goto error; + } + if (!_sapp_win32_utf8_to_wide(str, wchar_buf, (int)wchar_buf_size)) { + goto error; + } + GlobalUnlock(object); + wchar_buf = 0; + EmptyClipboard(); + // NOTE: when successful, SetClipboardData() takes ownership of memory object! + if (NULL == SetClipboardData(CF_UNICODETEXT, object)) { + goto error; + } + CloseClipboard(); + return true; + +error: + if (wchar_buf) { + GlobalUnlock(object); + } + if (object) { + GlobalFree(object); + } + CloseClipboard(); + return false; +} + +_SOKOL_PRIVATE const char* _sapp_win32_get_clipboard_string(void) { + SOKOL_ASSERT(_sapp.clipboard.enabled && _sapp.clipboard.buffer); + SOKOL_ASSERT(_sapp.win32.hwnd); + if (!OpenClipboard(_sapp.win32.hwnd)) { + /* silently ignore any errors and just return the current + content of the local clipboard buffer + */ + return _sapp.clipboard.buffer; + } + HANDLE object = GetClipboardData(CF_UNICODETEXT); + if (!object) { + CloseClipboard(); + return _sapp.clipboard.buffer; + } + const wchar_t* wchar_buf = (const wchar_t*) GlobalLock(object); + if (!wchar_buf) { + CloseClipboard(); + return _sapp.clipboard.buffer; + } + if (!_sapp_win32_wide_to_utf8(wchar_buf, _sapp.clipboard.buffer, _sapp.clipboard.buf_size)) { + _SAPP_ERROR(CLIPBOARD_STRING_TOO_BIG); + } + GlobalUnlock(object); + CloseClipboard(); + return _sapp.clipboard.buffer; +} + +_SOKOL_PRIVATE void _sapp_win32_update_window_title(void) { + _sapp_win32_utf8_to_wide(_sapp.window_title, _sapp.window_title_wide, sizeof(_sapp.window_title_wide)); + SetWindowTextW(_sapp.win32.hwnd, _sapp.window_title_wide); +} + +_SOKOL_PRIVATE HICON _sapp_win32_create_icon_from_image(const sapp_image_desc* desc, bool is_cursor) { + BITMAPV5HEADER bi; + _sapp_clear(&bi, sizeof(bi)); + bi.bV5Size = sizeof(bi); + bi.bV5Width = desc->width; + bi.bV5Height = -desc->height; // NOTE the '-' here to indicate that origin is top-left + bi.bV5Planes = 1; + bi.bV5BitCount = 32; + bi.bV5Compression = BI_BITFIELDS; + bi.bV5RedMask = 0x00FF0000; + bi.bV5GreenMask = 0x0000FF00; + bi.bV5BlueMask = 0x000000FF; + bi.bV5AlphaMask = 0xFF000000; + + uint8_t* target = 0; + const uint8_t* source = (const uint8_t*)desc->pixels.ptr; + + HDC dc = GetDC(NULL); + HBITMAP color = CreateDIBSection(dc, (BITMAPINFO*)&bi, DIB_RGB_COLORS, (void**)&target, NULL, (DWORD)0); + ReleaseDC(NULL, dc); + if (0 == color) { + return NULL; + } + SOKOL_ASSERT(target); + + HBITMAP mask = CreateBitmap(desc->width, desc->height, 1, 1, NULL); + if (0 == mask) { + DeleteObject(color); + return NULL; + } + + for (int i = 0; i < (desc->width*desc->height); i++) { + target[0] = source[2]; + target[1] = source[1]; + target[2] = source[0]; + target[3] = source[3]; + target += 4; + source += 4; + } + + ICONINFO icon_info; + _sapp_clear(&icon_info, sizeof(icon_info)); + icon_info.fIcon = !is_cursor; + icon_info.xHotspot = (DWORD) (is_cursor ? desc->cursor_hotspot_x : 0); + icon_info.yHotspot = (DWORD) (is_cursor ? desc->cursor_hotspot_y : 0); + icon_info.hbmMask = mask; + icon_info.hbmColor = color; + HICON icon_handle = CreateIconIndirect(&icon_info); + DeleteObject(color); + DeleteObject(mask); + + return icon_handle; +} + +_SOKOL_PRIVATE void _sapp_win32_set_icon(const sapp_icon_desc* icon_desc, int num_images) { + SOKOL_ASSERT((num_images > 0) && (num_images <= SAPP_MAX_ICONIMAGES)); + + int big_img_index = _sapp_image_bestmatch(icon_desc->images, num_images, GetSystemMetrics(SM_CXICON), GetSystemMetrics(SM_CYICON)); + int sml_img_index = _sapp_image_bestmatch(icon_desc->images, num_images, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON)); + HICON big_icon = _sapp_win32_create_icon_from_image(&icon_desc->images[big_img_index], false); + HICON sml_icon = _sapp_win32_create_icon_from_image(&icon_desc->images[sml_img_index], false); + + // if icon creation or lookup has failed for some reason, leave the currently set icon untouched + if (0 != big_icon) { + SendMessage(_sapp.win32.hwnd, WM_SETICON, ICON_BIG, (LPARAM) big_icon); + if (0 != _sapp.win32.big_icon) { + DestroyIcon(_sapp.win32.big_icon); + } + _sapp.win32.big_icon = big_icon; + } + if (0 != sml_icon) { + SendMessage(_sapp.win32.hwnd, WM_SETICON, ICON_SMALL, (LPARAM) sml_icon); + if (0 != _sapp.win32.small_icon) { + DestroyIcon(_sapp.win32.small_icon); + } + _sapp.win32.small_icon = sml_icon; + } +} + +/* don't laugh, but this seems to be the easiest and most robust + way to check if we're running on Win10 + + From: https://github.com/videolan/vlc/blob/232fb13b0d6110c4d1b683cde24cf9a7f2c5c2ea/modules/video_output/win32/d3d11_swapchain.c#L263 +*/ +_SOKOL_PRIVATE bool _sapp_win32_is_win10_or_greater(void) { + HMODULE h = GetModuleHandleW(L"kernel32.dll"); + if (NULL != h) { + return (NULL != GetProcAddress(h, "GetSystemCpuSetInformation")); + } else { + return false; + } +} + +_SOKOL_PRIVATE void _sapp_win32_run(const sapp_desc* desc) { + _sapp_init_state(desc); + _sapp_win32_init_console(); + _sapp.win32.is_win10_or_greater = _sapp_win32_is_win10_or_greater(); + _sapp_win32_init_keytable(); + _sapp_win32_utf8_to_wide(_sapp.window_title, _sapp.window_title_wide, sizeof(_sapp.window_title_wide)); + _sapp_win32_init_dpi(); + _sapp_win32_init_cursors(); + _sapp_win32_create_window(); + sapp_set_icon(&desc->icon); + #if defined(SOKOL_D3D11) + _sapp_d3d11_create_device_and_swapchain(); + _sapp_d3d11_create_default_render_target(); + #elif defined(SOKOL_GLCORE) + _sapp_wgl_init(); + _sapp_wgl_load_extensions(); + _sapp_wgl_create_context(); + #elif defined(SOKOL_WGPU) + _sapp_wgpu_init(); + #endif + _sapp.valid = true; + + bool done = false; + while (!(done || _sapp.quit_ordered)) { + _sapp_win32_timing_measure(); + MSG msg; + while (PeekMessageW(&msg, NULL, 0, 0, PM_REMOVE)) { + if (WM_QUIT == msg.message) { + done = true; + continue; + } else { + TranslateMessage(&msg); + DispatchMessageW(&msg); + } + } + _sapp_win32_frame(false); + // check for window resized, this cannot happen in WM_SIZE as it explodes memory usage + if (_sapp_win32_update_dimensions()) { + #if defined(SOKOL_D3D11) + _sapp_d3d11_resize_default_render_target(); + #elif defined(SOKOL_WGPU) + _sapp_wgpu_swapchain_size_changed(); + #endif + _sapp_win32_app_event(SAPP_EVENTTYPE_RESIZED); + } + /* check if the window monitor has changed, need to reset timing because + the new monitor might have a different refresh rate + */ + if (_sapp_win32_update_monitor()) { + _sapp_timing_reset(&_sapp.timing); + } + if (_sapp.quit_requested) { + PostMessage(_sapp.win32.hwnd, WM_CLOSE, 0, 0); + } + // update mouse-lock state + _sapp_win32_update_mouse_lock(); + } + _sapp_call_cleanup(); + + #if defined(SOKOL_D3D11) + _sapp_d3d11_destroy_default_render_target(); + _sapp_d3d11_destroy_device_and_swapchain(); + #elif defined(SOKOL_GLCORE) + _sapp_wgl_destroy_context(); + _sapp_wgl_shutdown(); + #elif defined(SOKOL_WGPU) + _sapp_wgpu_discard(); + #endif + _sapp_win32_destroy_window(); + _sapp_win32_destroy_icons(); + _sapp_win32_restore_console(); + _sapp_win32_free_raw_input_data(); + _sapp_discard_state(); +} + +_SOKOL_PRIVATE char** _sapp_win32_command_line_to_utf8_argv(LPWSTR w_command_line, int* o_argc) { + int argc = 0; + char** argv = 0; + char* args; + + LPWSTR* w_argv = CommandLineToArgvW(w_command_line, &argc); + if (w_argv == NULL) { + // FIXME: chicken egg problem, can't report errors before sokol_main() is called! + } else { + size_t size = wcslen(w_command_line) * 4; + argv = (char**) _sapp_malloc_clear(((size_t)argc + 1) * sizeof(char*) + size); + SOKOL_ASSERT(argv); + args = (char*) &argv[argc + 1]; + int n; + for (int i = 0; i < argc; ++i) { + n = WideCharToMultiByte(CP_UTF8, 0, w_argv[i], -1, args, (int)size, NULL, NULL); + if (n == 0) { + // FIXME: chicken egg problem, can't report errors before sokol_main() is called! + break; + } + argv[i] = args; + size -= (size_t)n; + args += n; + } + LocalFree(w_argv); + } + *o_argc = argc; + return argv; +} + +_SOKOL_PRIVATE bool _sapp_win32_make_custom_mouse_cursor(sapp_mouse_cursor cursor, const sapp_image_desc* desc) { + SOKOL_ASSERT((cursor >= 0) && (cursor < _SAPP_MOUSECURSOR_NUM)); + SOKOL_ASSERT(0 == _sapp.win32.custom_cursors[cursor]); + const HCURSOR win32_cursor = _sapp_win32_create_icon_from_image(desc, true); + _sapp.win32.custom_cursors[cursor] = win32_cursor; + return win32_cursor != 0; +} + +SOKOL_API_IMPL void _sapp_win32_destroy_custom_mouse_cursor(sapp_mouse_cursor cursor) { + SOKOL_ASSERT((cursor >= 0) && (cursor < _SAPP_MOUSECURSOR_NUM)); + HCURSOR win32_cursor = _sapp.win32.custom_cursors[cursor]; + SOKOL_ASSERT(win32_cursor); + _sapp.win32.custom_cursors[cursor] = 0; + // NOTE: DestroyIcon() may return zero (failure) if the cursor is currently in + // use. Normally that shouldn't happen since when attempting to unbind the + // current cursor it will be hidden first, but since there might be other edge + // cases we just log a warning but don't fail hard + BOOL res = DestroyIcon(win32_cursor); + if (!res) { + _SAPP_WARN(WIN32_DESTROYICON_FOR_CURSOR_FAILED); + } +} + +#if !defined(SOKOL_NO_ENTRY) +#if defined(SOKOL_WIN32_FORCE_MAIN) +int main(int argc, char* argv[]) { + sapp_desc desc = sokol_main(argc, argv); + _sapp_win32_run(&desc); + return 0; +} +#endif /* SOKOL_WIN32_FORCE_MAIN */ +#if defined(SOKOL_WIN32_FORCE_WINMAIN) || !defined(SOKOL_WIN32_FORCE_MAIN) +int WINAPI WinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPSTR lpCmdLine, _In_ int nCmdShow) { + _SOKOL_UNUSED(hInstance); + _SOKOL_UNUSED(hPrevInstance); + _SOKOL_UNUSED(lpCmdLine); + _SOKOL_UNUSED(nCmdShow); + int argc_utf8 = 0; + char** argv_utf8 = _sapp_win32_command_line_to_utf8_argv(GetCommandLineW(), &argc_utf8); + sapp_desc desc = sokol_main(argc_utf8, argv_utf8); + _sapp_win32_run(&desc); + _sapp_free(argv_utf8); + return 0; +} +#endif /* SOKOL_WIN32_FORCE_WINMAIN */ +#endif /* SOKOL_NO_ENTRY */ + +#ifdef _MSC_VER + #pragma warning(pop) +#endif + +#endif /* _SAPP_WIN32 */ + +// █████ ███ ██ ██████ ██████ ██████ ██ ██████ +// ██ ██ ████ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ███████ ██ ██ ██ ██ ██ ██████ ██ ██ ██ ██ ██ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██ ██ ██ ████ ██████ ██ ██ ██████ ██ ██████ +// +// >>android +#if defined(_SAPP_ANDROID) + +/* android loop thread */ +_SOKOL_PRIVATE bool _sapp_android_init_egl(void) { + SOKOL_ASSERT(_sapp.android.display == EGL_NO_DISPLAY); + SOKOL_ASSERT(_sapp.android.context == EGL_NO_CONTEXT); + + EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY); + if (display == EGL_NO_DISPLAY) { + return false; + } + if (eglInitialize(display, NULL, NULL) == EGL_FALSE) { + return false; + } + EGLint alpha_size = _sapp.desc.alpha ? 8 : 0; + const EGLint cfg_attributes[] = { + EGL_SURFACE_TYPE, EGL_WINDOW_BIT, + EGL_RENDERABLE_TYPE, EGL_OPENGL_ES3_BIT, + EGL_RED_SIZE, 8, + EGL_GREEN_SIZE, 8, + EGL_BLUE_SIZE, 8, + EGL_ALPHA_SIZE, alpha_size, + EGL_DEPTH_SIZE, 16, + EGL_STENCIL_SIZE, 0, + EGL_NONE, + }; + EGLConfig available_cfgs[32]; + EGLint cfg_count; + eglChooseConfig(display, cfg_attributes, available_cfgs, 32, &cfg_count); + SOKOL_ASSERT(cfg_count > 0); + SOKOL_ASSERT(cfg_count <= 32); + + /* find config with 8-bit rgb buffer if available, ndk sample does not trust egl spec */ + EGLConfig config; + bool exact_cfg_found = false; + for (int i = 0; i < cfg_count; ++i) { + EGLConfig c = available_cfgs[i]; + EGLint r, g, b, a, d; + if (eglGetConfigAttrib(display, c, EGL_RED_SIZE, &r) == EGL_TRUE && + eglGetConfigAttrib(display, c, EGL_GREEN_SIZE, &g) == EGL_TRUE && + eglGetConfigAttrib(display, c, EGL_BLUE_SIZE, &b) == EGL_TRUE && + eglGetConfigAttrib(display, c, EGL_ALPHA_SIZE, &a) == EGL_TRUE && + eglGetConfigAttrib(display, c, EGL_DEPTH_SIZE, &d) == EGL_TRUE && + r == 8 && g == 8 && b == 8 && (alpha_size == 0 || a == alpha_size) && d == 16) { + exact_cfg_found = true; + config = c; + break; + } + } + if (!exact_cfg_found) { + config = available_cfgs[0]; + } + + EGLint ctx_attributes[] = { + EGL_CONTEXT_MAJOR_VERSION, _sapp.desc.gl_major_version, + EGL_CONTEXT_MINOR_VERSION, _sapp.desc.gl_minor_version, + EGL_NONE, + }; + EGLContext context = eglCreateContext(display, config, EGL_NO_CONTEXT, ctx_attributes); + if (context == EGL_NO_CONTEXT) { + return false; + } + + _sapp.android.config = config; + _sapp.android.display = display; + _sapp.android.context = context; + return true; +} + +_SOKOL_PRIVATE void _sapp_android_cleanup_egl(void) { + if (_sapp.android.display != EGL_NO_DISPLAY) { + eglMakeCurrent(_sapp.android.display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + if (_sapp.android.surface != EGL_NO_SURFACE) { + eglDestroySurface(_sapp.android.display, _sapp.android.surface); + _sapp.android.surface = EGL_NO_SURFACE; + } + if (_sapp.android.context != EGL_NO_CONTEXT) { + eglDestroyContext(_sapp.android.display, _sapp.android.context); + _sapp.android.context = EGL_NO_CONTEXT; + } + eglTerminate(_sapp.android.display); + _sapp.android.display = EGL_NO_DISPLAY; + } +} + +_SOKOL_PRIVATE bool _sapp_android_init_egl_surface(ANativeWindow* window) { + SOKOL_ASSERT(_sapp.android.display != EGL_NO_DISPLAY); + SOKOL_ASSERT(_sapp.android.context != EGL_NO_CONTEXT); + SOKOL_ASSERT(_sapp.android.surface == EGL_NO_SURFACE); + SOKOL_ASSERT(window); + + /* TODO: set window flags */ + /* ANativeActivity_setWindowFlags(activity, AWINDOW_FLAG_KEEP_SCREEN_ON, 0); */ + + /* create egl surface and make it current */ + EGLSurface surface = eglCreateWindowSurface(_sapp.android.display, _sapp.android.config, window, NULL); + if (surface == EGL_NO_SURFACE) { + return false; + } + if (eglMakeCurrent(_sapp.android.display, surface, surface, _sapp.android.context) == EGL_FALSE) { + return false; + } + _sapp.android.surface = surface; + glGetIntegerv(GL_FRAMEBUFFER_BINDING, (GLint*)&_sapp.gl.framebuffer); + return true; +} + +_SOKOL_PRIVATE void _sapp_android_cleanup_egl_surface(void) { + if (_sapp.android.display == EGL_NO_DISPLAY) { + return; + } + eglMakeCurrent(_sapp.android.display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + if (_sapp.android.surface != EGL_NO_SURFACE) { + eglDestroySurface(_sapp.android.display, _sapp.android.surface); + _sapp.android.surface = EGL_NO_SURFACE; + } +} + +_SOKOL_PRIVATE void _sapp_android_app_event(sapp_event_type type) { + if (_sapp_events_enabled()) { + _sapp_init_event(type); + _sapp_call_event(&_sapp.event); + } +} + +_SOKOL_PRIVATE void _sapp_android_update_dimensions(ANativeWindow* window, bool force_update) { + SOKOL_ASSERT(_sapp.android.display != EGL_NO_DISPLAY); + SOKOL_ASSERT(_sapp.android.context != EGL_NO_CONTEXT); + SOKOL_ASSERT(_sapp.android.surface != EGL_NO_SURFACE); + SOKOL_ASSERT(window); + + const int32_t win_w = ANativeWindow_getWidth(window); + const int32_t win_h = ANativeWindow_getHeight(window); + SOKOL_ASSERT(win_w >= 0 && win_h >= 0); + const bool win_changed = (win_w != _sapp.window_width) || (win_h != _sapp.window_height); + _sapp.window_width = win_w; + _sapp.window_height = win_h; + if (win_changed || force_update) { + if (!_sapp.desc.high_dpi) { + const int32_t buf_w = win_w / 2; + const int32_t buf_h = win_h / 2; + EGLint format; + EGLBoolean egl_result = eglGetConfigAttrib(_sapp.android.display, _sapp.android.config, EGL_NATIVE_VISUAL_ID, &format); + SOKOL_ASSERT(egl_result == EGL_TRUE); _SOKOL_UNUSED(egl_result); + /* NOTE: calling ANativeWindow_setBuffersGeometry() with the same dimensions + as the ANativeWindow size results in weird display artefacts, that's + why it's only called when the buffer geometry is different from + the window size + */ + int32_t result = ANativeWindow_setBuffersGeometry(window, buf_w, buf_h, format); + SOKOL_ASSERT(result == 0); _SOKOL_UNUSED(result); + } + } + + /* query surface size */ + EGLint fb_w, fb_h; + EGLBoolean egl_result_w = eglQuerySurface(_sapp.android.display, _sapp.android.surface, EGL_WIDTH, &fb_w); + EGLBoolean egl_result_h = eglQuerySurface(_sapp.android.display, _sapp.android.surface, EGL_HEIGHT, &fb_h); + SOKOL_ASSERT(egl_result_w == EGL_TRUE); _SOKOL_UNUSED(egl_result_w); + SOKOL_ASSERT(egl_result_h == EGL_TRUE); _SOKOL_UNUSED(egl_result_h); + const bool fb_changed = (fb_w != _sapp.framebuffer_width) || (fb_h != _sapp.framebuffer_height); + _sapp.framebuffer_width = fb_w; + _sapp.framebuffer_height = fb_h; + _sapp.dpi_scale = (float)_sapp.framebuffer_width / (float)_sapp.window_width; + if (win_changed || fb_changed || force_update) { + if (!_sapp.first_frame) { + _sapp_android_app_event(SAPP_EVENTTYPE_RESIZED); + } + } +} + +_SOKOL_PRIVATE void _sapp_android_cleanup(void) { + if (_sapp.android.surface != EGL_NO_SURFACE) { + /* egl context is bound, cleanup gracefully */ + if (_sapp.init_called && !_sapp.cleanup_called) { + _sapp_call_cleanup(); + } + } + /* always try to cleanup by destroying egl context */ + _sapp_android_cleanup_egl(); +} + +_SOKOL_PRIVATE void _sapp_android_shutdown(void) { + /* try to cleanup while we still have a surface and can call cleanup_cb() */ + _sapp_android_cleanup(); + /* request exit */ + ANativeActivity_finish(_sapp.android.activity); +} + +_SOKOL_PRIVATE void _sapp_android_frame(void) { + SOKOL_ASSERT(_sapp.android.display != EGL_NO_DISPLAY); + SOKOL_ASSERT(_sapp.android.context != EGL_NO_CONTEXT); + SOKOL_ASSERT(_sapp.android.surface != EGL_NO_SURFACE); + _sapp_timing_measure(&_sapp.timing); + _sapp_android_update_dimensions(_sapp.android.current.window, false); + _sapp_frame(); + eglSwapBuffers(_sapp.android.display, _sapp.android.surface); +} + +_SOKOL_PRIVATE bool _sapp_android_touch_event(const AInputEvent* e) { + if (AInputEvent_getType(e) != AINPUT_EVENT_TYPE_MOTION) { + return false; + } + if (!_sapp_events_enabled()) { + return false; + } + int32_t action_idx = AMotionEvent_getAction(e); + int32_t action = action_idx & AMOTION_EVENT_ACTION_MASK; + sapp_event_type type = SAPP_EVENTTYPE_INVALID; + switch (action) { + case AMOTION_EVENT_ACTION_DOWN: + case AMOTION_EVENT_ACTION_POINTER_DOWN: + type = SAPP_EVENTTYPE_TOUCHES_BEGAN; + break; + case AMOTION_EVENT_ACTION_MOVE: + type = SAPP_EVENTTYPE_TOUCHES_MOVED; + break; + case AMOTION_EVENT_ACTION_UP: + case AMOTION_EVENT_ACTION_POINTER_UP: + type = SAPP_EVENTTYPE_TOUCHES_ENDED; + break; + case AMOTION_EVENT_ACTION_CANCEL: + type = SAPP_EVENTTYPE_TOUCHES_CANCELLED; + break; + default: + break; + } + if (type == SAPP_EVENTTYPE_INVALID) { + return false; + } + int32_t idx = action_idx >> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT; + _sapp_init_event(type); + _sapp.event.num_touches = (int)AMotionEvent_getPointerCount(e); + if (_sapp.event.num_touches > SAPP_MAX_TOUCHPOINTS) { + _sapp.event.num_touches = SAPP_MAX_TOUCHPOINTS; + } + for (int32_t i = 0; i < _sapp.event.num_touches; i++) { + sapp_touchpoint* dst = &_sapp.event.touches[i]; + dst->identifier = (uintptr_t)AMotionEvent_getPointerId(e, (size_t)i); + dst->pos_x = (AMotionEvent_getX(e, (size_t)i) / _sapp.window_width) * _sapp.framebuffer_width; + dst->pos_y = (AMotionEvent_getY(e, (size_t)i) / _sapp.window_height) * _sapp.framebuffer_height; + dst->android_tooltype = (sapp_android_tooltype) AMotionEvent_getToolType(e, (size_t)i); + if (action == AMOTION_EVENT_ACTION_POINTER_DOWN || + action == AMOTION_EVENT_ACTION_POINTER_UP) { + dst->changed = (i == idx); + } else { + dst->changed = true; + } + } + _sapp_call_event(&_sapp.event); + return true; +} + +_SOKOL_PRIVATE bool _sapp_android_key_event(const AInputEvent* e) { + if (AInputEvent_getType(e) != AINPUT_EVENT_TYPE_KEY) { + return false; + } + if (AKeyEvent_getKeyCode(e) == AKEYCODE_BACK) { + /* FIXME: this should be hooked into a "really quit?" mechanism + so the app can ask the user for confirmation, this is currently + generally missing in sokol_app.h + */ + _sapp_android_shutdown(); + return true; + } + return false; +} + +_SOKOL_PRIVATE int _sapp_android_input_cb(int fd, int events, void* data) { + _SOKOL_UNUSED(fd); + _SOKOL_UNUSED(data); + if ((events & ALOOPER_EVENT_INPUT) == 0) { + _SAPP_ERROR(ANDROID_UNSUPPORTED_INPUT_EVENT_INPUT_CB); + return 1; + } + SOKOL_ASSERT(_sapp.android.current.input); + AInputEvent* event = NULL; + while (AInputQueue_getEvent(_sapp.android.current.input, &event) >= 0) { + if (AInputQueue_preDispatchEvent(_sapp.android.current.input, event) != 0) { + continue; + } + int32_t handled = 0; + if (_sapp_android_touch_event(event) || _sapp_android_key_event(event)) { + handled = 1; + } + AInputQueue_finishEvent(_sapp.android.current.input, event, handled); + } + return 1; +} + +_SOKOL_PRIVATE int _sapp_android_main_cb(int fd, int events, void* data) { + _SOKOL_UNUSED(data); + if ((events & ALOOPER_EVENT_INPUT) == 0) { + _SAPP_ERROR(ANDROID_UNSUPPORTED_INPUT_EVENT_MAIN_CB); + return 1; + } + + _sapp_android_msg_t msg; + if (read(fd, &msg, sizeof(msg)) != sizeof(msg)) { + _SAPP_ERROR(ANDROID_READ_MSG_FAILED); + return 1; + } + + pthread_mutex_lock(&_sapp.android.pt.mutex); + switch (msg) { + case _SOKOL_ANDROID_MSG_CREATE: + { + _SAPP_INFO(ANDROID_MSG_CREATE); + SOKOL_ASSERT(!_sapp.valid); + bool result = _sapp_android_init_egl(); + SOKOL_ASSERT(result); _SOKOL_UNUSED(result); + _sapp.valid = true; + _sapp.android.has_created = true; + } + break; + case _SOKOL_ANDROID_MSG_RESUME: + _SAPP_INFO(ANDROID_MSG_RESUME); + _sapp.android.has_resumed = true; + _sapp_android_app_event(SAPP_EVENTTYPE_RESUMED); + break; + case _SOKOL_ANDROID_MSG_PAUSE: + _SAPP_INFO(ANDROID_MSG_PAUSE); + _sapp.android.has_resumed = false; + _sapp_android_app_event(SAPP_EVENTTYPE_SUSPENDED); + break; + case _SOKOL_ANDROID_MSG_FOCUS: + _SAPP_INFO(ANDROID_MSG_FOCUS); + _sapp.android.has_focus = true; + break; + case _SOKOL_ANDROID_MSG_NO_FOCUS: + _SAPP_INFO(ANDROID_MSG_NO_FOCUS); + _sapp.android.has_focus = false; + break; + case _SOKOL_ANDROID_MSG_SET_NATIVE_WINDOW: + _SAPP_INFO(ANDROID_MSG_SET_NATIVE_WINDOW); + if (_sapp.android.current.window != _sapp.android.pending.window) { + if (_sapp.android.current.window != NULL) { + _sapp_android_cleanup_egl_surface(); + } + if (_sapp.android.pending.window != NULL) { + if (_sapp_android_init_egl_surface(_sapp.android.pending.window)) { + _sapp_android_update_dimensions(_sapp.android.pending.window, true); + } else { + _sapp_android_shutdown(); + } + } + } + _sapp.android.current.window = _sapp.android.pending.window; + break; + case _SOKOL_ANDROID_MSG_SET_INPUT_QUEUE: + _SAPP_INFO(ANDROID_MSG_SET_INPUT_QUEUE); + if (_sapp.android.current.input != _sapp.android.pending.input) { + if (_sapp.android.current.input != NULL) { + AInputQueue_detachLooper(_sapp.android.current.input); + } + if (_sapp.android.pending.input != NULL) { + AInputQueue_attachLooper( + _sapp.android.pending.input, + _sapp.android.looper, + ALOOPER_POLL_CALLBACK, + _sapp_android_input_cb, + NULL); /* data */ + } + } + _sapp.android.current.input = _sapp.android.pending.input; + break; + case _SOKOL_ANDROID_MSG_DESTROY: + _SAPP_INFO(ANDROID_MSG_DESTROY); + _sapp_android_cleanup(); + _sapp.valid = false; + _sapp.android.is_thread_stopping = true; + break; + default: + _SAPP_WARN(ANDROID_UNKNOWN_MSG); + break; + } + pthread_cond_broadcast(&_sapp.android.pt.cond); /* signal "received" */ + pthread_mutex_unlock(&_sapp.android.pt.mutex); + return 1; +} + +_SOKOL_PRIVATE bool _sapp_android_should_update(void) { + bool is_in_front = _sapp.android.has_resumed && _sapp.android.has_focus; + bool has_surface = _sapp.android.surface != EGL_NO_SURFACE; + return is_in_front && has_surface; +} + +_SOKOL_PRIVATE void _sapp_android_show_keyboard(bool shown) { + SOKOL_ASSERT(_sapp.valid); + /* This seems to be broken in the NDK, but there is (a very cumbersome) workaround... */ + if (shown) { + ANativeActivity_showSoftInput(_sapp.android.activity, ANATIVEACTIVITY_SHOW_SOFT_INPUT_FORCED); + } else { + ANativeActivity_hideSoftInput(_sapp.android.activity, ANATIVEACTIVITY_HIDE_SOFT_INPUT_NOT_ALWAYS); + } +} + +_SOKOL_PRIVATE void* _sapp_android_loop(void* arg) { + _SOKOL_UNUSED(arg); + _SAPP_INFO(ANDROID_LOOP_THREAD_STARTED); + + _sapp.android.looper = ALooper_prepare(0 /* or ALOOPER_PREPARE_ALLOW_NON_CALLBACKS*/); + ALooper_addFd(_sapp.android.looper, + _sapp.android.pt.read_from_main_fd, + ALOOPER_POLL_CALLBACK, + ALOOPER_EVENT_INPUT, + _sapp_android_main_cb, + NULL); /* data */ + + /* signal start to main thread */ + pthread_mutex_lock(&_sapp.android.pt.mutex); + _sapp.android.is_thread_started = true; + pthread_cond_broadcast(&_sapp.android.pt.cond); + pthread_mutex_unlock(&_sapp.android.pt.mutex); + + /* main loop */ + while (!_sapp.android.is_thread_stopping) { + /* sokol frame */ + if (_sapp_android_should_update()) { + _sapp_android_frame(); + } + + /* process all events (or stop early if app is requested to quit) */ + bool process_events = true; + while (process_events && !_sapp.android.is_thread_stopping) { + bool block_until_event = !_sapp.android.is_thread_stopping && !_sapp_android_should_update(); + process_events = ALooper_pollOnce(block_until_event ? -1 : 0, NULL, NULL, NULL) == ALOOPER_POLL_CALLBACK; + } + } + + /* cleanup thread */ + if (_sapp.android.current.input != NULL) { + AInputQueue_detachLooper(_sapp.android.current.input); + } + + /* the following causes heap corruption on exit, why?? + ALooper_removeFd(_sapp.android.looper, _sapp.android.pt.read_from_main_fd); + ALooper_release(_sapp.android.looper);*/ + + /* signal "destroyed" */ + pthread_mutex_lock(&_sapp.android.pt.mutex); + _sapp.android.is_thread_stopped = true; + pthread_cond_broadcast(&_sapp.android.pt.cond); + pthread_mutex_unlock(&_sapp.android.pt.mutex); + + _SAPP_INFO(ANDROID_LOOP_THREAD_DONE); + return NULL; +} + +/* android main/ui thread */ +_SOKOL_PRIVATE void _sapp_android_msg(_sapp_android_msg_t msg) { + if (write(_sapp.android.pt.write_from_main_fd, &msg, sizeof(msg)) != sizeof(msg)) { + _SAPP_ERROR(ANDROID_WRITE_MSG_FAILED); + } +} + +_SOKOL_PRIVATE void _sapp_android_on_start(ANativeActivity* activity) { + _SOKOL_UNUSED(activity); + _SAPP_INFO(ANDROID_NATIVE_ACTIVITY_ONSTART); +} + +_SOKOL_PRIVATE void _sapp_android_on_resume(ANativeActivity* activity) { + _SOKOL_UNUSED(activity); + _SAPP_INFO(ANDROID_NATIVE_ACTIVITY_ONRESUME); + _sapp_android_msg(_SOKOL_ANDROID_MSG_RESUME); +} + +_SOKOL_PRIVATE void* _sapp_android_on_save_instance_state(ANativeActivity* activity, size_t* out_size) { + _SOKOL_UNUSED(activity); + _SAPP_INFO(ANDROID_NATIVE_ACTIVITY_ONSAVEINSTANCESTATE); + *out_size = 0; + return NULL; +} + +_SOKOL_PRIVATE void _sapp_android_on_window_focus_changed(ANativeActivity* activity, int has_focus) { + _SOKOL_UNUSED(activity); + _SAPP_INFO(ANDROID_NATIVE_ACTIVITY_ONWINDOWFOCUSCHANGED); + if (has_focus) { + _sapp_android_msg(_SOKOL_ANDROID_MSG_FOCUS); + } else { + _sapp_android_msg(_SOKOL_ANDROID_MSG_NO_FOCUS); + } +} + +_SOKOL_PRIVATE void _sapp_android_on_pause(ANativeActivity* activity) { + _SOKOL_UNUSED(activity); + _SAPP_INFO(ANDROID_NATIVE_ACTIVITY_ONPAUSE); + _sapp_android_msg(_SOKOL_ANDROID_MSG_PAUSE); +} + +_SOKOL_PRIVATE void _sapp_android_on_stop(ANativeActivity* activity) { + _SOKOL_UNUSED(activity); + _SAPP_INFO(ANDROID_NATIVE_ACTIVITY_ONSTOP); +} + +_SOKOL_PRIVATE void _sapp_android_msg_set_native_window(ANativeWindow* window) { + pthread_mutex_lock(&_sapp.android.pt.mutex); + _sapp.android.pending.window = window; + _sapp_android_msg(_SOKOL_ANDROID_MSG_SET_NATIVE_WINDOW); + while (_sapp.android.current.window != window) { + pthread_cond_wait(&_sapp.android.pt.cond, &_sapp.android.pt.mutex); + } + pthread_mutex_unlock(&_sapp.android.pt.mutex); +} + +_SOKOL_PRIVATE void _sapp_android_on_native_window_created(ANativeActivity* activity, ANativeWindow* window) { + _SOKOL_UNUSED(activity); + _SAPP_INFO(ANDROID_NATIVE_ACTIVITY_ONNATIVEWINDOWCREATED); + _sapp_android_msg_set_native_window(window); +} + +_SOKOL_PRIVATE void _sapp_android_on_native_window_destroyed(ANativeActivity* activity, ANativeWindow* window) { + _SOKOL_UNUSED(activity); + _SOKOL_UNUSED(window); + _SAPP_INFO(ANDROID_NATIVE_ACTIVITY_ONNATIVEWINDOWDESTROYED); + _sapp_android_msg_set_native_window(NULL); +} + +_SOKOL_PRIVATE void _sapp_android_msg_set_input_queue(AInputQueue* input) { + pthread_mutex_lock(&_sapp.android.pt.mutex); + _sapp.android.pending.input = input; + _sapp_android_msg(_SOKOL_ANDROID_MSG_SET_INPUT_QUEUE); + while (_sapp.android.current.input != input) { + pthread_cond_wait(&_sapp.android.pt.cond, &_sapp.android.pt.mutex); + } + pthread_mutex_unlock(&_sapp.android.pt.mutex); +} + +_SOKOL_PRIVATE void _sapp_android_on_input_queue_created(ANativeActivity* activity, AInputQueue* queue) { + _SOKOL_UNUSED(activity); + _SAPP_INFO(ANDROID_NATIVE_ACTIVITY_ONINPUTQUEUECREATED); + _sapp_android_msg_set_input_queue(queue); +} + +_SOKOL_PRIVATE void _sapp_android_on_input_queue_destroyed(ANativeActivity* activity, AInputQueue* queue) { + _SOKOL_UNUSED(activity); + _SOKOL_UNUSED(queue); + _SAPP_INFO(ANDROID_NATIVE_ACTIVITY_ONINPUTQUEUEDESTROYED); + _sapp_android_msg_set_input_queue(NULL); +} + +_SOKOL_PRIVATE void _sapp_android_on_config_changed(ANativeActivity* activity) { + _SOKOL_UNUSED(activity); + _SAPP_INFO(ANDROID_NATIVE_ACTIVITY_ONCONFIGURATIONCHANGED); + /* see android:configChanges in manifest */ +} + +_SOKOL_PRIVATE void _sapp_android_on_low_memory(ANativeActivity* activity) { + _SOKOL_UNUSED(activity); + _SAPP_INFO(ANDROID_NATIVE_ACTIVITY_ONLOWMEMORY); +} + +_SOKOL_PRIVATE void _sapp_android_on_destroy(ANativeActivity* activity) { + /* + * For some reason even an empty app using nativeactivity.h will crash (WIN DEATH) + * on my device (Moto X 2nd gen) when the app is removed from the task view + * (TaskStackView: onTaskViewDismissed). + * + * However, if ANativeActivity_finish() is explicitly called from for example + * _sapp_android_on_stop(), the crash disappears. Is this a bug in NativeActivity? + */ + _SOKOL_UNUSED(activity); + _SAPP_INFO(ANDROID_NATIVE_ACTIVITY_ONDESTROY); + + /* send destroy msg */ + pthread_mutex_lock(&_sapp.android.pt.mutex); + _sapp_android_msg(_SOKOL_ANDROID_MSG_DESTROY); + while (!_sapp.android.is_thread_stopped) { + pthread_cond_wait(&_sapp.android.pt.cond, &_sapp.android.pt.mutex); + } + pthread_mutex_unlock(&_sapp.android.pt.mutex); + + /* clean up main thread */ + pthread_cond_destroy(&_sapp.android.pt.cond); + pthread_mutex_destroy(&_sapp.android.pt.mutex); + + close(_sapp.android.pt.read_from_main_fd); + close(_sapp.android.pt.write_from_main_fd); + + _SAPP_INFO(ANDROID_NATIVE_ACTIVITY_DONE); + + /* this is a bit naughty, but causes a clean restart of the app (static globals are reset) */ + exit(0); +} + +JNIEXPORT +void ANativeActivity_onCreate(ANativeActivity* activity, void* saved_state, size_t saved_state_size) { + _SOKOL_UNUSED(saved_state); + _SOKOL_UNUSED(saved_state_size); + _SAPP_INFO(ANDROID_NATIVE_ACTIVITY_ONCREATE); + + // the NativeActity pointer needs to be available inside sokol_main() + // (see https://github.com/floooh/sokol/issues/708), however _sapp_init_state() + // will clear the global _sapp_t struct, so we need to initialize the native + // activity pointer twice, once before sokol_main() and once after _sapp_init_state() + _sapp_clear(&_sapp, sizeof(_sapp)); + _sapp.android.activity = activity; + sapp_desc desc = sokol_main(0, NULL); + _sapp_init_state(&desc); + _sapp.android.activity = activity; + + int pipe_fd[2]; + if (pipe(pipe_fd) != 0) { + _SAPP_ERROR(ANDROID_CREATE_THREAD_PIPE_FAILED); + return; + } + _sapp.android.pt.read_from_main_fd = pipe_fd[0]; + _sapp.android.pt.write_from_main_fd = pipe_fd[1]; + + pthread_mutex_init(&_sapp.android.pt.mutex, NULL); + pthread_cond_init(&_sapp.android.pt.cond, NULL); + + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + pthread_create(&_sapp.android.pt.thread, &attr, _sapp_android_loop, 0); + pthread_attr_destroy(&attr); + + /* wait until main loop has started */ + pthread_mutex_lock(&_sapp.android.pt.mutex); + while (!_sapp.android.is_thread_started) { + pthread_cond_wait(&_sapp.android.pt.cond, &_sapp.android.pt.mutex); + } + pthread_mutex_unlock(&_sapp.android.pt.mutex); + + /* send create msg */ + pthread_mutex_lock(&_sapp.android.pt.mutex); + _sapp_android_msg(_SOKOL_ANDROID_MSG_CREATE); + while (!_sapp.android.has_created) { + pthread_cond_wait(&_sapp.android.pt.cond, &_sapp.android.pt.mutex); + } + pthread_mutex_unlock(&_sapp.android.pt.mutex); + + /* register for callbacks */ + activity->callbacks->onStart = _sapp_android_on_start; + activity->callbacks->onResume = _sapp_android_on_resume; + activity->callbacks->onSaveInstanceState = _sapp_android_on_save_instance_state; + activity->callbacks->onWindowFocusChanged = _sapp_android_on_window_focus_changed; + activity->callbacks->onPause = _sapp_android_on_pause; + activity->callbacks->onStop = _sapp_android_on_stop; + activity->callbacks->onDestroy = _sapp_android_on_destroy; + activity->callbacks->onNativeWindowCreated = _sapp_android_on_native_window_created; + /* activity->callbacks->onNativeWindowResized = _sapp_android_on_native_window_resized; */ + /* activity->callbacks->onNativeWindowRedrawNeeded = _sapp_android_on_native_window_redraw_needed; */ + activity->callbacks->onNativeWindowDestroyed = _sapp_android_on_native_window_destroyed; + activity->callbacks->onInputQueueCreated = _sapp_android_on_input_queue_created; + activity->callbacks->onInputQueueDestroyed = _sapp_android_on_input_queue_destroyed; + /* activity->callbacks->onContentRectChanged = _sapp_android_on_content_rect_changed; */ + /* activity->callbacks->onConfigurationChanged = _sapp_android_on_config_changed; */ + activity->callbacks->onLowMemory = _sapp_android_on_low_memory; + + _SAPP_INFO(ANDROID_NATIVE_ACTIVITY_CREATE_SUCCESS); + + /* NOT A BUG: do NOT call sapp_discard_state() */ +} + +#endif /* _SAPP_ANDROID */ + +// ██ ██ ███ ██ ██ ██ ██ ██ +// ██ ██ ████ ██ ██ ██ ██ ██ +// ██ ██ ██ ██ ██ ██ ██ ███ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ███████ ██ ██ ████ ██████ ██ ██ +// +// >>linux +#if defined(_SAPP_LINUX) + +/* see GLFW's xkb_unicode.c */ +static const struct _sapp_x11_codepair { + uint16_t keysym; + uint16_t ucs; +} _sapp_x11_keysymtab[] = { + { 0x01a1, 0x0104 }, + { 0x01a2, 0x02d8 }, + { 0x01a3, 0x0141 }, + { 0x01a5, 0x013d }, + { 0x01a6, 0x015a }, + { 0x01a9, 0x0160 }, + { 0x01aa, 0x015e }, + { 0x01ab, 0x0164 }, + { 0x01ac, 0x0179 }, + { 0x01ae, 0x017d }, + { 0x01af, 0x017b }, + { 0x01b1, 0x0105 }, + { 0x01b2, 0x02db }, + { 0x01b3, 0x0142 }, + { 0x01b5, 0x013e }, + { 0x01b6, 0x015b }, + { 0x01b7, 0x02c7 }, + { 0x01b9, 0x0161 }, + { 0x01ba, 0x015f }, + { 0x01bb, 0x0165 }, + { 0x01bc, 0x017a }, + { 0x01bd, 0x02dd }, + { 0x01be, 0x017e }, + { 0x01bf, 0x017c }, + { 0x01c0, 0x0154 }, + { 0x01c3, 0x0102 }, + { 0x01c5, 0x0139 }, + { 0x01c6, 0x0106 }, + { 0x01c8, 0x010c }, + { 0x01ca, 0x0118 }, + { 0x01cc, 0x011a }, + { 0x01cf, 0x010e }, + { 0x01d0, 0x0110 }, + { 0x01d1, 0x0143 }, + { 0x01d2, 0x0147 }, + { 0x01d5, 0x0150 }, + { 0x01d8, 0x0158 }, + { 0x01d9, 0x016e }, + { 0x01db, 0x0170 }, + { 0x01de, 0x0162 }, + { 0x01e0, 0x0155 }, + { 0x01e3, 0x0103 }, + { 0x01e5, 0x013a }, + { 0x01e6, 0x0107 }, + { 0x01e8, 0x010d }, + { 0x01ea, 0x0119 }, + { 0x01ec, 0x011b }, + { 0x01ef, 0x010f }, + { 0x01f0, 0x0111 }, + { 0x01f1, 0x0144 }, + { 0x01f2, 0x0148 }, + { 0x01f5, 0x0151 }, + { 0x01f8, 0x0159 }, + { 0x01f9, 0x016f }, + { 0x01fb, 0x0171 }, + { 0x01fe, 0x0163 }, + { 0x01ff, 0x02d9 }, + { 0x02a1, 0x0126 }, + { 0x02a6, 0x0124 }, + { 0x02a9, 0x0130 }, + { 0x02ab, 0x011e }, + { 0x02ac, 0x0134 }, + { 0x02b1, 0x0127 }, + { 0x02b6, 0x0125 }, + { 0x02b9, 0x0131 }, + { 0x02bb, 0x011f }, + { 0x02bc, 0x0135 }, + { 0x02c5, 0x010a }, + { 0x02c6, 0x0108 }, + { 0x02d5, 0x0120 }, + { 0x02d8, 0x011c }, + { 0x02dd, 0x016c }, + { 0x02de, 0x015c }, + { 0x02e5, 0x010b }, + { 0x02e6, 0x0109 }, + { 0x02f5, 0x0121 }, + { 0x02f8, 0x011d }, + { 0x02fd, 0x016d }, + { 0x02fe, 0x015d }, + { 0x03a2, 0x0138 }, + { 0x03a3, 0x0156 }, + { 0x03a5, 0x0128 }, + { 0x03a6, 0x013b }, + { 0x03aa, 0x0112 }, + { 0x03ab, 0x0122 }, + { 0x03ac, 0x0166 }, + { 0x03b3, 0x0157 }, + { 0x03b5, 0x0129 }, + { 0x03b6, 0x013c }, + { 0x03ba, 0x0113 }, + { 0x03bb, 0x0123 }, + { 0x03bc, 0x0167 }, + { 0x03bd, 0x014a }, + { 0x03bf, 0x014b }, + { 0x03c0, 0x0100 }, + { 0x03c7, 0x012e }, + { 0x03cc, 0x0116 }, + { 0x03cf, 0x012a }, + { 0x03d1, 0x0145 }, + { 0x03d2, 0x014c }, + { 0x03d3, 0x0136 }, + { 0x03d9, 0x0172 }, + { 0x03dd, 0x0168 }, + { 0x03de, 0x016a }, + { 0x03e0, 0x0101 }, + { 0x03e7, 0x012f }, + { 0x03ec, 0x0117 }, + { 0x03ef, 0x012b }, + { 0x03f1, 0x0146 }, + { 0x03f2, 0x014d }, + { 0x03f3, 0x0137 }, + { 0x03f9, 0x0173 }, + { 0x03fd, 0x0169 }, + { 0x03fe, 0x016b }, + { 0x047e, 0x203e }, + { 0x04a1, 0x3002 }, + { 0x04a2, 0x300c }, + { 0x04a3, 0x300d }, + { 0x04a4, 0x3001 }, + { 0x04a5, 0x30fb }, + { 0x04a6, 0x30f2 }, + { 0x04a7, 0x30a1 }, + { 0x04a8, 0x30a3 }, + { 0x04a9, 0x30a5 }, + { 0x04aa, 0x30a7 }, + { 0x04ab, 0x30a9 }, + { 0x04ac, 0x30e3 }, + { 0x04ad, 0x30e5 }, + { 0x04ae, 0x30e7 }, + { 0x04af, 0x30c3 }, + { 0x04b0, 0x30fc }, + { 0x04b1, 0x30a2 }, + { 0x04b2, 0x30a4 }, + { 0x04b3, 0x30a6 }, + { 0x04b4, 0x30a8 }, + { 0x04b5, 0x30aa }, + { 0x04b6, 0x30ab }, + { 0x04b7, 0x30ad }, + { 0x04b8, 0x30af }, + { 0x04b9, 0x30b1 }, + { 0x04ba, 0x30b3 }, + { 0x04bb, 0x30b5 }, + { 0x04bc, 0x30b7 }, + { 0x04bd, 0x30b9 }, + { 0x04be, 0x30bb }, + { 0x04bf, 0x30bd }, + { 0x04c0, 0x30bf }, + { 0x04c1, 0x30c1 }, + { 0x04c2, 0x30c4 }, + { 0x04c3, 0x30c6 }, + { 0x04c4, 0x30c8 }, + { 0x04c5, 0x30ca }, + { 0x04c6, 0x30cb }, + { 0x04c7, 0x30cc }, + { 0x04c8, 0x30cd }, + { 0x04c9, 0x30ce }, + { 0x04ca, 0x30cf }, + { 0x04cb, 0x30d2 }, + { 0x04cc, 0x30d5 }, + { 0x04cd, 0x30d8 }, + { 0x04ce, 0x30db }, + { 0x04cf, 0x30de }, + { 0x04d0, 0x30df }, + { 0x04d1, 0x30e0 }, + { 0x04d2, 0x30e1 }, + { 0x04d3, 0x30e2 }, + { 0x04d4, 0x30e4 }, + { 0x04d5, 0x30e6 }, + { 0x04d6, 0x30e8 }, + { 0x04d7, 0x30e9 }, + { 0x04d8, 0x30ea }, + { 0x04d9, 0x30eb }, + { 0x04da, 0x30ec }, + { 0x04db, 0x30ed }, + { 0x04dc, 0x30ef }, + { 0x04dd, 0x30f3 }, + { 0x04de, 0x309b }, + { 0x04df, 0x309c }, + { 0x05ac, 0x060c }, + { 0x05bb, 0x061b }, + { 0x05bf, 0x061f }, + { 0x05c1, 0x0621 }, + { 0x05c2, 0x0622 }, + { 0x05c3, 0x0623 }, + { 0x05c4, 0x0624 }, + { 0x05c5, 0x0625 }, + { 0x05c6, 0x0626 }, + { 0x05c7, 0x0627 }, + { 0x05c8, 0x0628 }, + { 0x05c9, 0x0629 }, + { 0x05ca, 0x062a }, + { 0x05cb, 0x062b }, + { 0x05cc, 0x062c }, + { 0x05cd, 0x062d }, + { 0x05ce, 0x062e }, + { 0x05cf, 0x062f }, + { 0x05d0, 0x0630 }, + { 0x05d1, 0x0631 }, + { 0x05d2, 0x0632 }, + { 0x05d3, 0x0633 }, + { 0x05d4, 0x0634 }, + { 0x05d5, 0x0635 }, + { 0x05d6, 0x0636 }, + { 0x05d7, 0x0637 }, + { 0x05d8, 0x0638 }, + { 0x05d9, 0x0639 }, + { 0x05da, 0x063a }, + { 0x05e0, 0x0640 }, + { 0x05e1, 0x0641 }, + { 0x05e2, 0x0642 }, + { 0x05e3, 0x0643 }, + { 0x05e4, 0x0644 }, + { 0x05e5, 0x0645 }, + { 0x05e6, 0x0646 }, + { 0x05e7, 0x0647 }, + { 0x05e8, 0x0648 }, + { 0x05e9, 0x0649 }, + { 0x05ea, 0x064a }, + { 0x05eb, 0x064b }, + { 0x05ec, 0x064c }, + { 0x05ed, 0x064d }, + { 0x05ee, 0x064e }, + { 0x05ef, 0x064f }, + { 0x05f0, 0x0650 }, + { 0x05f1, 0x0651 }, + { 0x05f2, 0x0652 }, + { 0x06a1, 0x0452 }, + { 0x06a2, 0x0453 }, + { 0x06a3, 0x0451 }, + { 0x06a4, 0x0454 }, + { 0x06a5, 0x0455 }, + { 0x06a6, 0x0456 }, + { 0x06a7, 0x0457 }, + { 0x06a8, 0x0458 }, + { 0x06a9, 0x0459 }, + { 0x06aa, 0x045a }, + { 0x06ab, 0x045b }, + { 0x06ac, 0x045c }, + { 0x06ae, 0x045e }, + { 0x06af, 0x045f }, + { 0x06b0, 0x2116 }, + { 0x06b1, 0x0402 }, + { 0x06b2, 0x0403 }, + { 0x06b3, 0x0401 }, + { 0x06b4, 0x0404 }, + { 0x06b5, 0x0405 }, + { 0x06b6, 0x0406 }, + { 0x06b7, 0x0407 }, + { 0x06b8, 0x0408 }, + { 0x06b9, 0x0409 }, + { 0x06ba, 0x040a }, + { 0x06bb, 0x040b }, + { 0x06bc, 0x040c }, + { 0x06be, 0x040e }, + { 0x06bf, 0x040f }, + { 0x06c0, 0x044e }, + { 0x06c1, 0x0430 }, + { 0x06c2, 0x0431 }, + { 0x06c3, 0x0446 }, + { 0x06c4, 0x0434 }, + { 0x06c5, 0x0435 }, + { 0x06c6, 0x0444 }, + { 0x06c7, 0x0433 }, + { 0x06c8, 0x0445 }, + { 0x06c9, 0x0438 }, + { 0x06ca, 0x0439 }, + { 0x06cb, 0x043a }, + { 0x06cc, 0x043b }, + { 0x06cd, 0x043c }, + { 0x06ce, 0x043d }, + { 0x06cf, 0x043e }, + { 0x06d0, 0x043f }, + { 0x06d1, 0x044f }, + { 0x06d2, 0x0440 }, + { 0x06d3, 0x0441 }, + { 0x06d4, 0x0442 }, + { 0x06d5, 0x0443 }, + { 0x06d6, 0x0436 }, + { 0x06d7, 0x0432 }, + { 0x06d8, 0x044c }, + { 0x06d9, 0x044b }, + { 0x06da, 0x0437 }, + { 0x06db, 0x0448 }, + { 0x06dc, 0x044d }, + { 0x06dd, 0x0449 }, + { 0x06de, 0x0447 }, + { 0x06df, 0x044a }, + { 0x06e0, 0x042e }, + { 0x06e1, 0x0410 }, + { 0x06e2, 0x0411 }, + { 0x06e3, 0x0426 }, + { 0x06e4, 0x0414 }, + { 0x06e5, 0x0415 }, + { 0x06e6, 0x0424 }, + { 0x06e7, 0x0413 }, + { 0x06e8, 0x0425 }, + { 0x06e9, 0x0418 }, + { 0x06ea, 0x0419 }, + { 0x06eb, 0x041a }, + { 0x06ec, 0x041b }, + { 0x06ed, 0x041c }, + { 0x06ee, 0x041d }, + { 0x06ef, 0x041e }, + { 0x06f0, 0x041f }, + { 0x06f1, 0x042f }, + { 0x06f2, 0x0420 }, + { 0x06f3, 0x0421 }, + { 0x06f4, 0x0422 }, + { 0x06f5, 0x0423 }, + { 0x06f6, 0x0416 }, + { 0x06f7, 0x0412 }, + { 0x06f8, 0x042c }, + { 0x06f9, 0x042b }, + { 0x06fa, 0x0417 }, + { 0x06fb, 0x0428 }, + { 0x06fc, 0x042d }, + { 0x06fd, 0x0429 }, + { 0x06fe, 0x0427 }, + { 0x06ff, 0x042a }, + { 0x07a1, 0x0386 }, + { 0x07a2, 0x0388 }, + { 0x07a3, 0x0389 }, + { 0x07a4, 0x038a }, + { 0x07a5, 0x03aa }, + { 0x07a7, 0x038c }, + { 0x07a8, 0x038e }, + { 0x07a9, 0x03ab }, + { 0x07ab, 0x038f }, + { 0x07ae, 0x0385 }, + { 0x07af, 0x2015 }, + { 0x07b1, 0x03ac }, + { 0x07b2, 0x03ad }, + { 0x07b3, 0x03ae }, + { 0x07b4, 0x03af }, + { 0x07b5, 0x03ca }, + { 0x07b6, 0x0390 }, + { 0x07b7, 0x03cc }, + { 0x07b8, 0x03cd }, + { 0x07b9, 0x03cb }, + { 0x07ba, 0x03b0 }, + { 0x07bb, 0x03ce }, + { 0x07c1, 0x0391 }, + { 0x07c2, 0x0392 }, + { 0x07c3, 0x0393 }, + { 0x07c4, 0x0394 }, + { 0x07c5, 0x0395 }, + { 0x07c6, 0x0396 }, + { 0x07c7, 0x0397 }, + { 0x07c8, 0x0398 }, + { 0x07c9, 0x0399 }, + { 0x07ca, 0x039a }, + { 0x07cb, 0x039b }, + { 0x07cc, 0x039c }, + { 0x07cd, 0x039d }, + { 0x07ce, 0x039e }, + { 0x07cf, 0x039f }, + { 0x07d0, 0x03a0 }, + { 0x07d1, 0x03a1 }, + { 0x07d2, 0x03a3 }, + { 0x07d4, 0x03a4 }, + { 0x07d5, 0x03a5 }, + { 0x07d6, 0x03a6 }, + { 0x07d7, 0x03a7 }, + { 0x07d8, 0x03a8 }, + { 0x07d9, 0x03a9 }, + { 0x07e1, 0x03b1 }, + { 0x07e2, 0x03b2 }, + { 0x07e3, 0x03b3 }, + { 0x07e4, 0x03b4 }, + { 0x07e5, 0x03b5 }, + { 0x07e6, 0x03b6 }, + { 0x07e7, 0x03b7 }, + { 0x07e8, 0x03b8 }, + { 0x07e9, 0x03b9 }, + { 0x07ea, 0x03ba }, + { 0x07eb, 0x03bb }, + { 0x07ec, 0x03bc }, + { 0x07ed, 0x03bd }, + { 0x07ee, 0x03be }, + { 0x07ef, 0x03bf }, + { 0x07f0, 0x03c0 }, + { 0x07f1, 0x03c1 }, + { 0x07f2, 0x03c3 }, + { 0x07f3, 0x03c2 }, + { 0x07f4, 0x03c4 }, + { 0x07f5, 0x03c5 }, + { 0x07f6, 0x03c6 }, + { 0x07f7, 0x03c7 }, + { 0x07f8, 0x03c8 }, + { 0x07f9, 0x03c9 }, + { 0x08a1, 0x23b7 }, + { 0x08a2, 0x250c }, + { 0x08a3, 0x2500 }, + { 0x08a4, 0x2320 }, + { 0x08a5, 0x2321 }, + { 0x08a6, 0x2502 }, + { 0x08a7, 0x23a1 }, + { 0x08a8, 0x23a3 }, + { 0x08a9, 0x23a4 }, + { 0x08aa, 0x23a6 }, + { 0x08ab, 0x239b }, + { 0x08ac, 0x239d }, + { 0x08ad, 0x239e }, + { 0x08ae, 0x23a0 }, + { 0x08af, 0x23a8 }, + { 0x08b0, 0x23ac }, + { 0x08bc, 0x2264 }, + { 0x08bd, 0x2260 }, + { 0x08be, 0x2265 }, + { 0x08bf, 0x222b }, + { 0x08c0, 0x2234 }, + { 0x08c1, 0x221d }, + { 0x08c2, 0x221e }, + { 0x08c5, 0x2207 }, + { 0x08c8, 0x223c }, + { 0x08c9, 0x2243 }, + { 0x08cd, 0x21d4 }, + { 0x08ce, 0x21d2 }, + { 0x08cf, 0x2261 }, + { 0x08d6, 0x221a }, + { 0x08da, 0x2282 }, + { 0x08db, 0x2283 }, + { 0x08dc, 0x2229 }, + { 0x08dd, 0x222a }, + { 0x08de, 0x2227 }, + { 0x08df, 0x2228 }, + { 0x08ef, 0x2202 }, + { 0x08f6, 0x0192 }, + { 0x08fb, 0x2190 }, + { 0x08fc, 0x2191 }, + { 0x08fd, 0x2192 }, + { 0x08fe, 0x2193 }, + { 0x09e0, 0x25c6 }, + { 0x09e1, 0x2592 }, + { 0x09e2, 0x2409 }, + { 0x09e3, 0x240c }, + { 0x09e4, 0x240d }, + { 0x09e5, 0x240a }, + { 0x09e8, 0x2424 }, + { 0x09e9, 0x240b }, + { 0x09ea, 0x2518 }, + { 0x09eb, 0x2510 }, + { 0x09ec, 0x250c }, + { 0x09ed, 0x2514 }, + { 0x09ee, 0x253c }, + { 0x09ef, 0x23ba }, + { 0x09f0, 0x23bb }, + { 0x09f1, 0x2500 }, + { 0x09f2, 0x23bc }, + { 0x09f3, 0x23bd }, + { 0x09f4, 0x251c }, + { 0x09f5, 0x2524 }, + { 0x09f6, 0x2534 }, + { 0x09f7, 0x252c }, + { 0x09f8, 0x2502 }, + { 0x0aa1, 0x2003 }, + { 0x0aa2, 0x2002 }, + { 0x0aa3, 0x2004 }, + { 0x0aa4, 0x2005 }, + { 0x0aa5, 0x2007 }, + { 0x0aa6, 0x2008 }, + { 0x0aa7, 0x2009 }, + { 0x0aa8, 0x200a }, + { 0x0aa9, 0x2014 }, + { 0x0aaa, 0x2013 }, + { 0x0aae, 0x2026 }, + { 0x0aaf, 0x2025 }, + { 0x0ab0, 0x2153 }, + { 0x0ab1, 0x2154 }, + { 0x0ab2, 0x2155 }, + { 0x0ab3, 0x2156 }, + { 0x0ab4, 0x2157 }, + { 0x0ab5, 0x2158 }, + { 0x0ab6, 0x2159 }, + { 0x0ab7, 0x215a }, + { 0x0ab8, 0x2105 }, + { 0x0abb, 0x2012 }, + { 0x0abc, 0x2329 }, + { 0x0abe, 0x232a }, + { 0x0ac3, 0x215b }, + { 0x0ac4, 0x215c }, + { 0x0ac5, 0x215d }, + { 0x0ac6, 0x215e }, + { 0x0ac9, 0x2122 }, + { 0x0aca, 0x2613 }, + { 0x0acc, 0x25c1 }, + { 0x0acd, 0x25b7 }, + { 0x0ace, 0x25cb }, + { 0x0acf, 0x25af }, + { 0x0ad0, 0x2018 }, + { 0x0ad1, 0x2019 }, + { 0x0ad2, 0x201c }, + { 0x0ad3, 0x201d }, + { 0x0ad4, 0x211e }, + { 0x0ad6, 0x2032 }, + { 0x0ad7, 0x2033 }, + { 0x0ad9, 0x271d }, + { 0x0adb, 0x25ac }, + { 0x0adc, 0x25c0 }, + { 0x0add, 0x25b6 }, + { 0x0ade, 0x25cf }, + { 0x0adf, 0x25ae }, + { 0x0ae0, 0x25e6 }, + { 0x0ae1, 0x25ab }, + { 0x0ae2, 0x25ad }, + { 0x0ae3, 0x25b3 }, + { 0x0ae4, 0x25bd }, + { 0x0ae5, 0x2606 }, + { 0x0ae6, 0x2022 }, + { 0x0ae7, 0x25aa }, + { 0x0ae8, 0x25b2 }, + { 0x0ae9, 0x25bc }, + { 0x0aea, 0x261c }, + { 0x0aeb, 0x261e }, + { 0x0aec, 0x2663 }, + { 0x0aed, 0x2666 }, + { 0x0aee, 0x2665 }, + { 0x0af0, 0x2720 }, + { 0x0af1, 0x2020 }, + { 0x0af2, 0x2021 }, + { 0x0af3, 0x2713 }, + { 0x0af4, 0x2717 }, + { 0x0af5, 0x266f }, + { 0x0af6, 0x266d }, + { 0x0af7, 0x2642 }, + { 0x0af8, 0x2640 }, + { 0x0af9, 0x260e }, + { 0x0afa, 0x2315 }, + { 0x0afb, 0x2117 }, + { 0x0afc, 0x2038 }, + { 0x0afd, 0x201a }, + { 0x0afe, 0x201e }, + { 0x0ba3, 0x003c }, + { 0x0ba6, 0x003e }, + { 0x0ba8, 0x2228 }, + { 0x0ba9, 0x2227 }, + { 0x0bc0, 0x00af }, + { 0x0bc2, 0x22a5 }, + { 0x0bc3, 0x2229 }, + { 0x0bc4, 0x230a }, + { 0x0bc6, 0x005f }, + { 0x0bca, 0x2218 }, + { 0x0bcc, 0x2395 }, + { 0x0bce, 0x22a4 }, + { 0x0bcf, 0x25cb }, + { 0x0bd3, 0x2308 }, + { 0x0bd6, 0x222a }, + { 0x0bd8, 0x2283 }, + { 0x0bda, 0x2282 }, + { 0x0bdc, 0x22a2 }, + { 0x0bfc, 0x22a3 }, + { 0x0cdf, 0x2017 }, + { 0x0ce0, 0x05d0 }, + { 0x0ce1, 0x05d1 }, + { 0x0ce2, 0x05d2 }, + { 0x0ce3, 0x05d3 }, + { 0x0ce4, 0x05d4 }, + { 0x0ce5, 0x05d5 }, + { 0x0ce6, 0x05d6 }, + { 0x0ce7, 0x05d7 }, + { 0x0ce8, 0x05d8 }, + { 0x0ce9, 0x05d9 }, + { 0x0cea, 0x05da }, + { 0x0ceb, 0x05db }, + { 0x0cec, 0x05dc }, + { 0x0ced, 0x05dd }, + { 0x0cee, 0x05de }, + { 0x0cef, 0x05df }, + { 0x0cf0, 0x05e0 }, + { 0x0cf1, 0x05e1 }, + { 0x0cf2, 0x05e2 }, + { 0x0cf3, 0x05e3 }, + { 0x0cf4, 0x05e4 }, + { 0x0cf5, 0x05e5 }, + { 0x0cf6, 0x05e6 }, + { 0x0cf7, 0x05e7 }, + { 0x0cf8, 0x05e8 }, + { 0x0cf9, 0x05e9 }, + { 0x0cfa, 0x05ea }, + { 0x0da1, 0x0e01 }, + { 0x0da2, 0x0e02 }, + { 0x0da3, 0x0e03 }, + { 0x0da4, 0x0e04 }, + { 0x0da5, 0x0e05 }, + { 0x0da6, 0x0e06 }, + { 0x0da7, 0x0e07 }, + { 0x0da8, 0x0e08 }, + { 0x0da9, 0x0e09 }, + { 0x0daa, 0x0e0a }, + { 0x0dab, 0x0e0b }, + { 0x0dac, 0x0e0c }, + { 0x0dad, 0x0e0d }, + { 0x0dae, 0x0e0e }, + { 0x0daf, 0x0e0f }, + { 0x0db0, 0x0e10 }, + { 0x0db1, 0x0e11 }, + { 0x0db2, 0x0e12 }, + { 0x0db3, 0x0e13 }, + { 0x0db4, 0x0e14 }, + { 0x0db5, 0x0e15 }, + { 0x0db6, 0x0e16 }, + { 0x0db7, 0x0e17 }, + { 0x0db8, 0x0e18 }, + { 0x0db9, 0x0e19 }, + { 0x0dba, 0x0e1a }, + { 0x0dbb, 0x0e1b }, + { 0x0dbc, 0x0e1c }, + { 0x0dbd, 0x0e1d }, + { 0x0dbe, 0x0e1e }, + { 0x0dbf, 0x0e1f }, + { 0x0dc0, 0x0e20 }, + { 0x0dc1, 0x0e21 }, + { 0x0dc2, 0x0e22 }, + { 0x0dc3, 0x0e23 }, + { 0x0dc4, 0x0e24 }, + { 0x0dc5, 0x0e25 }, + { 0x0dc6, 0x0e26 }, + { 0x0dc7, 0x0e27 }, + { 0x0dc8, 0x0e28 }, + { 0x0dc9, 0x0e29 }, + { 0x0dca, 0x0e2a }, + { 0x0dcb, 0x0e2b }, + { 0x0dcc, 0x0e2c }, + { 0x0dcd, 0x0e2d }, + { 0x0dce, 0x0e2e }, + { 0x0dcf, 0x0e2f }, + { 0x0dd0, 0x0e30 }, + { 0x0dd1, 0x0e31 }, + { 0x0dd2, 0x0e32 }, + { 0x0dd3, 0x0e33 }, + { 0x0dd4, 0x0e34 }, + { 0x0dd5, 0x0e35 }, + { 0x0dd6, 0x0e36 }, + { 0x0dd7, 0x0e37 }, + { 0x0dd8, 0x0e38 }, + { 0x0dd9, 0x0e39 }, + { 0x0dda, 0x0e3a }, + { 0x0ddf, 0x0e3f }, + { 0x0de0, 0x0e40 }, + { 0x0de1, 0x0e41 }, + { 0x0de2, 0x0e42 }, + { 0x0de3, 0x0e43 }, + { 0x0de4, 0x0e44 }, + { 0x0de5, 0x0e45 }, + { 0x0de6, 0x0e46 }, + { 0x0de7, 0x0e47 }, + { 0x0de8, 0x0e48 }, + { 0x0de9, 0x0e49 }, + { 0x0dea, 0x0e4a }, + { 0x0deb, 0x0e4b }, + { 0x0dec, 0x0e4c }, + { 0x0ded, 0x0e4d }, + { 0x0df0, 0x0e50 }, + { 0x0df1, 0x0e51 }, + { 0x0df2, 0x0e52 }, + { 0x0df3, 0x0e53 }, + { 0x0df4, 0x0e54 }, + { 0x0df5, 0x0e55 }, + { 0x0df6, 0x0e56 }, + { 0x0df7, 0x0e57 }, + { 0x0df8, 0x0e58 }, + { 0x0df9, 0x0e59 }, + { 0x0ea1, 0x3131 }, + { 0x0ea2, 0x3132 }, + { 0x0ea3, 0x3133 }, + { 0x0ea4, 0x3134 }, + { 0x0ea5, 0x3135 }, + { 0x0ea6, 0x3136 }, + { 0x0ea7, 0x3137 }, + { 0x0ea8, 0x3138 }, + { 0x0ea9, 0x3139 }, + { 0x0eaa, 0x313a }, + { 0x0eab, 0x313b }, + { 0x0eac, 0x313c }, + { 0x0ead, 0x313d }, + { 0x0eae, 0x313e }, + { 0x0eaf, 0x313f }, + { 0x0eb0, 0x3140 }, + { 0x0eb1, 0x3141 }, + { 0x0eb2, 0x3142 }, + { 0x0eb3, 0x3143 }, + { 0x0eb4, 0x3144 }, + { 0x0eb5, 0x3145 }, + { 0x0eb6, 0x3146 }, + { 0x0eb7, 0x3147 }, + { 0x0eb8, 0x3148 }, + { 0x0eb9, 0x3149 }, + { 0x0eba, 0x314a }, + { 0x0ebb, 0x314b }, + { 0x0ebc, 0x314c }, + { 0x0ebd, 0x314d }, + { 0x0ebe, 0x314e }, + { 0x0ebf, 0x314f }, + { 0x0ec0, 0x3150 }, + { 0x0ec1, 0x3151 }, + { 0x0ec2, 0x3152 }, + { 0x0ec3, 0x3153 }, + { 0x0ec4, 0x3154 }, + { 0x0ec5, 0x3155 }, + { 0x0ec6, 0x3156 }, + { 0x0ec7, 0x3157 }, + { 0x0ec8, 0x3158 }, + { 0x0ec9, 0x3159 }, + { 0x0eca, 0x315a }, + { 0x0ecb, 0x315b }, + { 0x0ecc, 0x315c }, + { 0x0ecd, 0x315d }, + { 0x0ece, 0x315e }, + { 0x0ecf, 0x315f }, + { 0x0ed0, 0x3160 }, + { 0x0ed1, 0x3161 }, + { 0x0ed2, 0x3162 }, + { 0x0ed3, 0x3163 }, + { 0x0ed4, 0x11a8 }, + { 0x0ed5, 0x11a9 }, + { 0x0ed6, 0x11aa }, + { 0x0ed7, 0x11ab }, + { 0x0ed8, 0x11ac }, + { 0x0ed9, 0x11ad }, + { 0x0eda, 0x11ae }, + { 0x0edb, 0x11af }, + { 0x0edc, 0x11b0 }, + { 0x0edd, 0x11b1 }, + { 0x0ede, 0x11b2 }, + { 0x0edf, 0x11b3 }, + { 0x0ee0, 0x11b4 }, + { 0x0ee1, 0x11b5 }, + { 0x0ee2, 0x11b6 }, + { 0x0ee3, 0x11b7 }, + { 0x0ee4, 0x11b8 }, + { 0x0ee5, 0x11b9 }, + { 0x0ee6, 0x11ba }, + { 0x0ee7, 0x11bb }, + { 0x0ee8, 0x11bc }, + { 0x0ee9, 0x11bd }, + { 0x0eea, 0x11be }, + { 0x0eeb, 0x11bf }, + { 0x0eec, 0x11c0 }, + { 0x0eed, 0x11c1 }, + { 0x0eee, 0x11c2 }, + { 0x0eef, 0x316d }, + { 0x0ef0, 0x3171 }, + { 0x0ef1, 0x3178 }, + { 0x0ef2, 0x317f }, + { 0x0ef3, 0x3181 }, + { 0x0ef4, 0x3184 }, + { 0x0ef5, 0x3186 }, + { 0x0ef6, 0x318d }, + { 0x0ef7, 0x318e }, + { 0x0ef8, 0x11eb }, + { 0x0ef9, 0x11f0 }, + { 0x0efa, 0x11f9 }, + { 0x0eff, 0x20a9 }, + { 0x13a4, 0x20ac }, + { 0x13bc, 0x0152 }, + { 0x13bd, 0x0153 }, + { 0x13be, 0x0178 }, + { 0x20ac, 0x20ac }, + { 0xfe50, '`' }, + { 0xfe51, 0x00b4 }, + { 0xfe52, '^' }, + { 0xfe53, '~' }, + { 0xfe54, 0x00af }, + { 0xfe55, 0x02d8 }, + { 0xfe56, 0x02d9 }, + { 0xfe57, 0x00a8 }, + { 0xfe58, 0x02da }, + { 0xfe59, 0x02dd }, + { 0xfe5a, 0x02c7 }, + { 0xfe5b, 0x00b8 }, + { 0xfe5c, 0x02db }, + { 0xfe5d, 0x037a }, + { 0xfe5e, 0x309b }, + { 0xfe5f, 0x309c }, + { 0xfe63, '/' }, + { 0xfe64, 0x02bc }, + { 0xfe65, 0x02bd }, + { 0xfe66, 0x02f5 }, + { 0xfe67, 0x02f3 }, + { 0xfe68, 0x02cd }, + { 0xfe69, 0xa788 }, + { 0xfe6a, 0x02f7 }, + { 0xfe6e, ',' }, + { 0xfe6f, 0x00a4 }, + { 0xfe80, 'a' }, /* XK_dead_a */ + { 0xfe81, 'A' }, /* XK_dead_A */ + { 0xfe82, 'e' }, /* XK_dead_e */ + { 0xfe83, 'E' }, /* XK_dead_E */ + { 0xfe84, 'i' }, /* XK_dead_i */ + { 0xfe85, 'I' }, /* XK_dead_I */ + { 0xfe86, 'o' }, /* XK_dead_o */ + { 0xfe87, 'O' }, /* XK_dead_O */ + { 0xfe88, 'u' }, /* XK_dead_u */ + { 0xfe89, 'U' }, /* XK_dead_U */ + { 0xfe8a, 0x0259 }, + { 0xfe8b, 0x018f }, + { 0xfe8c, 0x00b5 }, + { 0xfe90, '_' }, + { 0xfe91, 0x02c8 }, + { 0xfe92, 0x02cc }, + { 0xff80 /*XKB_KEY_KP_Space*/, ' ' }, + { 0xff95 /*XKB_KEY_KP_7*/, 0x0037 }, + { 0xff96 /*XKB_KEY_KP_4*/, 0x0034 }, + { 0xff97 /*XKB_KEY_KP_8*/, 0x0038 }, + { 0xff98 /*XKB_KEY_KP_6*/, 0x0036 }, + { 0xff99 /*XKB_KEY_KP_2*/, 0x0032 }, + { 0xff9a /*XKB_KEY_KP_9*/, 0x0039 }, + { 0xff9b /*XKB_KEY_KP_3*/, 0x0033 }, + { 0xff9c /*XKB_KEY_KP_1*/, 0x0031 }, + { 0xff9d /*XKB_KEY_KP_5*/, 0x0035 }, + { 0xff9e /*XKB_KEY_KP_0*/, 0x0030 }, + { 0xffaa /*XKB_KEY_KP_Multiply*/, '*' }, + { 0xffab /*XKB_KEY_KP_Add*/, '+' }, + { 0xffac /*XKB_KEY_KP_Separator*/, ',' }, + { 0xffad /*XKB_KEY_KP_Subtract*/, '-' }, + { 0xffae /*XKB_KEY_KP_Decimal*/, '.' }, + { 0xffaf /*XKB_KEY_KP_Divide*/, '/' }, + { 0xffb0 /*XKB_KEY_KP_0*/, 0x0030 }, + { 0xffb1 /*XKB_KEY_KP_1*/, 0x0031 }, + { 0xffb2 /*XKB_KEY_KP_2*/, 0x0032 }, + { 0xffb3 /*XKB_KEY_KP_3*/, 0x0033 }, + { 0xffb4 /*XKB_KEY_KP_4*/, 0x0034 }, + { 0xffb5 /*XKB_KEY_KP_5*/, 0x0035 }, + { 0xffb6 /*XKB_KEY_KP_6*/, 0x0036 }, + { 0xffb7 /*XKB_KEY_KP_7*/, 0x0037 }, + { 0xffb8 /*XKB_KEY_KP_8*/, 0x0038 }, + { 0xffb9 /*XKB_KEY_KP_9*/, 0x0039 }, + { 0xffbd /*XKB_KEY_KP_Equal*/, '=' } +}; + +_SOKOL_PRIVATE int _sapp_x11_error_handler(Display* display, XErrorEvent* event) { + _SOKOL_UNUSED(display); + _sapp.x11.error_code = event->error_code; + return 0; +} + +_SOKOL_PRIVATE void _sapp_x11_grab_error_handler(void) { + _sapp.x11.error_code = Success; + XSetErrorHandler(_sapp_x11_error_handler); +} + +_SOKOL_PRIVATE void _sapp_x11_release_error_handler(void) { + XSync(_sapp.x11.display, False); + XSetErrorHandler(NULL); +} + +_SOKOL_PRIVATE void _sapp_x11_init_extensions(void) { + _sapp.x11.UTF8_STRING = XInternAtom(_sapp.x11.display, "UTF8_STRING", False); + _sapp.x11.WM_PROTOCOLS = XInternAtom(_sapp.x11.display, "WM_PROTOCOLS", False); + _sapp.x11.WM_DELETE_WINDOW = XInternAtom(_sapp.x11.display, "WM_DELETE_WINDOW", False); + _sapp.x11.WM_STATE = XInternAtom(_sapp.x11.display, "WM_STATE", False); + _sapp.x11.NET_WM_NAME = XInternAtom(_sapp.x11.display, "_NET_WM_NAME", False); + _sapp.x11.NET_WM_ICON_NAME = XInternAtom(_sapp.x11.display, "_NET_WM_ICON_NAME", False); + _sapp.x11.NET_WM_ICON = XInternAtom(_sapp.x11.display, "_NET_WM_ICON", False); + _sapp.x11.NET_WM_STATE = XInternAtom(_sapp.x11.display, "_NET_WM_STATE", False); + _sapp.x11.NET_WM_STATE_FULLSCREEN = XInternAtom(_sapp.x11.display, "_NET_WM_STATE_FULLSCREEN", False); + _sapp.x11.CLIPBOARD = XInternAtom(_sapp.x11.display, "CLIPBOARD", False); + _sapp.x11.TARGETS = XInternAtom(_sapp.x11.display, "TARGETS", False); + if (_sapp.drop.enabled) { + _sapp.x11.xdnd.XdndAware = XInternAtom(_sapp.x11.display, "XdndAware", False); + _sapp.x11.xdnd.XdndEnter = XInternAtom(_sapp.x11.display, "XdndEnter", False); + _sapp.x11.xdnd.XdndPosition = XInternAtom(_sapp.x11.display, "XdndPosition", False); + _sapp.x11.xdnd.XdndStatus = XInternAtom(_sapp.x11.display, "XdndStatus", False); + _sapp.x11.xdnd.XdndActionCopy = XInternAtom(_sapp.x11.display, "XdndActionCopy", False); + _sapp.x11.xdnd.XdndDrop = XInternAtom(_sapp.x11.display, "XdndDrop", False); + _sapp.x11.xdnd.XdndFinished = XInternAtom(_sapp.x11.display, "XdndFinished", False); + _sapp.x11.xdnd.XdndSelection = XInternAtom(_sapp.x11.display, "XdndSelection", False); + _sapp.x11.xdnd.XdndTypeList = XInternAtom(_sapp.x11.display, "XdndTypeList", False); + _sapp.x11.xdnd.text_uri_list = XInternAtom(_sapp.x11.display, "text/uri-list", False); + } + + /* check Xi extension for raw mouse input */ + if (XQueryExtension(_sapp.x11.display, "XInputExtension", &_sapp.x11.xi.major_opcode, &_sapp.x11.xi.event_base, &_sapp.x11.xi.error_base)) { + _sapp.x11.xi.major = 2; + _sapp.x11.xi.minor = 0; + if (XIQueryVersion(_sapp.x11.display, &_sapp.x11.xi.major, &_sapp.x11.xi.minor) == Success) { + _sapp.x11.xi.available = true; + } + } +} + +// translate the X11 KeySyms for a key to sokol-app key code +// NOTE: this is only used as a fallback, in case the XBK method fails +// it is layout-dependent and will fail partially on most non-US layouts. +// +_SOKOL_PRIVATE sapp_keycode _sapp_x11_translate_keysyms(const KeySym* keysyms, int width) { + if (width > 1) { + switch (keysyms[1]) { + case XK_KP_0: return SAPP_KEYCODE_KP_0; + case XK_KP_1: return SAPP_KEYCODE_KP_1; + case XK_KP_2: return SAPP_KEYCODE_KP_2; + case XK_KP_3: return SAPP_KEYCODE_KP_3; + case XK_KP_4: return SAPP_KEYCODE_KP_4; + case XK_KP_5: return SAPP_KEYCODE_KP_5; + case XK_KP_6: return SAPP_KEYCODE_KP_6; + case XK_KP_7: return SAPP_KEYCODE_KP_7; + case XK_KP_8: return SAPP_KEYCODE_KP_8; + case XK_KP_9: return SAPP_KEYCODE_KP_9; + case XK_KP_Separator: + case XK_KP_Decimal: return SAPP_KEYCODE_KP_DECIMAL; + case XK_KP_Equal: return SAPP_KEYCODE_KP_EQUAL; + case XK_KP_Enter: return SAPP_KEYCODE_KP_ENTER; + default: break; + } + } + + switch (keysyms[0]) { + case XK_Escape: return SAPP_KEYCODE_ESCAPE; + case XK_Tab: return SAPP_KEYCODE_TAB; + case XK_Shift_L: return SAPP_KEYCODE_LEFT_SHIFT; + case XK_Shift_R: return SAPP_KEYCODE_RIGHT_SHIFT; + case XK_Control_L: return SAPP_KEYCODE_LEFT_CONTROL; + case XK_Control_R: return SAPP_KEYCODE_RIGHT_CONTROL; + case XK_Meta_L: + case XK_Alt_L: return SAPP_KEYCODE_LEFT_ALT; + case XK_Mode_switch: // Mapped to Alt_R on many keyboards + case XK_ISO_Level3_Shift: // AltGr on at least some machines + case XK_Meta_R: + case XK_Alt_R: return SAPP_KEYCODE_RIGHT_ALT; + case XK_Super_L: return SAPP_KEYCODE_LEFT_SUPER; + case XK_Super_R: return SAPP_KEYCODE_RIGHT_SUPER; + case XK_Menu: return SAPP_KEYCODE_MENU; + case XK_Num_Lock: return SAPP_KEYCODE_NUM_LOCK; + case XK_Caps_Lock: return SAPP_KEYCODE_CAPS_LOCK; + case XK_Print: return SAPP_KEYCODE_PRINT_SCREEN; + case XK_Scroll_Lock: return SAPP_KEYCODE_SCROLL_LOCK; + case XK_Pause: return SAPP_KEYCODE_PAUSE; + case XK_Delete: return SAPP_KEYCODE_DELETE; + case XK_BackSpace: return SAPP_KEYCODE_BACKSPACE; + case XK_Return: return SAPP_KEYCODE_ENTER; + case XK_Home: return SAPP_KEYCODE_HOME; + case XK_End: return SAPP_KEYCODE_END; + case XK_Page_Up: return SAPP_KEYCODE_PAGE_UP; + case XK_Page_Down: return SAPP_KEYCODE_PAGE_DOWN; + case XK_Insert: return SAPP_KEYCODE_INSERT; + case XK_Left: return SAPP_KEYCODE_LEFT; + case XK_Right: return SAPP_KEYCODE_RIGHT; + case XK_Down: return SAPP_KEYCODE_DOWN; + case XK_Up: return SAPP_KEYCODE_UP; + case XK_F1: return SAPP_KEYCODE_F1; + case XK_F2: return SAPP_KEYCODE_F2; + case XK_F3: return SAPP_KEYCODE_F3; + case XK_F4: return SAPP_KEYCODE_F4; + case XK_F5: return SAPP_KEYCODE_F5; + case XK_F6: return SAPP_KEYCODE_F6; + case XK_F7: return SAPP_KEYCODE_F7; + case XK_F8: return SAPP_KEYCODE_F8; + case XK_F9: return SAPP_KEYCODE_F9; + case XK_F10: return SAPP_KEYCODE_F10; + case XK_F11: return SAPP_KEYCODE_F11; + case XK_F12: return SAPP_KEYCODE_F12; + case XK_F13: return SAPP_KEYCODE_F13; + case XK_F14: return SAPP_KEYCODE_F14; + case XK_F15: return SAPP_KEYCODE_F15; + case XK_F16: return SAPP_KEYCODE_F16; + case XK_F17: return SAPP_KEYCODE_F17; + case XK_F18: return SAPP_KEYCODE_F18; + case XK_F19: return SAPP_KEYCODE_F19; + case XK_F20: return SAPP_KEYCODE_F20; + case XK_F21: return SAPP_KEYCODE_F21; + case XK_F22: return SAPP_KEYCODE_F22; + case XK_F23: return SAPP_KEYCODE_F23; + case XK_F24: return SAPP_KEYCODE_F24; + case XK_F25: return SAPP_KEYCODE_F25; + + // numeric keypad + case XK_KP_Divide: return SAPP_KEYCODE_KP_DIVIDE; + case XK_KP_Multiply: return SAPP_KEYCODE_KP_MULTIPLY; + case XK_KP_Subtract: return SAPP_KEYCODE_KP_SUBTRACT; + case XK_KP_Add: return SAPP_KEYCODE_KP_ADD; + + // these should have been detected in secondary keysym test above! + case XK_KP_Insert: return SAPP_KEYCODE_KP_0; + case XK_KP_End: return SAPP_KEYCODE_KP_1; + case XK_KP_Down: return SAPP_KEYCODE_KP_2; + case XK_KP_Page_Down: return SAPP_KEYCODE_KP_3; + case XK_KP_Left: return SAPP_KEYCODE_KP_4; + case XK_KP_Right: return SAPP_KEYCODE_KP_6; + case XK_KP_Home: return SAPP_KEYCODE_KP_7; + case XK_KP_Up: return SAPP_KEYCODE_KP_8; + case XK_KP_Page_Up: return SAPP_KEYCODE_KP_9; + case XK_KP_Delete: return SAPP_KEYCODE_KP_DECIMAL; + case XK_KP_Equal: return SAPP_KEYCODE_KP_EQUAL; + case XK_KP_Enter: return SAPP_KEYCODE_KP_ENTER; + + // last resort: Check for printable keys (should not happen if the XKB + // extension is available). This will give a layout dependent mapping + // (which is wrong, and we may miss some keys, especially on non-US + // keyboards), but it's better than nothing... + case XK_a: return SAPP_KEYCODE_A; + case XK_b: return SAPP_KEYCODE_B; + case XK_c: return SAPP_KEYCODE_C; + case XK_d: return SAPP_KEYCODE_D; + case XK_e: return SAPP_KEYCODE_E; + case XK_f: return SAPP_KEYCODE_F; + case XK_g: return SAPP_KEYCODE_G; + case XK_h: return SAPP_KEYCODE_H; + case XK_i: return SAPP_KEYCODE_I; + case XK_j: return SAPP_KEYCODE_J; + case XK_k: return SAPP_KEYCODE_K; + case XK_l: return SAPP_KEYCODE_L; + case XK_m: return SAPP_KEYCODE_M; + case XK_n: return SAPP_KEYCODE_N; + case XK_o: return SAPP_KEYCODE_O; + case XK_p: return SAPP_KEYCODE_P; + case XK_q: return SAPP_KEYCODE_Q; + case XK_r: return SAPP_KEYCODE_R; + case XK_s: return SAPP_KEYCODE_S; + case XK_t: return SAPP_KEYCODE_T; + case XK_u: return SAPP_KEYCODE_U; + case XK_v: return SAPP_KEYCODE_V; + case XK_w: return SAPP_KEYCODE_W; + case XK_x: return SAPP_KEYCODE_X; + case XK_y: return SAPP_KEYCODE_Y; + case XK_z: return SAPP_KEYCODE_Z; + case XK_1: return SAPP_KEYCODE_1; + case XK_2: return SAPP_KEYCODE_2; + case XK_3: return SAPP_KEYCODE_3; + case XK_4: return SAPP_KEYCODE_4; + case XK_5: return SAPP_KEYCODE_5; + case XK_6: return SAPP_KEYCODE_6; + case XK_7: return SAPP_KEYCODE_7; + case XK_8: return SAPP_KEYCODE_8; + case XK_9: return SAPP_KEYCODE_9; + case XK_0: return SAPP_KEYCODE_0; + case XK_space: return SAPP_KEYCODE_SPACE; + case XK_minus: return SAPP_KEYCODE_MINUS; + case XK_equal: return SAPP_KEYCODE_EQUAL; + case XK_bracketleft: return SAPP_KEYCODE_LEFT_BRACKET; + case XK_bracketright: return SAPP_KEYCODE_RIGHT_BRACKET; + case XK_backslash: return SAPP_KEYCODE_BACKSLASH; + case XK_semicolon: return SAPP_KEYCODE_SEMICOLON; + case XK_apostrophe: return SAPP_KEYCODE_APOSTROPHE; + case XK_grave: return SAPP_KEYCODE_GRAVE_ACCENT; + case XK_comma: return SAPP_KEYCODE_COMMA; + case XK_period: return SAPP_KEYCODE_PERIOD; + case XK_slash: return SAPP_KEYCODE_SLASH; + case XK_less: return SAPP_KEYCODE_WORLD_1; // At least in some layouts... + default: break; + } + + // no matching translation was found + return SAPP_KEYCODE_INVALID; +} + + +// setup dynamic keycode/scancode mapping tables, this is required +// for getting layout-independent keycodes on X11. +// +// see GLFW x11_init.c/createKeyTables() +_SOKOL_PRIVATE void _sapp_x11_init_keytable(void) { + for (int i = 0; i < SAPP_MAX_KEYCODES; i++) { + _sapp.keycodes[i] = SAPP_KEYCODE_INVALID; + } + // use XKB to determine physical key locations independently of the current keyboard layout + XkbDescPtr desc = XkbGetMap(_sapp.x11.display, 0, XkbUseCoreKbd); + SOKOL_ASSERT(desc); + XkbGetNames(_sapp.x11.display, XkbKeyNamesMask | XkbKeyAliasesMask, desc); + + const int scancode_min = desc->min_key_code; + const int scancode_max = desc->max_key_code; + + const struct { sapp_keycode key; const char* name; } keymap[] = { + { SAPP_KEYCODE_GRAVE_ACCENT, "TLDE" }, + { SAPP_KEYCODE_1, "AE01" }, + { SAPP_KEYCODE_2, "AE02" }, + { SAPP_KEYCODE_3, "AE03" }, + { SAPP_KEYCODE_4, "AE04" }, + { SAPP_KEYCODE_5, "AE05" }, + { SAPP_KEYCODE_6, "AE06" }, + { SAPP_KEYCODE_7, "AE07" }, + { SAPP_KEYCODE_8, "AE08" }, + { SAPP_KEYCODE_9, "AE09" }, + { SAPP_KEYCODE_0, "AE10" }, + { SAPP_KEYCODE_MINUS, "AE11" }, + { SAPP_KEYCODE_EQUAL, "AE12" }, + { SAPP_KEYCODE_Q, "AD01" }, + { SAPP_KEYCODE_W, "AD02" }, + { SAPP_KEYCODE_E, "AD03" }, + { SAPP_KEYCODE_R, "AD04" }, + { SAPP_KEYCODE_T, "AD05" }, + { SAPP_KEYCODE_Y, "AD06" }, + { SAPP_KEYCODE_U, "AD07" }, + { SAPP_KEYCODE_I, "AD08" }, + { SAPP_KEYCODE_O, "AD09" }, + { SAPP_KEYCODE_P, "AD10" }, + { SAPP_KEYCODE_LEFT_BRACKET, "AD11" }, + { SAPP_KEYCODE_RIGHT_BRACKET, "AD12" }, + { SAPP_KEYCODE_A, "AC01" }, + { SAPP_KEYCODE_S, "AC02" }, + { SAPP_KEYCODE_D, "AC03" }, + { SAPP_KEYCODE_F, "AC04" }, + { SAPP_KEYCODE_G, "AC05" }, + { SAPP_KEYCODE_H, "AC06" }, + { SAPP_KEYCODE_J, "AC07" }, + { SAPP_KEYCODE_K, "AC08" }, + { SAPP_KEYCODE_L, "AC09" }, + { SAPP_KEYCODE_SEMICOLON, "AC10" }, + { SAPP_KEYCODE_APOSTROPHE, "AC11" }, + { SAPP_KEYCODE_Z, "AB01" }, + { SAPP_KEYCODE_X, "AB02" }, + { SAPP_KEYCODE_C, "AB03" }, + { SAPP_KEYCODE_V, "AB04" }, + { SAPP_KEYCODE_B, "AB05" }, + { SAPP_KEYCODE_N, "AB06" }, + { SAPP_KEYCODE_M, "AB07" }, + { SAPP_KEYCODE_COMMA, "AB08" }, + { SAPP_KEYCODE_PERIOD, "AB09" }, + { SAPP_KEYCODE_SLASH, "AB10" }, + { SAPP_KEYCODE_BACKSLASH, "BKSL" }, + { SAPP_KEYCODE_WORLD_1, "LSGT" }, + { SAPP_KEYCODE_SPACE, "SPCE" }, + { SAPP_KEYCODE_ESCAPE, "ESC" }, + { SAPP_KEYCODE_ENTER, "RTRN" }, + { SAPP_KEYCODE_TAB, "TAB" }, + { SAPP_KEYCODE_BACKSPACE, "BKSP" }, + { SAPP_KEYCODE_INSERT, "INS" }, + { SAPP_KEYCODE_DELETE, "DELE" }, + { SAPP_KEYCODE_RIGHT, "RGHT" }, + { SAPP_KEYCODE_LEFT, "LEFT" }, + { SAPP_KEYCODE_DOWN, "DOWN" }, + { SAPP_KEYCODE_UP, "UP" }, + { SAPP_KEYCODE_PAGE_UP, "PGUP" }, + { SAPP_KEYCODE_PAGE_DOWN, "PGDN" }, + { SAPP_KEYCODE_HOME, "HOME" }, + { SAPP_KEYCODE_END, "END" }, + { SAPP_KEYCODE_CAPS_LOCK, "CAPS" }, + { SAPP_KEYCODE_SCROLL_LOCK, "SCLK" }, + { SAPP_KEYCODE_NUM_LOCK, "NMLK" }, + { SAPP_KEYCODE_PRINT_SCREEN, "PRSC" }, + { SAPP_KEYCODE_PAUSE, "PAUS" }, + { SAPP_KEYCODE_F1, "FK01" }, + { SAPP_KEYCODE_F2, "FK02" }, + { SAPP_KEYCODE_F3, "FK03" }, + { SAPP_KEYCODE_F4, "FK04" }, + { SAPP_KEYCODE_F5, "FK05" }, + { SAPP_KEYCODE_F6, "FK06" }, + { SAPP_KEYCODE_F7, "FK07" }, + { SAPP_KEYCODE_F8, "FK08" }, + { SAPP_KEYCODE_F9, "FK09" }, + { SAPP_KEYCODE_F10, "FK10" }, + { SAPP_KEYCODE_F11, "FK11" }, + { SAPP_KEYCODE_F12, "FK12" }, + { SAPP_KEYCODE_F13, "FK13" }, + { SAPP_KEYCODE_F14, "FK14" }, + { SAPP_KEYCODE_F15, "FK15" }, + { SAPP_KEYCODE_F16, "FK16" }, + { SAPP_KEYCODE_F17, "FK17" }, + { SAPP_KEYCODE_F18, "FK18" }, + { SAPP_KEYCODE_F19, "FK19" }, + { SAPP_KEYCODE_F20, "FK20" }, + { SAPP_KEYCODE_F21, "FK21" }, + { SAPP_KEYCODE_F22, "FK22" }, + { SAPP_KEYCODE_F23, "FK23" }, + { SAPP_KEYCODE_F24, "FK24" }, + { SAPP_KEYCODE_F25, "FK25" }, + { SAPP_KEYCODE_KP_0, "KP0" }, + { SAPP_KEYCODE_KP_1, "KP1" }, + { SAPP_KEYCODE_KP_2, "KP2" }, + { SAPP_KEYCODE_KP_3, "KP3" }, + { SAPP_KEYCODE_KP_4, "KP4" }, + { SAPP_KEYCODE_KP_5, "KP5" }, + { SAPP_KEYCODE_KP_6, "KP6" }, + { SAPP_KEYCODE_KP_7, "KP7" }, + { SAPP_KEYCODE_KP_8, "KP8" }, + { SAPP_KEYCODE_KP_9, "KP9" }, + { SAPP_KEYCODE_KP_DECIMAL, "KPDL" }, + { SAPP_KEYCODE_KP_DIVIDE, "KPDV" }, + { SAPP_KEYCODE_KP_MULTIPLY, "KPMU" }, + { SAPP_KEYCODE_KP_SUBTRACT, "KPSU" }, + { SAPP_KEYCODE_KP_ADD, "KPAD" }, + { SAPP_KEYCODE_KP_ENTER, "KPEN" }, + { SAPP_KEYCODE_KP_EQUAL, "KPEQ" }, + { SAPP_KEYCODE_LEFT_SHIFT, "LFSH" }, + { SAPP_KEYCODE_LEFT_CONTROL, "LCTL" }, + { SAPP_KEYCODE_LEFT_ALT, "LALT" }, + { SAPP_KEYCODE_LEFT_SUPER, "LWIN" }, + { SAPP_KEYCODE_RIGHT_SHIFT, "RTSH" }, + { SAPP_KEYCODE_RIGHT_CONTROL, "RCTL" }, + { SAPP_KEYCODE_RIGHT_ALT, "RALT" }, + { SAPP_KEYCODE_RIGHT_ALT, "LVL3" }, + { SAPP_KEYCODE_RIGHT_ALT, "MDSW" }, + { SAPP_KEYCODE_RIGHT_SUPER, "RWIN" }, + { SAPP_KEYCODE_MENU, "MENU" } + }; + const int num_keymap_items = (int)(sizeof(keymap) / sizeof(keymap[0])); + + // find X11 keycode to sokol-app key code mapping + for (int scancode = scancode_min; scancode <= scancode_max; scancode++) { + sapp_keycode key = SAPP_KEYCODE_INVALID; + for (int i = 0; i < num_keymap_items; i++) { + if (strncmp(desc->names->keys[scancode].name, keymap[i].name, XkbKeyNameLength) == 0) { + key = keymap[i].key; + break; + } + } + + // fall back to key aliases in case the key name did not match + for (int i = 0; i < desc->names->num_key_aliases; i++) { + if (key != SAPP_KEYCODE_INVALID) { + break; + } + if (strncmp(desc->names->key_aliases[i].real, desc->names->keys[scancode].name, XkbKeyNameLength) != 0) { + continue; + } + for (int j = 0; j < num_keymap_items; j++) { + if (strncmp(desc->names->key_aliases[i].alias, keymap[i].name, XkbKeyNameLength) == 0) { + key = keymap[i].key; + break; + } + } + } + _sapp.keycodes[scancode] = key; + } + XkbFreeNames(desc, XkbKeyNamesMask, True); + XkbFreeKeyboard(desc, 0, True); + + int width = 0; + KeySym* keysyms = XGetKeyboardMapping(_sapp.x11.display, scancode_min, scancode_max - scancode_min + 1, &width); + for (int scancode = scancode_min; scancode <= scancode_max; scancode++) { + // translate untranslated key codes using the traditional X11 KeySym lookups + if (_sapp.keycodes[scancode] == SAPP_KEYCODE_INVALID) { + const size_t base = (size_t)((scancode - scancode_min) * width); + _sapp.keycodes[scancode] = _sapp_x11_translate_keysyms(&keysyms[base], width); + } + } + XFree(keysyms); +} + +_SOKOL_PRIVATE void _sapp_x11_query_system_dpi(void) { + /* from GLFW: + + NOTE: Default to the display-wide DPI as we don't currently have a policy + for which monitor a window is considered to be on + + _sapp.x11.dpi = DisplayWidth(_sapp.x11.display, _sapp.x11.screen) * + 25.4f / DisplayWidthMM(_sapp.x11.display, _sapp.x11.screen); + + NOTE: Basing the scale on Xft.dpi where available should provide the most + consistent user experience (matches Qt, Gtk, etc), although not + always the most accurate one + */ + bool dpi_ok = false; + char* rms = XResourceManagerString(_sapp.x11.display); + if (rms) { + XrmDatabase db = XrmGetStringDatabase(rms); + if (db) { + XrmValue value; + char* type = NULL; + if (XrmGetResource(db, "Xft.dpi", "Xft.Dpi", &type, &value)) { + if (type && strcmp(type, "String") == 0) { + _sapp.x11.dpi = atof(value.addr); + dpi_ok = true; + } + } + XrmDestroyDatabase(db); + } + } + // fallback if querying DPI had failed: assume the standard DPI 96.0f + if (!dpi_ok) { + _sapp.x11.dpi = 96.0f; + _SAPP_WARN(LINUX_X11_QUERY_SYSTEM_DPI_FAILED); + } +} + +#if defined(_SAPP_GLX) + +_SOKOL_PRIVATE bool _sapp_glx_has_ext(const char* ext, const char* extensions) { + SOKOL_ASSERT(ext); + const char* start = extensions; + while (true) { + const char* where = strstr(start, ext); + if (!where) { + return false; + } + const char* terminator = where + strlen(ext); + if ((where == start) || (*(where - 1) == ' ')) { + if (*terminator == ' ' || *terminator == '\0') { + break; + } + } + start = terminator; + } + return true; +} + +_SOKOL_PRIVATE bool _sapp_glx_extsupported(const char* ext, const char* extensions) { + if (extensions) { + return _sapp_glx_has_ext(ext, extensions); + } else { + return false; + } +} + +_SOKOL_PRIVATE void* _sapp_glx_getprocaddr(const char* procname) +{ + if (_sapp.glx.GetProcAddress) { + return (void*) _sapp.glx.GetProcAddress(procname); + } else if (_sapp.glx.GetProcAddressARB) { + return (void*) _sapp.glx.GetProcAddressARB(procname); + } else { + return dlsym(_sapp.glx.libgl, procname); + } +} + +_SOKOL_PRIVATE void _sapp_glx_init(void) { + const char* sonames[] = { "libGL.so.1", "libGL.so", 0 }; + for (int i = 0; sonames[i]; i++) { + _sapp.glx.libgl = dlopen(sonames[i], RTLD_LAZY|RTLD_GLOBAL); + if (_sapp.glx.libgl) { + break; + } + } + if (!_sapp.glx.libgl) { + _SAPP_PANIC(LINUX_GLX_LOAD_LIBGL_FAILED); + } + _sapp.glx.GetFBConfigs = (PFNGLXGETFBCONFIGSPROC) dlsym(_sapp.glx.libgl, "glXGetFBConfigs"); + _sapp.glx.GetFBConfigAttrib = (PFNGLXGETFBCONFIGATTRIBPROC) dlsym(_sapp.glx.libgl, "glXGetFBConfigAttrib"); + _sapp.glx.GetClientString = (PFNGLXGETCLIENTSTRINGPROC) dlsym(_sapp.glx.libgl, "glXGetClientString"); + _sapp.glx.QueryExtension = (PFNGLXQUERYEXTENSIONPROC) dlsym(_sapp.glx.libgl, "glXQueryExtension"); + _sapp.glx.QueryVersion = (PFNGLXQUERYVERSIONPROC) dlsym(_sapp.glx.libgl, "glXQueryVersion"); + _sapp.glx.DestroyContext = (PFNGLXDESTROYCONTEXTPROC) dlsym(_sapp.glx.libgl, "glXDestroyContext"); + _sapp.glx.MakeCurrent = (PFNGLXMAKECURRENTPROC) dlsym(_sapp.glx.libgl, "glXMakeCurrent"); + _sapp.glx.SwapBuffers = (PFNGLXSWAPBUFFERSPROC) dlsym(_sapp.glx.libgl, "glXSwapBuffers"); + _sapp.glx.QueryExtensionsString = (PFNGLXQUERYEXTENSIONSSTRINGPROC) dlsym(_sapp.glx.libgl, "glXQueryExtensionsString"); + _sapp.glx.CreateWindow = (PFNGLXCREATEWINDOWPROC) dlsym(_sapp.glx.libgl, "glXCreateWindow"); + _sapp.glx.DestroyWindow = (PFNGLXDESTROYWINDOWPROC) dlsym(_sapp.glx.libgl, "glXDestroyWindow"); + _sapp.glx.GetProcAddress = (PFNGLXGETPROCADDRESSPROC) dlsym(_sapp.glx.libgl, "glXGetProcAddress"); + _sapp.glx.GetProcAddressARB = (PFNGLXGETPROCADDRESSPROC) dlsym(_sapp.glx.libgl, "glXGetProcAddressARB"); + _sapp.glx.GetVisualFromFBConfig = (PFNGLXGETVISUALFROMFBCONFIGPROC) dlsym(_sapp.glx.libgl, "glXGetVisualFromFBConfig"); + if (!_sapp.glx.GetFBConfigs || + !_sapp.glx.GetFBConfigAttrib || + !_sapp.glx.GetClientString || + !_sapp.glx.QueryExtension || + !_sapp.glx.QueryVersion || + !_sapp.glx.DestroyContext || + !_sapp.glx.MakeCurrent || + !_sapp.glx.SwapBuffers || + !_sapp.glx.QueryExtensionsString || + !_sapp.glx.CreateWindow || + !_sapp.glx.DestroyWindow || + !_sapp.glx.GetProcAddress || + !_sapp.glx.GetProcAddressARB || + !_sapp.glx.GetVisualFromFBConfig) + { + _SAPP_PANIC(LINUX_GLX_LOAD_ENTRY_POINTS_FAILED); + } + + if (!_sapp.glx.QueryExtension(_sapp.x11.display, &_sapp.glx.error_base, &_sapp.glx.event_base)) { + _SAPP_PANIC(LINUX_GLX_EXTENSION_NOT_FOUND); + } + if (!_sapp.glx.QueryVersion(_sapp.x11.display, &_sapp.glx.major, &_sapp.glx.minor)) { + _SAPP_PANIC(LINUX_GLX_QUERY_VERSION_FAILED); + } + if (_sapp.glx.major == 1 && _sapp.glx.minor < 3) { + _SAPP_PANIC(LINUX_GLX_VERSION_TOO_LOW); + } + const char* exts = _sapp.glx.QueryExtensionsString(_sapp.x11.display, _sapp.x11.screen); + if (_sapp_glx_extsupported("GLX_EXT_swap_control", exts)) { + _sapp.glx.SwapIntervalEXT = (PFNGLXSWAPINTERVALEXTPROC) _sapp_glx_getprocaddr("glXSwapIntervalEXT"); + _sapp.glx.EXT_swap_control = 0 != _sapp.glx.SwapIntervalEXT; + } + if (_sapp_glx_extsupported("GLX_MESA_swap_control", exts)) { + _sapp.glx.SwapIntervalMESA = (PFNGLXSWAPINTERVALMESAPROC) _sapp_glx_getprocaddr("glXSwapIntervalMESA"); + _sapp.glx.MESA_swap_control = 0 != _sapp.glx.SwapIntervalMESA; + } + _sapp.glx.ARB_multisample = _sapp_glx_extsupported("GLX_ARB_multisample", exts); + if (_sapp_glx_extsupported("GLX_ARB_create_context", exts)) { + _sapp.glx.CreateContextAttribsARB = (PFNGLXCREATECONTEXTATTRIBSARBPROC) _sapp_glx_getprocaddr("glXCreateContextAttribsARB"); + _sapp.glx.ARB_create_context = 0 != _sapp.glx.CreateContextAttribsARB; + } + _sapp.glx.ARB_create_context_profile = _sapp_glx_extsupported("GLX_ARB_create_context_profile", exts); +} + +_SOKOL_PRIVATE int _sapp_glx_attrib(GLXFBConfig fbconfig, int attrib) { + int value; + _sapp.glx.GetFBConfigAttrib(_sapp.x11.display, fbconfig, attrib, &value); + return value; +} + +_SOKOL_PRIVATE GLXFBConfig _sapp_glx_choosefbconfig(void) { + GLXFBConfig* native_configs; + _sapp_gl_fbconfig* usable_configs; + const _sapp_gl_fbconfig* closest; + int i, native_count, usable_count; + const char* vendor; + bool trust_window_bit = true; + + /* HACK: This is a (hopefully temporary) workaround for Chromium + (VirtualBox GL) not setting the window bit on any GLXFBConfigs + */ + vendor = _sapp.glx.GetClientString(_sapp.x11.display, GLX_VENDOR); + if (vendor && strcmp(vendor, "Chromium") == 0) { + trust_window_bit = false; + } + + native_configs = _sapp.glx.GetFBConfigs(_sapp.x11.display, _sapp.x11.screen, &native_count); + if (!native_configs || !native_count) { + _SAPP_PANIC(LINUX_GLX_NO_GLXFBCONFIGS); + } + + usable_configs = (_sapp_gl_fbconfig*) _sapp_malloc_clear((size_t)native_count * sizeof(_sapp_gl_fbconfig)); + usable_count = 0; + for (i = 0; i < native_count; i++) { + const GLXFBConfig n = native_configs[i]; + _sapp_gl_fbconfig* u = usable_configs + usable_count; + _sapp_gl_init_fbconfig(u); + + /* Only consider RGBA GLXFBConfigs */ + if (0 == (_sapp_glx_attrib(n, GLX_RENDER_TYPE) & GLX_RGBA_BIT)) { + continue; + } + /* Only consider window GLXFBConfigs */ + if (0 == (_sapp_glx_attrib(n, GLX_DRAWABLE_TYPE) & GLX_WINDOW_BIT)) { + if (trust_window_bit) { + continue; + } + } + u->red_bits = _sapp_glx_attrib(n, GLX_RED_SIZE); + u->green_bits = _sapp_glx_attrib(n, GLX_GREEN_SIZE); + u->blue_bits = _sapp_glx_attrib(n, GLX_BLUE_SIZE); + u->alpha_bits = _sapp_glx_attrib(n, GLX_ALPHA_SIZE); + u->depth_bits = _sapp_glx_attrib(n, GLX_DEPTH_SIZE); + u->stencil_bits = _sapp_glx_attrib(n, GLX_STENCIL_SIZE); + if (_sapp_glx_attrib(n, GLX_DOUBLEBUFFER)) { + u->doublebuffer = true; + } + if (_sapp.glx.ARB_multisample) { + u->samples = _sapp_glx_attrib(n, GLX_SAMPLES); + } + u->handle = (uintptr_t) n; + usable_count++; + } + _sapp_gl_fbconfig desired; + _sapp_gl_init_fbconfig(&desired); + desired.red_bits = 8; + desired.green_bits = 8; + desired.blue_bits = 8; + desired.alpha_bits = 8; + desired.depth_bits = 24; + desired.stencil_bits = 8; + desired.doublebuffer = true; + desired.samples = _sapp.sample_count > 1 ? _sapp.sample_count : 0; + closest = _sapp_gl_choose_fbconfig(&desired, usable_configs, usable_count); + GLXFBConfig result = 0; + if (closest) { + result = (GLXFBConfig) closest->handle; + } + XFree(native_configs); + _sapp_free(usable_configs); + return result; +} + +_SOKOL_PRIVATE void _sapp_glx_choose_visual(Visual** visual, int* depth) { + GLXFBConfig native = _sapp_glx_choosefbconfig(); + if (0 == native) { + _SAPP_PANIC(LINUX_GLX_NO_SUITABLE_GLXFBCONFIG); + } + XVisualInfo* result = _sapp.glx.GetVisualFromFBConfig(_sapp.x11.display, native); + if (!result) { + _SAPP_PANIC(LINUX_GLX_GET_VISUAL_FROM_FBCONFIG_FAILED); + } + *visual = result->visual; + *depth = result->depth; + XFree(result); +} + +_SOKOL_PRIVATE void _sapp_glx_make_current(void) { + _sapp.glx.MakeCurrent(_sapp.x11.display, _sapp.glx.window, _sapp.glx.ctx); + glGetIntegerv(GL_FRAMEBUFFER_BINDING, (GLint*)&_sapp.gl.framebuffer); +} + +_SOKOL_PRIVATE void _sapp_glx_create_context(void) { + GLXFBConfig native = _sapp_glx_choosefbconfig(); + if (0 == native){ + _SAPP_PANIC(LINUX_GLX_NO_SUITABLE_GLXFBCONFIG); + } + if (!(_sapp.glx.ARB_create_context && _sapp.glx.ARB_create_context_profile)) { + _SAPP_PANIC(LINUX_GLX_REQUIRED_EXTENSIONS_MISSING); + } + _sapp_x11_grab_error_handler(); + const int attribs[] = { + GLX_CONTEXT_MAJOR_VERSION_ARB, _sapp.desc.gl_major_version, + GLX_CONTEXT_MINOR_VERSION_ARB, _sapp.desc.gl_minor_version, + GLX_CONTEXT_PROFILE_MASK_ARB, GLX_CONTEXT_CORE_PROFILE_BIT_ARB, + GLX_CONTEXT_FLAGS_ARB, GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB, + 0, 0 + }; + _sapp.glx.ctx = _sapp.glx.CreateContextAttribsARB(_sapp.x11.display, native, NULL, True, attribs); + if (!_sapp.glx.ctx) { + _SAPP_PANIC(LINUX_GLX_CREATE_CONTEXT_FAILED); + } + _sapp_x11_release_error_handler(); + _sapp.glx.window = _sapp.glx.CreateWindow(_sapp.x11.display, native, _sapp.x11.window, NULL); + if (!_sapp.glx.window) { + _SAPP_PANIC(LINUX_GLX_CREATE_WINDOW_FAILED); + } + _sapp_glx_make_current(); +} + +_SOKOL_PRIVATE void _sapp_glx_destroy_context(void) { + if (_sapp.glx.window) { + _sapp.glx.DestroyWindow(_sapp.x11.display, _sapp.glx.window); + _sapp.glx.window = 0; + } + if (_sapp.glx.ctx) { + _sapp.glx.DestroyContext(_sapp.x11.display, _sapp.glx.ctx); + _sapp.glx.ctx = 0; + } +} + +_SOKOL_PRIVATE void _sapp_glx_swap_buffers(void) { + _sapp.glx.SwapBuffers(_sapp.x11.display, _sapp.glx.window); +} + +_SOKOL_PRIVATE void _sapp_glx_swapinterval(int interval) { + if (_sapp.glx.EXT_swap_control) { + _sapp.glx.SwapIntervalEXT(_sapp.x11.display, _sapp.glx.window, interval); + } else if (_sapp.glx.MESA_swap_control) { + _sapp.glx.SwapIntervalMESA(interval); + } +} + +#endif // _SAPP_GLX + +_SOKOL_PRIVATE void _sapp_x11_send_event(Atom type, int a, int b, int c, int d, int e) { + XEvent event; + _sapp_clear(&event, sizeof(event)); + + event.type = ClientMessage; + event.xclient.window = _sapp.x11.window; + event.xclient.format = 32; + event.xclient.message_type = type; + event.xclient.data.l[0] = a; + event.xclient.data.l[1] = b; + event.xclient.data.l[2] = c; + event.xclient.data.l[3] = d; + event.xclient.data.l[4] = e; + + XSendEvent(_sapp.x11.display, _sapp.x11.root, + False, + SubstructureNotifyMask | SubstructureRedirectMask, + &event); +} + +_SOKOL_PRIVATE bool _sapp_x11_wait_for_event(int event_type, double timeout_sec, XEvent* out_event) { + _sapp_timestamp_t ts; + _sapp_timestamp_init(&ts); + while (!XCheckTypedWindowEvent(_sapp.x11.display, _sapp.x11.window, event_type, out_event)) { + struct pollfd fd = { ConnectionNumber(_sapp.x11.display), POLLIN, 0 }; + poll(&fd, 1, timeout_sec * 1000); + if (_sapp_timestamp_now(&ts) > timeout_sec) { + return false; + } + } + return true; +} + +_SOKOL_PRIVATE void _sapp_x11_app_event(sapp_event_type type) { + if (_sapp_events_enabled()) { + _sapp_init_event(type); + _sapp_call_event(&_sapp.event); + } +} + +_SOKOL_PRIVATE void _sapp_x11_update_dimensions(int x11_window_width, int x11_window_height) { + // NOTE: do *NOT* use _sapp.dpi_scale for the window scale + const float window_scale = _sapp.x11.dpi / 96.0f; + _sapp.window_width = _sapp_roundf_gzero(x11_window_width / window_scale); + _sapp.window_height = _sapp_roundf_gzero(x11_window_height / window_scale); + int cur_fb_width = _sapp.framebuffer_width; + int cur_fb_height = _sapp.framebuffer_height; + _sapp.framebuffer_width = _sapp_roundf_gzero(_sapp.window_width * _sapp.dpi_scale); + _sapp.framebuffer_height = _sapp_roundf_gzero(_sapp.window_height * _sapp.dpi_scale); + bool dim_changed = (_sapp.framebuffer_width != cur_fb_width) || (_sapp.framebuffer_height != cur_fb_height); + if (dim_changed) { + #if defined(SOKOL_WGPU) + _sapp_wgpu_swapchain_size_changed(); + #endif + if (!_sapp.first_frame) { + _sapp_x11_app_event(SAPP_EVENTTYPE_RESIZED); + } + } +} + +_SOKOL_PRIVATE void _sapp_x11_update_dimensions_from_window_size(void) { + XWindowAttributes attribs; + XGetWindowAttributes(_sapp.x11.display, _sapp.x11.window, &attribs); + _sapp_x11_update_dimensions(attribs.width, attribs.height); +} + +_SOKOL_PRIVATE void _sapp_x11_set_fullscreen(bool enable) { + /* NOTE: this function must be called after XMapWindow (which happens in _sapp_x11_show_window()) */ + if (_sapp.x11.NET_WM_STATE && _sapp.x11.NET_WM_STATE_FULLSCREEN) { + if (enable) { + const int _NET_WM_STATE_ADD = 1; + _sapp_x11_send_event(_sapp.x11.NET_WM_STATE, + _NET_WM_STATE_ADD, + _sapp.x11.NET_WM_STATE_FULLSCREEN, + 0, 1, 0); + } else { + const int _NET_WM_STATE_REMOVE = 0; + _sapp_x11_send_event(_sapp.x11.NET_WM_STATE, + _NET_WM_STATE_REMOVE, + _sapp.x11.NET_WM_STATE_FULLSCREEN, + 0, 1, 0); + } + } + XFlush(_sapp.x11.display); +} + +_SOKOL_PRIVATE void _sapp_x11_create_hidden_cursor(void) { + SOKOL_ASSERT(0 == _sapp.x11.hidden_cursor); + const int w = 16; + const int h = 16; + XcursorImage* img = XcursorImageCreate(w, h); + SOKOL_ASSERT(img && (img->width == 16) && (img->height == 16) && img->pixels); + img->xhot = 0; + img->yhot = 0; + const size_t num_bytes = (size_t)(w * h) * sizeof(XcursorPixel); + _sapp_clear(img->pixels, num_bytes); + _sapp.x11.hidden_cursor = XcursorImageLoadCursor(_sapp.x11.display, img); + XcursorImageDestroy(img); +} + + _SOKOL_PRIVATE void _sapp_x11_create_standard_cursor(sapp_mouse_cursor cursor, const char* name, const char* theme, int size, uint32_t fallback_native) { + SOKOL_ASSERT((cursor >= 0) && (cursor < _SAPP_MOUSECURSOR_NUM)); + SOKOL_ASSERT(_sapp.x11.display); + if (theme) { + XcursorImage* img = XcursorLibraryLoadImage(name, theme, size); + if (img) { + _sapp.x11.standard_cursors[cursor] = XcursorImageLoadCursor(_sapp.x11.display, img); + XcursorImageDestroy(img); + } + } + if (0 == _sapp.x11.standard_cursors[cursor]) { + _sapp.x11.standard_cursors[cursor] = XCreateFontCursor(_sapp.x11.display, fallback_native); + } +} + +_SOKOL_PRIVATE void _sapp_x11_create_standard_cursors(void) { + SOKOL_ASSERT(_sapp.x11.display); + const char* cursor_theme = XcursorGetTheme(_sapp.x11.display); + const int size = XcursorGetDefaultSize(_sapp.x11.display); + _sapp_x11_create_standard_cursor(SAPP_MOUSECURSOR_ARROW, "default", cursor_theme, size, XC_left_ptr); + _sapp_x11_create_standard_cursor(SAPP_MOUSECURSOR_IBEAM, "text", cursor_theme, size, XC_xterm); + _sapp_x11_create_standard_cursor(SAPP_MOUSECURSOR_CROSSHAIR, "crosshair", cursor_theme, size, XC_crosshair); + _sapp_x11_create_standard_cursor(SAPP_MOUSECURSOR_POINTING_HAND, "pointer", cursor_theme, size, XC_hand2); + _sapp_x11_create_standard_cursor(SAPP_MOUSECURSOR_RESIZE_EW, "ew-resize", cursor_theme, size, XC_sb_h_double_arrow); + _sapp_x11_create_standard_cursor(SAPP_MOUSECURSOR_RESIZE_NS, "ns-resize", cursor_theme, size, XC_sb_v_double_arrow); + _sapp_x11_create_standard_cursor(SAPP_MOUSECURSOR_RESIZE_NWSE, "nwse-resize", cursor_theme, size, 0); + _sapp_x11_create_standard_cursor(SAPP_MOUSECURSOR_RESIZE_NESW, "nesw-resize", cursor_theme, size, 0); + _sapp_x11_create_standard_cursor(SAPP_MOUSECURSOR_RESIZE_ALL, "all-scroll", cursor_theme, size, XC_fleur); + _sapp_x11_create_standard_cursor(SAPP_MOUSECURSOR_NOT_ALLOWED, "no-allowed", cursor_theme, size, 0); + _sapp_x11_create_hidden_cursor(); +} + +_SOKOL_PRIVATE void _sapp_x11_destroy_standard_cursors(void) { + SOKOL_ASSERT(_sapp.x11.display); + if (_sapp.x11.hidden_cursor) { + XFreeCursor(_sapp.x11.display, _sapp.x11.hidden_cursor); + _sapp.x11.hidden_cursor = 0; + } + for (int i = 0; i < _SAPP_MOUSECURSOR_NUM; i++) { + if (_sapp.x11.standard_cursors[i]) { + XFreeCursor(_sapp.x11.display, _sapp.x11.standard_cursors[i]); + _sapp.x11.standard_cursors[i] = 0; + } + } +} + +_SOKOL_PRIVATE bool _sapp_x11_make_custom_mouse_cursor(sapp_mouse_cursor cursor, const sapp_image_desc* desc) { + SOKOL_ASSERT((cursor >= 0) && (cursor < _SAPP_MOUSECURSOR_NUM)); + SOKOL_ASSERT(0 == _sapp.x11.custom_cursors[cursor]); + XcursorImage* img = XcursorImageCreate(desc->width, desc->height); + SOKOL_ASSERT(img && ((int) img->width == desc->width) && ((int) img->height == desc->height) && img->pixels); + img->xhot = (XcursorDim) desc->cursor_hotspot_x; + img->yhot = (XcursorDim) desc->cursor_hotspot_y; + const size_t dest_num_bytes = (size_t)(img->width * img->height) * sizeof(XcursorPixel); + SOKOL_ASSERT(dest_num_bytes == desc->pixels.size); + // Copy RGBA -> BGRA + for (size_t i = 0; i < dest_num_bytes; i += 4) { + ((uint8_t*) img->pixels)[i+0] = ((uint8_t*) desc->pixels.ptr)[i+2]; + ((uint8_t*) img->pixels)[i+1] = ((uint8_t*) desc->pixels.ptr)[i+1]; + ((uint8_t*) img->pixels)[i+2] = ((uint8_t*) desc->pixels.ptr)[i+0]; + ((uint8_t*) img->pixels)[i+3] = ((uint8_t*) desc->pixels.ptr)[i+3]; + } + _sapp.x11.custom_cursors[cursor] = XcursorImageLoadCursor(_sapp.x11.display, img); + XcursorImageDestroy(img); + return 0 != _sapp.x11.custom_cursors[cursor]; +} + +_SOKOL_PRIVATE void _sapp_x11_destroy_custom_mouse_cursor(sapp_mouse_cursor cursor) { + SOKOL_ASSERT((cursor >= 0) && (cursor < _SAPP_MOUSECURSOR_NUM)); + Cursor xcursor = _sapp.x11.custom_cursors[cursor]; + _sapp.x11.custom_cursors[cursor] = 0; + SOKOL_ASSERT(xcursor); + XFreeCursor(_sapp.x11.display, xcursor); +} + +_SOKOL_PRIVATE void _sapp_x11_toggle_fullscreen(void) { + _sapp.fullscreen = !_sapp.fullscreen; + _sapp_x11_set_fullscreen(_sapp.fullscreen); + _sapp_x11_update_dimensions_from_window_size(); +} + +_SOKOL_PRIVATE void _sapp_x11_update_cursor(sapp_mouse_cursor cursor, bool shown) { + SOKOL_ASSERT((cursor >= 0) && (cursor < _SAPP_MOUSECURSOR_NUM)); + if (shown) { + if (_sapp.custom_cursor_bound[cursor]) { + Cursor xcursor = _sapp.x11.custom_cursors[cursor]; + SOKOL_ASSERT(0 != xcursor); + XDefineCursor(_sapp.x11.display, _sapp.x11.window, xcursor); + } else if (_sapp.x11.standard_cursors[cursor]) { + XDefineCursor(_sapp.x11.display, _sapp.x11.window, _sapp.x11.standard_cursors[cursor]); + } else { + XUndefineCursor(_sapp.x11.display, _sapp.x11.window); + } + } else { + XDefineCursor(_sapp.x11.display, _sapp.x11.window, _sapp.x11.hidden_cursor); + } + XFlush(_sapp.x11.display); +} + +_SOKOL_PRIVATE void _sapp_x11_lock_mouse(bool lock) { + if (lock == _sapp.mouse.locked) { + return; + } + _sapp.mouse.dx = 0.0f; + _sapp.mouse.dy = 0.0f; + _sapp.mouse.locked = lock; + if (_sapp.mouse.locked) { + if (_sapp.x11.xi.available) { + XIEventMask em; + unsigned char mask[XIMaskLen(XI_RawMotion)] = { 0 }; // XIMaskLen is a macro + em.deviceid = XIAllMasterDevices; + em.mask_len = sizeof(mask); + em.mask = mask; + XISetMask(mask, XI_RawMotion); + XISelectEvents(_sapp.x11.display, _sapp.x11.root, &em, 1); + } + XGrabPointer(_sapp.x11.display, // display + _sapp.x11.window, // grab_window + True, // owner_events + ButtonPressMask | ButtonReleaseMask | PointerMotionMask, // event_mask + GrabModeAsync, // pointer_mode + GrabModeAsync, // keyboard_mode + _sapp.x11.window, // confine_to + _sapp.x11.hidden_cursor, // cursor + CurrentTime); // time + } else { + if (_sapp.x11.xi.available) { + XIEventMask em; + unsigned char mask[] = { 0 }; + em.deviceid = XIAllMasterDevices; + em.mask_len = sizeof(mask); + em.mask = mask; + XISelectEvents(_sapp.x11.display, _sapp.x11.root, &em, 1); + } + XWarpPointer(_sapp.x11.display, None, _sapp.x11.window, 0, 0, 0, 0, (int) _sapp.mouse.x, _sapp.mouse.y); + XUngrabPointer(_sapp.x11.display, CurrentTime); + } + XFlush(_sapp.x11.display); +} + +_SOKOL_PRIVATE void _sapp_x11_set_clipboard_string(const char* str) { + SOKOL_ASSERT(_sapp.clipboard.enabled && _sapp.clipboard.buffer); + if (strlen(str) >= (size_t)_sapp.clipboard.buf_size) { + _SAPP_ERROR(CLIPBOARD_STRING_TOO_BIG); + } + XSetSelectionOwner(_sapp.x11.display, _sapp.x11.CLIPBOARD, _sapp.x11.window, CurrentTime); + if (XGetSelectionOwner(_sapp.x11.display, _sapp.x11.CLIPBOARD) != _sapp.x11.window) { + _SAPP_ERROR(LINUX_X11_FAILED_TO_BECOME_OWNER_OF_CLIPBOARD); + } +} + +_SOKOL_PRIVATE const char* _sapp_x11_get_clipboard_string(void) { + SOKOL_ASSERT(_sapp.clipboard.enabled && _sapp.clipboard.buffer); + Atom none = XInternAtom(_sapp.x11.display, "SAPP_SELECTION", False); + Atom incremental = XInternAtom(_sapp.x11.display, "INCR", False); + if (XGetSelectionOwner(_sapp.x11.display, _sapp.x11.CLIPBOARD) == _sapp.x11.window) { + // Instead of doing a large number of X round-trips just to put this + // string into a window property and then read it back, just return it + return _sapp.clipboard.buffer; + } + XConvertSelection(_sapp.x11.display, + _sapp.x11.CLIPBOARD, + _sapp.x11.UTF8_STRING, + none, + _sapp.x11.window, + CurrentTime); + XEvent event; + if (!_sapp_x11_wait_for_event(SelectionNotify, 0.1, &event)) { + return NULL; + } + if (event.xselection.property == None) { + return NULL; + } + char* data = NULL; + Atom actualType; + int actualFormat; + unsigned long itemCount, bytesAfter; + const bool ret = XGetWindowProperty(_sapp.x11.display, + event.xselection.requestor, + event.xselection.property, + 0, + LONG_MAX, + True, + _sapp.x11.UTF8_STRING, + &actualType, + &actualFormat, + &itemCount, + &bytesAfter, + (unsigned char**) &data); + if (ret != Success || data == NULL) { + if (data != NULL) { + XFree(data); + } + return NULL; + } + if ((actualType == incremental) || (itemCount >= (size_t)_sapp.clipboard.buf_size)) { + _SAPP_ERROR(CLIPBOARD_STRING_TOO_BIG); + XFree(data); + return NULL; + } + _sapp_strcpy(data, _sapp.clipboard.buffer, (size_t)_sapp.clipboard.buf_size); + XFree(data); + return _sapp.clipboard.buffer; +} + +_SOKOL_PRIVATE void _sapp_x11_update_window_title(void) { + Xutf8SetWMProperties(_sapp.x11.display, + _sapp.x11.window, + _sapp.window_title, _sapp.window_title, + NULL, 0, NULL, NULL, NULL); + XChangeProperty(_sapp.x11.display, _sapp.x11.window, + _sapp.x11.NET_WM_NAME, _sapp.x11.UTF8_STRING, 8, + PropModeReplace, + (unsigned char*)_sapp.window_title, + strlen(_sapp.window_title)); + XChangeProperty(_sapp.x11.display, _sapp.x11.window, + _sapp.x11.NET_WM_ICON_NAME, _sapp.x11.UTF8_STRING, 8, + PropModeReplace, + (unsigned char*)_sapp.window_title, + strlen(_sapp.window_title)); + XFlush(_sapp.x11.display); +} + +_SOKOL_PRIVATE void _sapp_x11_set_icon(const sapp_icon_desc* icon_desc, int num_images) { + SOKOL_ASSERT((num_images > 0) && (num_images <= SAPP_MAX_ICONIMAGES)); + int long_count = 0; + for (int i = 0; i < num_images; i++) { + const sapp_image_desc* img_desc = &icon_desc->images[i]; + long_count += 2 + (img_desc->width * img_desc->height); + } + long* icon_data = (long*) _sapp_malloc_clear((size_t)long_count * sizeof(long)); + SOKOL_ASSERT(icon_data); + long* dst = icon_data; + for (int img_index = 0; img_index < num_images; img_index++) { + const sapp_image_desc* img_desc = &icon_desc->images[img_index]; + const uint8_t* src = (const uint8_t*) img_desc->pixels.ptr; + *dst++ = img_desc->width; + *dst++ = img_desc->height; + const int num_pixels = img_desc->width * img_desc->height; + for (int pixel_index = 0; pixel_index < num_pixels; pixel_index++) { + *dst++ = ((long)(src[pixel_index * 4 + 0]) << 16) | + ((long)(src[pixel_index * 4 + 1]) << 8) | + ((long)(src[pixel_index * 4 + 2]) << 0) | + ((long)(src[pixel_index * 4 + 3]) << 24); + } + } + XChangeProperty(_sapp.x11.display, _sapp.x11.window, + _sapp.x11.NET_WM_ICON, + XA_CARDINAL, 32, + PropModeReplace, + (unsigned char*)icon_data, + long_count); + _sapp_free(icon_data); + XFlush(_sapp.x11.display); +} + +_SOKOL_PRIVATE void _sapp_x11_create_window(Visual* visual_or_null, int depth) { + Visual* visual = visual_or_null; + if (0 == visual_or_null) { + visual = DefaultVisual(_sapp.x11.display, _sapp.x11.screen); + depth = DefaultDepth(_sapp.x11.display, _sapp.x11.screen); + } + _sapp.x11.colormap = XCreateColormap(_sapp.x11.display, _sapp.x11.root, visual, AllocNone); + XSetWindowAttributes wa; + _sapp_clear(&wa, sizeof(wa)); + const uint32_t wamask = CWBorderPixel | CWColormap | CWEventMask; + wa.colormap = _sapp.x11.colormap; + wa.border_pixel = 0; + wa.event_mask = StructureNotifyMask | KeyPressMask | KeyReleaseMask | + PointerMotionMask | ButtonPressMask | ButtonReleaseMask | + ExposureMask | FocusChangeMask | VisibilityChangeMask | + EnterWindowMask | LeaveWindowMask | PropertyChangeMask; + + int display_width = DisplayWidth(_sapp.x11.display, _sapp.x11.screen); + int display_height = DisplayHeight(_sapp.x11.display, _sapp.x11.screen); + // NOTE: do *NOT* use _sapp.dpi_scale for the size multiplicator! + const float window_scale = _sapp.x11.dpi / 96.0f; + int x11_window_width = _sapp_roundf_gzero(_sapp.window_width * window_scale); + int x11_window_height = _sapp_roundf_gzero(_sapp.window_height * window_scale); + if (0 == _sapp.window_width) { + x11_window_width = (display_width * 4) / 5; + } + if (0 == _sapp.window_height) { + x11_window_height = (display_height * 4) / 5; + } + _sapp_x11_grab_error_handler(); + _sapp.x11.window = XCreateWindow(_sapp.x11.display, + _sapp.x11.root, + 0, 0, + (uint32_t)x11_window_width, + (uint32_t)x11_window_height, + 0, /* border width */ + depth, /* color depth */ + InputOutput, + visual, + wamask, + &wa); + _sapp_x11_release_error_handler(); + if (!_sapp.x11.window) { + _SAPP_PANIC(LINUX_X11_CREATE_WINDOW_FAILED); + } + Atom protocols[] = { + _sapp.x11.WM_DELETE_WINDOW + }; + XSetWMProtocols(_sapp.x11.display, _sapp.x11.window, protocols, 1); + + // NOTE: PPosition and PSize are obsolete and ignored + XSizeHints* hints = XAllocSizeHints(); + hints->flags = PWinGravity; + hints->win_gravity = CenterGravity; + XSetWMNormalHints(_sapp.x11.display, _sapp.x11.window, hints); + XFree(hints); + + // announce support for drag'n'drop + if (_sapp.drop.enabled) { + const Atom version = _SAPP_X11_XDND_VERSION; + XChangeProperty(_sapp.x11.display, _sapp.x11.window, _sapp.x11.xdnd.XdndAware, XA_ATOM, 32, PropModeReplace, (unsigned char*) &version, 1); + } + _sapp_x11_update_window_title(); + _sapp_x11_update_dimensions_from_window_size(); +} + +_SOKOL_PRIVATE void _sapp_x11_destroy_window(void) { + if (_sapp.x11.window) { + XUnmapWindow(_sapp.x11.display, _sapp.x11.window); + XDestroyWindow(_sapp.x11.display, _sapp.x11.window); + _sapp.x11.window = 0; + } + if (_sapp.x11.colormap) { + XFreeColormap(_sapp.x11.display, _sapp.x11.colormap); + _sapp.x11.colormap = 0; + } + XFlush(_sapp.x11.display); +} + +_SOKOL_PRIVATE bool _sapp_x11_window_visible(void) { + XWindowAttributes wa; + XGetWindowAttributes(_sapp.x11.display, _sapp.x11.window, &wa); + return wa.map_state == IsViewable; +} + +_SOKOL_PRIVATE void _sapp_x11_show_window(void) { + if (!_sapp_x11_window_visible()) { + XMapWindow(_sapp.x11.display, _sapp.x11.window); + XEvent dummy; + _sapp_x11_wait_for_event(VisibilityNotify, 0.1, &dummy); + XRaiseWindow(_sapp.x11.display, _sapp.x11.window); + XFlush(_sapp.x11.display); + } +} + +_SOKOL_PRIVATE void _sapp_x11_hide_window(void) { + XUnmapWindow(_sapp.x11.display, _sapp.x11.window); + XFlush(_sapp.x11.display); +} + +_SOKOL_PRIVATE unsigned long _sapp_x11_get_window_property(Window window, Atom property, Atom type, unsigned char** value) { + Atom actualType; + int actualFormat; + unsigned long itemCount, bytesAfter; + XGetWindowProperty(_sapp.x11.display, + window, + property, + 0, + LONG_MAX, + False, + type, + &actualType, + &actualFormat, + &itemCount, + &bytesAfter, + value); + return itemCount; +} + +_SOKOL_PRIVATE int _sapp_x11_get_window_state(void) { + int result = WithdrawnState; + struct { + CARD32 state; + Window icon; + } *state = NULL; + + if (_sapp_x11_get_window_property(_sapp.x11.window, _sapp.x11.WM_STATE, _sapp.x11.WM_STATE, (unsigned char**)&state) >= 2) { + result = (int)state->state; + } + if (state) { + XFree(state); + } + return result; +} + +_SOKOL_PRIVATE uint32_t _sapp_x11_key_modifier_bit(sapp_keycode key) { + switch (key) { + case SAPP_KEYCODE_LEFT_SHIFT: + case SAPP_KEYCODE_RIGHT_SHIFT: + return SAPP_MODIFIER_SHIFT; + case SAPP_KEYCODE_LEFT_CONTROL: + case SAPP_KEYCODE_RIGHT_CONTROL: + return SAPP_MODIFIER_CTRL; + case SAPP_KEYCODE_LEFT_ALT: + case SAPP_KEYCODE_RIGHT_ALT: + return SAPP_MODIFIER_ALT; + case SAPP_KEYCODE_LEFT_SUPER: + case SAPP_KEYCODE_RIGHT_SUPER: + return SAPP_MODIFIER_SUPER; + default: + return 0; + } +} + +_SOKOL_PRIVATE uint32_t _sapp_x11_button_modifier_bit(sapp_mousebutton btn) { + switch (btn) { + case SAPP_MOUSEBUTTON_LEFT: return SAPP_MODIFIER_LMB; + case SAPP_MOUSEBUTTON_RIGHT: return SAPP_MODIFIER_RMB; + case SAPP_MOUSEBUTTON_MIDDLE: return SAPP_MODIFIER_MMB; + default: return 0; + } +} + +_SOKOL_PRIVATE uint32_t _sapp_x11_mods(uint32_t x11_mods) { + uint32_t mods = 0; + if (x11_mods & ShiftMask) { + mods |= SAPP_MODIFIER_SHIFT; + } + if (x11_mods & ControlMask) { + mods |= SAPP_MODIFIER_CTRL; + } + if (x11_mods & Mod1Mask) { + mods |= SAPP_MODIFIER_ALT; + } + if (x11_mods & Mod4Mask) { + mods |= SAPP_MODIFIER_SUPER; + } + if (x11_mods & Button1Mask) { + mods |= SAPP_MODIFIER_LMB; + } + if (x11_mods & Button2Mask) { + mods |= SAPP_MODIFIER_MMB; + } + if (x11_mods & Button3Mask) { + mods |= SAPP_MODIFIER_RMB; + } + return mods; +} + +_SOKOL_PRIVATE sapp_mousebutton _sapp_x11_translate_button(const XEvent* event) { + switch (event->xbutton.button) { + case Button1: return SAPP_MOUSEBUTTON_LEFT; + case Button2: return SAPP_MOUSEBUTTON_MIDDLE; + case Button3: return SAPP_MOUSEBUTTON_RIGHT; + default: return SAPP_MOUSEBUTTON_INVALID; + } +} + +_SOKOL_PRIVATE void _sapp_x11_mouse_update(int x, int y, bool clear_dxdy) { + if (!_sapp.mouse.locked) { + const float new_x = (float)x; + const float new_y = (float)y; + if (clear_dxdy) { + _sapp.mouse.dx = 0.0f; + _sapp.mouse.dy = 0.0f; + } else if (_sapp.mouse.pos_valid) { + _sapp.mouse.dx = new_x - _sapp.mouse.x; + _sapp.mouse.dy = new_y - _sapp.mouse.y; + } + _sapp.mouse.x = new_x; + _sapp.mouse.y = new_y; + _sapp.mouse.pos_valid = true; + } +} + +_SOKOL_PRIVATE void _sapp_x11_mouse_event(sapp_event_type type, sapp_mousebutton btn, uint32_t mods) { + if (_sapp_events_enabled()) { + _sapp_init_event(type); + _sapp.event.mouse_button = btn; + _sapp.event.modifiers = mods; + _sapp_call_event(&_sapp.event); + } +} + +_SOKOL_PRIVATE void _sapp_x11_scroll_event(float x, float y, uint32_t mods) { + if (_sapp_events_enabled()) { + _sapp_init_event(SAPP_EVENTTYPE_MOUSE_SCROLL); + _sapp.event.modifiers = mods; + _sapp.event.scroll_x = x; + _sapp.event.scroll_y = y; + _sapp_call_event(&_sapp.event); + } +} + +_SOKOL_PRIVATE void _sapp_x11_key_event(sapp_event_type type, sapp_keycode key, bool repeat, uint32_t mods) { + if (_sapp_events_enabled()) { + _sapp_init_event(type); + _sapp.event.key_code = key; + _sapp.event.key_repeat = repeat; + _sapp.event.modifiers = mods; + _sapp_call_event(&_sapp.event); + /* check if a CLIPBOARD_PASTED event must be sent too */ + if (_sapp.clipboard.enabled && + (type == SAPP_EVENTTYPE_KEY_DOWN) && + (_sapp.event.modifiers == SAPP_MODIFIER_CTRL) && + (_sapp.event.key_code == SAPP_KEYCODE_V)) + { + _sapp_init_event(SAPP_EVENTTYPE_CLIPBOARD_PASTED); + _sapp_call_event(&_sapp.event); + } + } +} + +_SOKOL_PRIVATE void _sapp_x11_char_event(uint32_t chr, bool repeat, uint32_t mods) { + if (_sapp_events_enabled()) { + _sapp_init_event(SAPP_EVENTTYPE_CHAR); + _sapp.event.char_code = chr; + _sapp.event.key_repeat = repeat; + _sapp.event.modifiers = mods; + _sapp_call_event(&_sapp.event); + } +} + +_SOKOL_PRIVATE sapp_keycode _sapp_x11_translate_key(int scancode) { + if ((scancode >= 0) && (scancode < _SAPP_X11_MAX_X11_KEYCODES)) { + return _sapp.keycodes[scancode]; + } else { + return SAPP_KEYCODE_INVALID; + } +} + +_SOKOL_PRIVATE int32_t _sapp_x11_keysym_to_unicode(KeySym keysym) { + int min = 0; + int max = sizeof(_sapp_x11_keysymtab) / sizeof(struct _sapp_x11_codepair) - 1; + int mid; + + /* First check for Latin-1 characters (1:1 mapping) */ + if ((keysym >= 0x0020 && keysym <= 0x007e) || + (keysym >= 0x00a0 && keysym <= 0x00ff)) + { + return keysym; + } + + /* Also check for directly encoded 24-bit UCS characters */ + if ((keysym & 0xff000000) == 0x01000000) { + return keysym & 0x00ffffff; + } + + /* Binary search in table */ + while (max >= min) { + mid = (min + max) / 2; + if (_sapp_x11_keysymtab[mid].keysym < keysym) { + min = mid + 1; + } else if (_sapp_x11_keysymtab[mid].keysym > keysym) { + max = mid - 1; + } else { + return _sapp_x11_keysymtab[mid].ucs; + } + } + + /* No matching Unicode value found */ + return -1; +} + +_SOKOL_PRIVATE bool _sapp_x11_keypress_repeat(int keycode) { + bool repeat = false; + if ((keycode >= 0) && (keycode < _SAPP_X11_MAX_X11_KEYCODES)) { + repeat = _sapp.x11.key_repeat[keycode]; + _sapp.x11.key_repeat[keycode] = true; + } + return repeat; +} + +_SOKOL_PRIVATE void _sapp_x11_keyrelease_repeat(int keycode) { + if ((keycode >= 0) && (keycode < _SAPP_X11_MAX_X11_KEYCODES)) { + _sapp.x11.key_repeat[keycode] = false; + } +} + +_SOKOL_PRIVATE bool _sapp_x11_parse_dropped_files_list(const char* src) { + SOKOL_ASSERT(src); + SOKOL_ASSERT(_sapp.drop.buffer); + + _sapp_clear_drop_buffer(); + _sapp.drop.num_files = 0; + + /* + src is (potentially percent-encoded) string made of one or multiple paths + separated by \r\n, each path starting with 'file://' + */ + bool err = false; + int src_count = 0; + char src_chr = 0; + char* dst_ptr = _sapp.drop.buffer; + const char* dst_end_ptr = dst_ptr + (_sapp.drop.max_path_length - 1); // room for terminating 0 + while (0 != (src_chr = *src++)) { + src_count++; + char dst_chr = 0; + /* check leading 'file://' */ + if (src_count <= 7) { + if (((src_count == 1) && (src_chr != 'f')) || + ((src_count == 2) && (src_chr != 'i')) || + ((src_count == 3) && (src_chr != 'l')) || + ((src_count == 4) && (src_chr != 'e')) || + ((src_count == 5) && (src_chr != ':')) || + ((src_count == 6) && (src_chr != '/')) || + ((src_count == 7) && (src_chr != '/'))) + { + _SAPP_ERROR(LINUX_X11_DROPPED_FILE_URI_WRONG_SCHEME); + err = true; + break; + } + } else if (src_chr == '\r') { + // skip + } else if (src_chr == '\n') { + src_count = 0; + _sapp.drop.num_files++; + // too many files is not an error + if (_sapp.drop.num_files >= _sapp.drop.max_files) { + break; + } + dst_ptr = _sapp.drop.buffer + _sapp.drop.num_files * _sapp.drop.max_path_length; + dst_end_ptr = dst_ptr + (_sapp.drop.max_path_length - 1); + } else if ((src_chr == '%') && src[0] && src[1]) { + // a percent-encoded byte (most likely UTF-8 multibyte sequence) + const char digits[3] = { src[0], src[1], 0 }; + src += 2; + dst_chr = (char) strtol(digits, 0, 16); + } else { + dst_chr = src_chr; + } + if (dst_chr) { + // dst_end_ptr already has adjustment for terminating zero + if (dst_ptr < dst_end_ptr) { + *dst_ptr++ = dst_chr; + } else { + _SAPP_ERROR(DROPPED_FILE_PATH_TOO_LONG); + err = true; + break; + } + } + } + if (err) { + _sapp_clear_drop_buffer(); + _sapp.drop.num_files = 0; + return false; + } else { + return true; + } +} + +_SOKOL_PRIVATE void _sapp_x11_on_genericevent(XEvent* event) { + if (_sapp.mouse.locked && _sapp.x11.xi.available) { + if (event->xcookie.extension == _sapp.x11.xi.major_opcode) { + if (XGetEventData(_sapp.x11.display, &event->xcookie)) { + if (event->xcookie.evtype == XI_RawMotion) { + XIRawEvent* re = (XIRawEvent*) event->xcookie.data; + if (re->valuators.mask_len) { + const double* values = re->raw_values; + if (XIMaskIsSet(re->valuators.mask, 0)) { + _sapp.mouse.dx = (float) *values; + values++; + } + if (XIMaskIsSet(re->valuators.mask, 1)) { + _sapp.mouse.dy = (float) *values; + } + _sapp_x11_mouse_event(SAPP_EVENTTYPE_MOUSE_MOVE, SAPP_MOUSEBUTTON_INVALID, _sapp_x11_mods(event->xmotion.state)); + } + } + XFreeEventData(_sapp.x11.display, &event->xcookie); + } + } + } +} + +_SOKOL_PRIVATE void _sapp_x11_on_focusin(XEvent* event) { + // NOTE: ignoring NotifyGrab and NotifyUngrab is same behaviour as GLFW + if ((event->xfocus.mode != NotifyGrab) && (event->xfocus.mode != NotifyUngrab)) { + _sapp_x11_app_event(SAPP_EVENTTYPE_FOCUSED); + } +} + +_SOKOL_PRIVATE void _sapp_x11_on_focusout(XEvent* event) { + // if focus is lost for any reason, and we're in mouse locked mode, disable mouse lock + if (_sapp.mouse.locked) { + _sapp_x11_lock_mouse(false); + } + // NOTE: ignoring NotifyGrab and NotifyUngrab is same behaviour as GLFW + if ((event->xfocus.mode != NotifyGrab) && (event->xfocus.mode != NotifyUngrab)) { + _sapp_x11_app_event(SAPP_EVENTTYPE_UNFOCUSED); + } +} + +_SOKOL_PRIVATE void _sapp_x11_on_keypress(XEvent* event) { + int keycode = (int)event->xkey.keycode; + + const sapp_keycode key = _sapp_x11_translate_key(keycode); + const bool repeat = _sapp_x11_keypress_repeat(keycode); + uint32_t mods = _sapp_x11_mods(event->xkey.state); + // X11 doesn't set modifier bit on key down, so emulate that + mods |= _sapp_x11_key_modifier_bit(key); + if (key != SAPP_KEYCODE_INVALID) { + _sapp_x11_key_event(SAPP_EVENTTYPE_KEY_DOWN, key, repeat, mods); + } + KeySym keysym; + XLookupString(&event->xkey, NULL, 0, &keysym, NULL); + int32_t chr = _sapp_x11_keysym_to_unicode(keysym); + if (chr > 0) { + _sapp_x11_char_event((uint32_t)chr, repeat, mods); + } +} + +_SOKOL_PRIVATE void _sapp_x11_on_keyrelease(XEvent* event) { + int keycode = (int)event->xkey.keycode; + const sapp_keycode key = _sapp_x11_translate_key(keycode); + _sapp_x11_keyrelease_repeat(keycode); + if (key != SAPP_KEYCODE_INVALID) { + uint32_t mods = _sapp_x11_mods(event->xkey.state); + // X11 doesn't clear modifier bit on key up, so emulate that + mods &= ~_sapp_x11_key_modifier_bit(key); + _sapp_x11_key_event(SAPP_EVENTTYPE_KEY_UP, key, false, mods); + } +} + +_SOKOL_PRIVATE void _sapp_x11_on_buttonpress(XEvent* event) { + _sapp_x11_mouse_update(event->xbutton.x, event->xbutton.y, false); + const sapp_mousebutton btn = _sapp_x11_translate_button(event); + uint32_t mods = _sapp_x11_mods(event->xbutton.state); + // X11 doesn't set modifier bit on button down, so emulate that + mods |= _sapp_x11_button_modifier_bit(btn); + if (btn != SAPP_MOUSEBUTTON_INVALID) { + _sapp_x11_mouse_event(SAPP_EVENTTYPE_MOUSE_DOWN, btn, mods); + _sapp.x11.mouse_buttons |= (1 << btn); + } else { + // might be a scroll event + switch (event->xbutton.button) { + case 4: _sapp_x11_scroll_event(0.0f, 1.0f, mods); break; + case 5: _sapp_x11_scroll_event(0.0f, -1.0f, mods); break; + case 6: _sapp_x11_scroll_event(1.0f, 0.0f, mods); break; + case 7: _sapp_x11_scroll_event(-1.0f, 0.0f, mods); break; + } + } +} + +_SOKOL_PRIVATE void _sapp_x11_on_buttonrelease(XEvent* event) { + _sapp_x11_mouse_update(event->xbutton.x, event->xbutton.y, false); + const sapp_mousebutton btn = _sapp_x11_translate_button(event); + if (btn != SAPP_MOUSEBUTTON_INVALID) { + uint32_t mods = _sapp_x11_mods(event->xbutton.state); + // X11 doesn't clear modifier bit on button up, so emulate that + mods &= ~_sapp_x11_button_modifier_bit(btn); + _sapp_x11_mouse_event(SAPP_EVENTTYPE_MOUSE_UP, btn, mods); + _sapp.x11.mouse_buttons &= ~(1 << btn); + } +} + +_SOKOL_PRIVATE void _sapp_x11_on_enternotify(XEvent* event) { + // don't send enter/leave events while mouse button held down + if (0 == _sapp.x11.mouse_buttons) { + _sapp_x11_mouse_update(event->xcrossing.x, event->xcrossing.y, true); + _sapp_x11_mouse_event(SAPP_EVENTTYPE_MOUSE_ENTER, SAPP_MOUSEBUTTON_INVALID, _sapp_x11_mods(event->xcrossing.state)); + } +} + +_SOKOL_PRIVATE void _sapp_x11_on_leavenotify(XEvent* event) { + if (0 == _sapp.x11.mouse_buttons) { + _sapp_x11_mouse_update(event->xcrossing.x, event->xcrossing.y, true); + _sapp_x11_mouse_event(SAPP_EVENTTYPE_MOUSE_LEAVE, SAPP_MOUSEBUTTON_INVALID, _sapp_x11_mods(event->xcrossing.state)); + } +} + +_SOKOL_PRIVATE void _sapp_x11_on_motionnotify(XEvent* event) { + if (!_sapp.mouse.locked) { + _sapp_x11_mouse_update(event->xmotion.x, event->xmotion.y, false); + _sapp_x11_mouse_event(SAPP_EVENTTYPE_MOUSE_MOVE, SAPP_MOUSEBUTTON_INVALID, _sapp_x11_mods(event->xmotion.state)); + } +} + +_SOKOL_PRIVATE void _sapp_x11_on_propertynotify(XEvent* event) { + if (event->xproperty.state == PropertyNewValue) { + if (event->xproperty.atom == _sapp.x11.WM_STATE) { + const int state = _sapp_x11_get_window_state(); + if (state != _sapp.x11.window_state) { + _sapp.x11.window_state = state; + if (state == IconicState) { + _sapp_x11_app_event(SAPP_EVENTTYPE_ICONIFIED); + } else if (state == NormalState) { + _sapp_x11_app_event(SAPP_EVENTTYPE_RESTORED); + } + } + } + } +} + +_SOKOL_PRIVATE void _sapp_x11_on_selectionnotify(XEvent* event) { + if (event->xselection.property == _sapp.x11.xdnd.XdndSelection) { + char* data = 0; + uint32_t result = _sapp_x11_get_window_property(event->xselection.requestor, + event->xselection.property, + event->xselection.target, + (unsigned char**) &data); + if (_sapp.drop.enabled && result) { + if (_sapp_x11_parse_dropped_files_list(data)) { + _sapp.mouse.dx = 0.0f; + _sapp.mouse.dy = 0.0f; + if (_sapp_events_enabled()) { + // FIXME: Figure out how to get modifier key state here. + // The XSelection event has no 'state' item, and + // XQueryKeymap() always returns a zeroed array. + _sapp_init_event(SAPP_EVENTTYPE_FILES_DROPPED); + _sapp_call_event(&_sapp.event); + } + } + } + if (_sapp.x11.xdnd.version >= 2) { + XEvent reply; + _sapp_clear(&reply, sizeof(reply)); + reply.type = ClientMessage; + reply.xclient.window = _sapp.x11.xdnd.source; + reply.xclient.message_type = _sapp.x11.xdnd.XdndFinished; + reply.xclient.format = 32; + reply.xclient.data.l[0] = (long)_sapp.x11.window; + reply.xclient.data.l[1] = result; + reply.xclient.data.l[2] = (long)_sapp.x11.xdnd.XdndActionCopy; + XSendEvent(_sapp.x11.display, _sapp.x11.xdnd.source, False, NoEventMask, &reply); + XFlush(_sapp.x11.display); + } + if (data) { + XFree(data); + } + } +} + +_SOKOL_PRIVATE void _sapp_x11_on_clientmessage(XEvent* event) { + if (XFilterEvent(event, None)) { + return; + } + if (event->xclient.message_type == _sapp.x11.WM_PROTOCOLS) { + const Atom protocol = (Atom)event->xclient.data.l[0]; + if (protocol == _sapp.x11.WM_DELETE_WINDOW) { + _sapp.quit_requested = true; + } + } else if (event->xclient.message_type == _sapp.x11.xdnd.XdndEnter) { + const bool is_list = 0 != (event->xclient.data.l[1] & 1); + _sapp.x11.xdnd.source = (Window)event->xclient.data.l[0]; + _sapp.x11.xdnd.version = event->xclient.data.l[1] >> 24; + _sapp.x11.xdnd.format = None; + if (_sapp.x11.xdnd.version > _SAPP_X11_XDND_VERSION) { + return; + } + uint32_t count = 0; + Atom* formats = 0; + if (is_list) { + count = _sapp_x11_get_window_property(_sapp.x11.xdnd.source, _sapp.x11.xdnd.XdndTypeList, XA_ATOM, (unsigned char**)&formats); + } else { + count = 3; + formats = (Atom*) event->xclient.data.l + 2; + } + for (uint32_t i = 0; i < count; i++) { + if (formats[i] == _sapp.x11.xdnd.text_uri_list) { + _sapp.x11.xdnd.format = _sapp.x11.xdnd.text_uri_list; + break; + } + } + if (is_list && formats) { + XFree(formats); + } + } else if (event->xclient.message_type == _sapp.x11.xdnd.XdndDrop) { + if (_sapp.x11.xdnd.version > _SAPP_X11_XDND_VERSION) { + return; + } + Time time = CurrentTime; + if (_sapp.x11.xdnd.format) { + if (_sapp.x11.xdnd.version >= 1) { + time = (Time)event->xclient.data.l[2]; + } + XConvertSelection(_sapp.x11.display, + _sapp.x11.xdnd.XdndSelection, + _sapp.x11.xdnd.format, + _sapp.x11.xdnd.XdndSelection, + _sapp.x11.window, + time); + } else if (_sapp.x11.xdnd.version >= 2) { + XEvent reply; + _sapp_clear(&reply, sizeof(reply)); + reply.type = ClientMessage; + reply.xclient.window = _sapp.x11.xdnd.source; + reply.xclient.message_type = _sapp.x11.xdnd.XdndFinished; + reply.xclient.format = 32; + reply.xclient.data.l[0] = (long)_sapp.x11.window; + reply.xclient.data.l[1] = 0; // drag was rejected + reply.xclient.data.l[2] = None; + XSendEvent(_sapp.x11.display, _sapp.x11.xdnd.source, False, NoEventMask, &reply); + XFlush(_sapp.x11.display); + } + } else if (event->xclient.message_type == _sapp.x11.xdnd.XdndPosition) { + // drag operation has moved over the window + // FIXME: we could track the mouse position here, but + // this isn't implemented on other platforms either so far + if (_sapp.x11.xdnd.version > _SAPP_X11_XDND_VERSION) { + return; + } + XEvent reply; + _sapp_clear(&reply, sizeof(reply)); + reply.type = ClientMessage; + reply.xclient.window = _sapp.x11.xdnd.source; + reply.xclient.message_type = _sapp.x11.xdnd.XdndStatus; + reply.xclient.format = 32; + reply.xclient.data.l[0] = (long)_sapp.x11.window; + if (_sapp.x11.xdnd.format) { + /* reply that we are ready to copy the dragged data */ + reply.xclient.data.l[1] = 1; // accept with no rectangle + if (_sapp.x11.xdnd.version >= 2) { + reply.xclient.data.l[4] = (long)_sapp.x11.xdnd.XdndActionCopy; + } + } + XSendEvent(_sapp.x11.display, _sapp.x11.xdnd.source, False, NoEventMask, &reply); + XFlush(_sapp.x11.display); + } +} + +_SOKOL_PRIVATE void _sapp_x11_on_selectionrequest(XEvent* event) { + XSelectionRequestEvent* req = &event->xselectionrequest; + if (req->selection != _sapp.x11.CLIPBOARD) { + return; + } + if (!_sapp.clipboard.enabled) { + return; + } + SOKOL_ASSERT(_sapp.clipboard.buffer); + XSelectionEvent reply; + _sapp_clear(&reply, sizeof(reply)); + reply.type = SelectionNotify; + reply.display = req->display; + reply.requestor = req->requestor; + reply.selection = req->selection; + reply.target = req->target; + reply.property = req->property; + reply.time = req->time; + if (req->target == _sapp.x11.UTF8_STRING) { + XChangeProperty(_sapp.x11.display, + req->requestor, + req->property, + _sapp.x11.UTF8_STRING, + 8, + PropModeReplace, + (unsigned char*) _sapp.clipboard.buffer, + strlen(_sapp.clipboard.buffer)); + } else if (req->target == _sapp.x11.TARGETS) { + XChangeProperty(_sapp.x11.display, + req->requestor, + req->property, + XA_ATOM, + 32, + PropModeReplace, + (unsigned char*) &_sapp.x11.UTF8_STRING, + 1); + } else { + reply.property = None; + } + XSendEvent(_sapp.x11.display, req->requestor, False, 0, (XEvent*) &reply); +} + +_SOKOL_PRIVATE void _sapp_x11_process_event(XEvent* event) { + switch (event->type) { + case GenericEvent: + _sapp_x11_on_genericevent(event); + break; + case FocusIn: + _sapp_x11_on_focusin(event); + break; + case FocusOut: + _sapp_x11_on_focusout(event); + break; + case KeyPress: + _sapp_x11_on_keypress(event); + break; + case KeyRelease: + _sapp_x11_on_keyrelease(event); + break; + case ButtonPress: + _sapp_x11_on_buttonpress(event); + break; + case ButtonRelease: + _sapp_x11_on_buttonrelease(event); + break; + case EnterNotify: + _sapp_x11_on_enternotify(event); + break; + case LeaveNotify: + _sapp_x11_on_leavenotify(event); + break; + case MotionNotify: + _sapp_x11_on_motionnotify(event); + break; + case PropertyNotify: + _sapp_x11_on_propertynotify(event); + break; + case SelectionNotify: + _sapp_x11_on_selectionnotify(event); + break; + case SelectionRequest: + _sapp_x11_on_selectionrequest(event); + break; + case DestroyNotify: + // not a bug + break; + case ClientMessage: + _sapp_x11_on_clientmessage(event); + break; + } +} + +#if defined(_SAPP_EGL) + +_SOKOL_PRIVATE void _sapp_egl_init(void) { + #if defined(SOKOL_GLCORE) + if (!eglBindAPI(EGL_OPENGL_API)) { + _SAPP_PANIC(LINUX_EGL_BIND_OPENGL_API_FAILED); + } + #else + if (!eglBindAPI(EGL_OPENGL_ES_API)) { + _SAPP_PANIC(LINUX_EGL_BIND_OPENGL_ES_API_FAILED); + } + #endif + + _sapp.egl.display = eglGetDisplay((EGLNativeDisplayType)_sapp.x11.display); + if (EGL_NO_DISPLAY == _sapp.egl.display) { + _SAPP_PANIC(LINUX_EGL_GET_DISPLAY_FAILED); + } + + EGLint major, minor; + if (!eglInitialize(_sapp.egl.display, &major, &minor)) { + _SAPP_PANIC(LINUX_EGL_INITIALIZE_FAILED); + } + + EGLint sample_count = _sapp.desc.sample_count > 1 ? _sapp.desc.sample_count : 0; + EGLint alpha_size = _sapp.desc.alpha ? 8 : 0; + const EGLint config_attrs[] = { + EGL_SURFACE_TYPE, EGL_WINDOW_BIT, + #if defined(SOKOL_GLCORE) + EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT, + #elif defined(SOKOL_GLES3) + EGL_RENDERABLE_TYPE, EGL_OPENGL_ES3_BIT, + #endif + EGL_RED_SIZE, 8, + EGL_GREEN_SIZE, 8, + EGL_BLUE_SIZE, 8, + EGL_ALPHA_SIZE, alpha_size, + EGL_DEPTH_SIZE, 24, + EGL_STENCIL_SIZE, 8, + EGL_SAMPLE_BUFFERS, _sapp.desc.sample_count > 1 ? 1 : 0, + EGL_SAMPLES, sample_count, + EGL_NONE, + }; + + EGLConfig egl_configs[32]; + EGLint config_count; + if (!eglChooseConfig(_sapp.egl.display, config_attrs, egl_configs, 32, &config_count) || config_count == 0) { + _SAPP_PANIC(LINUX_EGL_NO_CONFIGS); + } + + EGLConfig config = egl_configs[0]; + for (int i = 0; i < config_count; ++i) { + EGLConfig c = egl_configs[i]; + EGLint r, g, b, a, d, s, n; + if (eglGetConfigAttrib(_sapp.egl.display, c, EGL_RED_SIZE, &r) && + eglGetConfigAttrib(_sapp.egl.display, c, EGL_GREEN_SIZE, &g) && + eglGetConfigAttrib(_sapp.egl.display, c, EGL_BLUE_SIZE, &b) && + eglGetConfigAttrib(_sapp.egl.display, c, EGL_ALPHA_SIZE, &a) && + eglGetConfigAttrib(_sapp.egl.display, c, EGL_DEPTH_SIZE, &d) && + eglGetConfigAttrib(_sapp.egl.display, c, EGL_STENCIL_SIZE, &s) && + eglGetConfigAttrib(_sapp.egl.display, c, EGL_SAMPLES, &n) && + (r == 8) && (g == 8) && (b == 8) && (a == alpha_size) && (d == 24) && (s == 8) && (n == sample_count)) { + config = c; + break; + } + } + + EGLint visual_id; + if (!eglGetConfigAttrib(_sapp.egl.display, config, EGL_NATIVE_VISUAL_ID, &visual_id)) { + _SAPP_PANIC(LINUX_EGL_NO_NATIVE_VISUAL); + } + + XVisualInfo visual_info_template; + _sapp_clear(&visual_info_template, sizeof(visual_info_template)); + visual_info_template.visualid = (VisualID)visual_id; + + int num_visuals; + XVisualInfo* visual_info = XGetVisualInfo(_sapp.x11.display, VisualIDMask, &visual_info_template, &num_visuals); + if (!visual_info) { + _SAPP_PANIC(LINUX_EGL_GET_VISUAL_INFO_FAILED); + } + + _sapp_x11_create_window(visual_info->visual, visual_info->depth); + XFree(visual_info); + + _sapp.egl.surface = eglCreateWindowSurface(_sapp.egl.display, config, (EGLNativeWindowType)_sapp.x11.window, NULL); + if (EGL_NO_SURFACE == _sapp.egl.surface) { + _SAPP_PANIC(LINUX_EGL_CREATE_WINDOW_SURFACE_FAILED); + } + + EGLint ctx_attrs[] = { + EGL_CONTEXT_MAJOR_VERSION, _sapp.desc.gl_major_version, + EGL_CONTEXT_MINOR_VERSION, _sapp.desc.gl_minor_version, + #if defined(SOKOL_GLCORE) + EGL_CONTEXT_OPENGL_PROFILE_MASK, EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT, + #endif + EGL_NONE, + }; + + _sapp.egl.context = eglCreateContext(_sapp.egl.display, config, EGL_NO_CONTEXT, ctx_attrs); + if (EGL_NO_CONTEXT == _sapp.egl.context) { + _SAPP_PANIC(LINUX_EGL_CREATE_CONTEXT_FAILED); + } + + if (!eglMakeCurrent(_sapp.egl.display, _sapp.egl.surface, _sapp.egl.surface, _sapp.egl.context)) { + _SAPP_PANIC(LINUX_EGL_MAKE_CURRENT_FAILED); + } + glGetIntegerv(GL_FRAMEBUFFER_BINDING, (GLint*)&_sapp.gl.framebuffer); + + eglSwapInterval(_sapp.egl.display, _sapp.swap_interval); +} + +_SOKOL_PRIVATE void _sapp_egl_destroy(void) { + if (_sapp.egl.display != EGL_NO_DISPLAY) { + eglMakeCurrent(_sapp.egl.display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + + if (_sapp.egl.context != EGL_NO_CONTEXT) { + eglDestroyContext(_sapp.egl.display, _sapp.egl.context); + _sapp.egl.context = EGL_NO_CONTEXT; + } + + if (_sapp.egl.surface != EGL_NO_SURFACE) { + eglDestroySurface(_sapp.egl.display, _sapp.egl.surface); + _sapp.egl.surface = EGL_NO_SURFACE; + } + + eglTerminate(_sapp.egl.display); + _sapp.egl.display = EGL_NO_DISPLAY; + } +} + +#endif // _SAPP_EGL + +_SOKOL_PRIVATE void _sapp_linux_frame(void) { + _sapp_x11_update_dimensions_from_window_size(); + #if defined(SOKOL_WGPU) + _sapp_wgpu_frame(); + #else + _sapp_frame(); + #if defined(_SAPP_GLX) + _sapp_glx_swap_buffers(); + #elif defined(_SAPP_EGL) + eglSwapBuffers(_sapp.egl.display, _sapp.egl.surface); + #endif + #endif +} + +_SOKOL_PRIVATE void _sapp_linux_run(const sapp_desc* desc) { + /* The following lines are here to trigger a linker error instead of an + obscure runtime error if the user has forgotten to add -pthread to + the compiler or linker options. They have no other purpose. + */ + pthread_attr_t pthread_attr; + pthread_attr_init(&pthread_attr); + pthread_attr_destroy(&pthread_attr); + + _sapp_init_state(desc); + _sapp.x11.window_state = NormalState; + + XInitThreads(); + XrmInitialize(); + _sapp.x11.display = XOpenDisplay(NULL); + if (!_sapp.x11.display) { + _SAPP_PANIC(LINUX_X11_OPEN_DISPLAY_FAILED); + } + _sapp.x11.screen = DefaultScreen(_sapp.x11.display); + _sapp.x11.root = DefaultRootWindow(_sapp.x11.display); + _sapp_x11_query_system_dpi(); + // NOTE: on Linux system-window-size to frame-buffer-size mapping is always 1:1 + _sapp.dpi_scale = _sapp.x11.dpi / 96.0f; + _sapp_x11_init_extensions(); + _sapp_x11_create_standard_cursors(); + XkbSetDetectableAutoRepeat(_sapp.x11.display, true, NULL); + _sapp_x11_init_keytable(); + #if defined(_SAPP_GLX) + _sapp_glx_init(); + Visual* visual = 0; + int depth = 0; + _sapp_glx_choose_visual(&visual, &depth); + _sapp_x11_create_window(visual, depth); + _sapp_glx_create_context(); + _sapp_glx_swapinterval(_sapp.swap_interval); + #elif defined(_SAPP_EGL) + _sapp_egl_init(); + #elif defined(SOKOL_WGPU) + _sapp_x11_create_window(0, 0); + _sapp_wgpu_init(); + #endif + sapp_set_icon(&desc->icon); + _sapp.valid = true; + _sapp_x11_show_window(); + if (_sapp.fullscreen) { + _sapp_x11_set_fullscreen(true); + } + + XFlush(_sapp.x11.display); + while (!_sapp.quit_ordered) { + _sapp_timing_measure(&_sapp.timing); + int count = XPending(_sapp.x11.display); + while (count--) { + XEvent event; + XNextEvent(_sapp.x11.display, &event); + _sapp_x11_process_event(&event); + } + _sapp_linux_frame(); + XFlush(_sapp.x11.display); + // handle quit-requested, either from window or from sapp_request_quit() + if (_sapp.quit_requested && !_sapp.quit_ordered) { + // give user code a chance to intervene + _sapp_x11_app_event(SAPP_EVENTTYPE_QUIT_REQUESTED); + /* if user code hasn't intervened, quit the app */ + if (_sapp.quit_requested) { + _sapp.quit_ordered = true; + } + } + } + _sapp_call_cleanup(); + #if defined(_SAPP_GLX) + _sapp_glx_destroy_context(); + #elif defined(_SAPP_EGL) + _sapp_egl_destroy(); + #elif defined(SOKOL_WGPU) + _sapp_wgpu_discard(); + #endif + _sapp_x11_destroy_window(); + _sapp_x11_destroy_standard_cursors(); + XCloseDisplay(_sapp.x11.display); + _sapp_discard_state(); +} + +#if !defined(SOKOL_NO_ENTRY) +int main(int argc, char* argv[]) { + sapp_desc desc = sokol_main(argc, argv); + _sapp_linux_run(&desc); + return 0; +} +#endif /* SOKOL_NO_ENTRY */ +#endif /* _SAPP_LINUX */ + +// ██████ ██ ██ ██████ ██ ██ ██████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██████ ██ ██ ██████ ██ ██ ██ +// ██ ██ ██ ██ ██ ██ ██ ██ +// ██ ██████ ██████ ███████ ██ ██████ +// +// >>public +#if defined(SOKOL_NO_ENTRY) +SOKOL_API_IMPL void sapp_run(const sapp_desc* desc) { + SOKOL_ASSERT(desc); + #if defined(_SAPP_MACOS) + _sapp_macos_run(desc); + #elif defined(_SAPP_IOS) + _sapp_ios_run(desc); + #elif defined(_SAPP_EMSCRIPTEN) + _sapp_emsc_run(desc); + #elif defined(_SAPP_WIN32) + _sapp_win32_run(desc); + #elif defined(_SAPP_LINUX) + _sapp_linux_run(desc); + #else + #error "sapp_run() not supported on this platform" + #endif +} + +/* this is just a stub so the linker doesn't complain */ +sapp_desc sokol_main(int argc, char* argv[]) { + _SOKOL_UNUSED(argc); + _SOKOL_UNUSED(argv); + sapp_desc desc; + _sapp_clear(&desc, sizeof(desc)); + return desc; +} +#else +/* likewise, in normal mode, sapp_run() is just an empty stub */ +SOKOL_API_IMPL void sapp_run(const sapp_desc* desc) { + _SOKOL_UNUSED(desc); +} +#endif + +SOKOL_API_IMPL bool sapp_isvalid(void) { + return _sapp.valid; +} + +SOKOL_API_IMPL void* sapp_userdata(void) { + return _sapp.desc.user_data; +} + +SOKOL_API_IMPL sapp_desc sapp_query_desc(void) { + return _sapp.desc; +} + +SOKOL_API_IMPL uint64_t sapp_frame_count(void) { + return _sapp.frame_count; +} + +SOKOL_API_IMPL double sapp_frame_duration(void) { + return _sapp_timing_get_avg(&_sapp.timing); +} + +SOKOL_API_IMPL int sapp_width(void) { + return (_sapp.framebuffer_width > 0) ? _sapp.framebuffer_width : 1; +} + +SOKOL_API_IMPL float sapp_widthf(void) { + return (float)sapp_width(); +} + +SOKOL_API_IMPL int sapp_height(void) { + return (_sapp.framebuffer_height > 0) ? _sapp.framebuffer_height : 1; +} + +SOKOL_API_IMPL float sapp_heightf(void) { + return (float)sapp_height(); +} + +SOKOL_API_IMPL int sapp_color_format(void) { + #if defined(SOKOL_WGPU) + switch (_sapp.wgpu.render_format) { + case WGPUTextureFormat_RGBA8Unorm: + return _SAPP_PIXELFORMAT_RGBA8; + case WGPUTextureFormat_BGRA8Unorm: + return _SAPP_PIXELFORMAT_BGRA8; + default: + SOKOL_UNREACHABLE; + return 0; + } + #elif defined(SOKOL_METAL) || defined(SOKOL_D3D11) + return _SAPP_PIXELFORMAT_BGRA8; + #else + return _SAPP_PIXELFORMAT_RGBA8; + #endif +} + +SOKOL_API_IMPL int sapp_depth_format(void) { + return _SAPP_PIXELFORMAT_DEPTH_STENCIL; +} + +SOKOL_API_IMPL int sapp_sample_count(void) { + return _sapp.sample_count; +} + +SOKOL_API_IMPL bool sapp_high_dpi(void) { + return _sapp.desc.high_dpi && (_sapp.dpi_scale >= 1.5f); +} + +SOKOL_API_IMPL float sapp_dpi_scale(void) { + return _sapp.dpi_scale; +} + +SOKOL_API_IMPL const void* sapp_egl_get_display(void) { + SOKOL_ASSERT(_sapp.valid); + #if defined(_SAPP_ANDROID) + return _sapp.android.display; + #elif defined(_SAPP_LINUX) && defined(_SAPP_EGL) + return _sapp.egl.display; + #else + return 0; + #endif +} + +SOKOL_API_IMPL const void* sapp_egl_get_context(void) { + SOKOL_ASSERT(_sapp.valid); + #if defined(_SAPP_ANDROID) + return _sapp.android.context; + #elif defined(_SAPP_LINUX) && defined(_SAPP_EGL) + return _sapp.egl.context; + #else + return 0; + #endif +} + +SOKOL_API_IMPL void sapp_show_keyboard(bool show) { + #if defined(_SAPP_IOS) + _sapp_ios_show_keyboard(show); + #elif defined(_SAPP_ANDROID) + _sapp_android_show_keyboard(show); + #else + _SOKOL_UNUSED(show); + #endif +} + +SOKOL_API_IMPL bool sapp_keyboard_shown(void) { + return _sapp.onscreen_keyboard_shown; +} + +SOKOL_API_IMPL bool sapp_is_fullscreen(void) { + return _sapp.fullscreen; +} + +SOKOL_API_IMPL void sapp_toggle_fullscreen(void) { + #if defined(_SAPP_MACOS) + _sapp_macos_toggle_fullscreen(); + #elif defined(_SAPP_WIN32) + _sapp_win32_toggle_fullscreen(); + #elif defined(_SAPP_LINUX) + _sapp_x11_toggle_fullscreen(); + #elif defined(_SAPP_EMSCRIPTEN) + _sapp_emsc_toggle_fullscreen(); + #endif +} + +_SOKOL_PRIVATE void _sapp_update_cursor(sapp_mouse_cursor cursor, bool shown) { + #if defined(_SAPP_MACOS) + _sapp_macos_update_cursor(cursor, shown); + #elif defined(_SAPP_WIN32) + _sapp_win32_update_cursor(cursor, shown, false); + #elif defined(_SAPP_LINUX) + _sapp_x11_update_cursor(cursor, shown); + #elif defined(_SAPP_EMSCRIPTEN) + _sapp_emsc_update_cursor(cursor, shown); + #endif + _sapp.mouse.current_cursor = cursor; + _sapp.mouse.shown = shown; +} + +/* NOTE that sapp_show_mouse() does not "stack" like the Win32 or macOS API functions! */ +SOKOL_API_IMPL void sapp_show_mouse(bool show) { + if (_sapp.mouse.shown != show) { + _sapp_update_cursor(_sapp.mouse.current_cursor, show); + } +} + +SOKOL_API_IMPL bool sapp_mouse_shown(void) { + return _sapp.mouse.shown; +} + +SOKOL_API_IMPL void sapp_lock_mouse(bool lock) { + #if defined(_SAPP_MACOS) + _sapp_macos_lock_mouse(lock); + #elif defined(_SAPP_EMSCRIPTEN) + _sapp_emsc_lock_mouse(lock); + #elif defined(_SAPP_WIN32) + _sapp_win32_lock_mouse(lock); + #elif defined(_SAPP_LINUX) + _sapp_x11_lock_mouse(lock); + #else + _sapp.mouse.locked = lock; + #endif +} + +SOKOL_API_IMPL bool sapp_mouse_locked(void) { + return _sapp.mouse.locked; +} + +SOKOL_API_IMPL void sapp_set_mouse_cursor(sapp_mouse_cursor cursor) { + SOKOL_ASSERT((cursor >= 0) && (cursor < _SAPP_MOUSECURSOR_NUM)); + if (_sapp.mouse.current_cursor != cursor) { + _sapp_update_cursor(cursor, _sapp.mouse.shown); + } +} + +SOKOL_API_IMPL sapp_mouse_cursor sapp_get_mouse_cursor(void) { + return _sapp.mouse.current_cursor; +} + +SOKOL_API_IMPL sapp_mouse_cursor sapp_bind_mouse_cursor_image(sapp_mouse_cursor cursor, const sapp_image_desc* desc) { + SOKOL_ASSERT((cursor >= 0) && (cursor < _SAPP_MOUSECURSOR_NUM)); + // NOTE: It seems that for some reason, the hotspot doesn't work if it is one less + // than the dimension of the cursor image (or more), on windows. So for a cursor + // that is 32 by 32 px, a hotspot of x = 30 works, but not x = 31. + // The cursor simply dissapears in such cases. Asserting for all platforms to make + // the behaviour consistent. + SOKOL_ASSERT(desc->cursor_hotspot_x < desc->width - 1 && desc->cursor_hotspot_y < desc->height - 1); + SOKOL_ASSERT(desc->width * desc->height * 4 == (int) desc->pixels.size); + + sapp_unbind_mouse_cursor_image(cursor); + + bool res = false; + #if defined(_SAPP_MACOS) + res = _sapp_macos_make_custom_mouse_cursor(cursor, desc); + #elif defined(_SAPP_EMSCRIPTEN) + res = _sapp_emsc_make_custom_mouse_cursor(cursor, desc); + #elif defined(_SAPP_WIN32) + res = _sapp_win32_make_custom_mouse_cursor(cursor, desc); + #elif defined(_SAPP_LINUX) + res = _sapp_x11_make_custom_mouse_cursor(cursor, desc); + #else + _SOKOL_UNUSED(desc); + #endif + _sapp.custom_cursor_bound[(int)cursor] = res; + + // Update the displayed cursor in case the current cursor is the one we just bound. + if (_sapp.mouse.current_cursor == cursor) { + _sapp_update_cursor(cursor, _sapp.mouse.shown); + } + return cursor; // returning the passed-in cursor puerly for convenience, in case you want to asign the value to a variable. +} + +SOKOL_APP_API_DECL void sapp_unbind_mouse_cursor_image(sapp_mouse_cursor cursor) { + SOKOL_ASSERT((cursor >= 0) && (cursor < _SAPP_MOUSECURSOR_NUM)); + if (_sapp.custom_cursor_bound[(int)cursor]) { + // if this is the active cursor, first restore it to its default image, + // this must be done before attempting to destroy any cursor image + // resources which at least on win32 would fail if the cursor is still in use + _sapp.custom_cursor_bound[(int)cursor] = false; + if (_sapp.mouse.current_cursor == cursor) { + _sapp_update_cursor(cursor, _sapp.mouse.shown); + } + #if defined(_SAPP_MACOS) + _sapp_macos_destroy_custom_mouse_cursor(cursor); + #elif defined(_SAPP_EMSCRIPTEN) + _sapp_emsc_destroy_custom_mouse_cursor(cursor); + #elif defined(_SAPP_WIN32) + _sapp_win32_destroy_custom_mouse_cursor(cursor); + #elif defined(_SAPP_LINUX) + _sapp_x11_destroy_custom_mouse_cursor(cursor); + #endif + } +} + +SOKOL_API_IMPL void sapp_request_quit(void) { + _sapp.quit_requested = true; +} + +SOKOL_API_IMPL void sapp_cancel_quit(void) { + _sapp.quit_requested = false; +} + +SOKOL_API_IMPL void sapp_quit(void) { + _sapp.quit_ordered = true; +} + +SOKOL_API_IMPL void sapp_consume_event(void) { + _sapp.event_consumed = true; +} + +/* NOTE: on HTML5, sapp_set_clipboard_string() must be called from within event handler! */ +SOKOL_API_IMPL void sapp_set_clipboard_string(const char* str) { + if (!_sapp.clipboard.enabled) { + return; + } + SOKOL_ASSERT(str); + #if defined(_SAPP_MACOS) + _sapp_macos_set_clipboard_string(str); + #elif defined(_SAPP_EMSCRIPTEN) + _sapp_emsc_set_clipboard_string(str); + #elif defined(_SAPP_WIN32) + _sapp_win32_set_clipboard_string(str); + #elif defined(_SAPP_LINUX) + _sapp_x11_set_clipboard_string(str); + #else + /* not implemented */ + #endif + _sapp_strcpy(str, _sapp.clipboard.buffer, (size_t)_sapp.clipboard.buf_size); +} + +SOKOL_API_IMPL const char* sapp_get_clipboard_string(void) { + if (!_sapp.clipboard.enabled) { + return ""; + } + #if defined(_SAPP_MACOS) + return _sapp_macos_get_clipboard_string(); + #elif defined(_SAPP_EMSCRIPTEN) + return _sapp.clipboard.buffer; + #elif defined(_SAPP_WIN32) + return _sapp_win32_get_clipboard_string(); + #elif defined(_SAPP_LINUX) + return _sapp_x11_get_clipboard_string(); + #else + /* not implemented */ + return _sapp.clipboard.buffer; + #endif +} + +SOKOL_API_IMPL void sapp_set_window_title(const char* title) { + SOKOL_ASSERT(title); + _sapp_strcpy(title, _sapp.window_title, sizeof(_sapp.window_title)); + #if defined(_SAPP_MACOS) + _sapp_macos_update_window_title(); + #elif defined(_SAPP_WIN32) + _sapp_win32_update_window_title(); + #elif defined(_SAPP_LINUX) + _sapp_x11_update_window_title(); + #endif +} + +SOKOL_API_IMPL void sapp_set_icon(const sapp_icon_desc* desc) { + SOKOL_ASSERT(desc); + if (desc->sokol_default) { + if (0 == _sapp.default_icon_pixels) { + _sapp_setup_default_icon(); + } + SOKOL_ASSERT(0 != _sapp.default_icon_pixels); + desc = &_sapp.default_icon_desc; + } + const int num_images = _sapp_icon_num_images(desc); + if (num_images == 0) { + return; + } + SOKOL_ASSERT((num_images > 0) && (num_images <= SAPP_MAX_ICONIMAGES)); + if (!_sapp_validate_icon_desc(desc, num_images)) { + return; + } + #if defined(_SAPP_MACOS) + _sapp_macos_set_icon(desc, num_images); + #elif defined(_SAPP_WIN32) + _sapp_win32_set_icon(desc, num_images); + #elif defined(_SAPP_LINUX) + _sapp_x11_set_icon(desc, num_images); + #elif defined(_SAPP_EMSCRIPTEN) + _sapp_emsc_set_icon(desc, num_images); + #endif +} + +SOKOL_API_IMPL int sapp_get_num_dropped_files(void) { + SOKOL_ASSERT(_sapp.drop.enabled); + return _sapp.drop.num_files; +} + +SOKOL_API_IMPL const char* sapp_get_dropped_file_path(int index) { + SOKOL_ASSERT(_sapp.drop.enabled); + SOKOL_ASSERT((index >= 0) && (index < _sapp.drop.num_files)); + SOKOL_ASSERT(_sapp.drop.buffer); + if (!_sapp.drop.enabled) { + return ""; + } + if ((index < 0) || (index >= _sapp.drop.max_files)) { + return ""; + } + return (const char*) _sapp_dropped_file_path_ptr(index); +} + +SOKOL_API_IMPL uint32_t sapp_html5_get_dropped_file_size(int index) { + SOKOL_ASSERT(_sapp.drop.enabled); + SOKOL_ASSERT((index >= 0) && (index < _sapp.drop.num_files)); + #if defined(_SAPP_EMSCRIPTEN) + if (!_sapp.drop.enabled) { + return 0; + } + return sapp_js_dropped_file_size(index); + #else + (void)index; + return 0; + #endif +} + +SOKOL_API_IMPL void sapp_html5_fetch_dropped_file(const sapp_html5_fetch_request* request) { + SOKOL_ASSERT(_sapp.drop.enabled); + SOKOL_ASSERT(request); + SOKOL_ASSERT(request->callback); + SOKOL_ASSERT(request->buffer.ptr); + SOKOL_ASSERT(request->buffer.size > 0); + #if defined(_SAPP_EMSCRIPTEN) + const int index = request->dropped_file_index; + sapp_html5_fetch_error error_code = SAPP_HTML5_FETCH_ERROR_NO_ERROR; + if ((index < 0) || (index >= _sapp.drop.num_files)) { + error_code = SAPP_HTML5_FETCH_ERROR_OTHER; + } + if (sapp_html5_get_dropped_file_size(index) > request->buffer.size) { + error_code = SAPP_HTML5_FETCH_ERROR_BUFFER_TOO_SMALL; + } + if (SAPP_HTML5_FETCH_ERROR_NO_ERROR != error_code) { + _sapp_emsc_invoke_fetch_cb(index, + false, // success + (int)error_code, + request->callback, + 0, // fetched_size + (void*)request->buffer.ptr, + request->buffer.size, + request->user_data); + } else { + sapp_js_fetch_dropped_file(index, + request->callback, + (void*)request->buffer.ptr, + request->buffer.size, + request->user_data); + } + #else + (void)request; + #endif +} + +SOKOL_API_IMPL const void* sapp_metal_get_device(void) { + SOKOL_ASSERT(_sapp.valid); + #if defined(SOKOL_METAL) + #if defined(_SAPP_MACOS) + const void* obj = (__bridge const void*) _sapp.macos.mtl_device; + #else + const void* obj = (__bridge const void*) _sapp.ios.mtl_device; + #endif + SOKOL_ASSERT(obj); + return obj; + #else + return 0; + #endif +} + +SOKOL_API_IMPL const void* sapp_metal_get_current_drawable(void) { + SOKOL_ASSERT(_sapp.valid); + #if defined(SOKOL_METAL) + #if defined(_SAPP_MACOS) + const void* obj = (__bridge const void*) [_sapp.macos.view currentDrawable]; + #else + const void* obj = (__bridge const void*) [_sapp.ios.view currentDrawable]; + #endif + SOKOL_ASSERT(obj); + return obj; + #else + return 0; + #endif +} + +SOKOL_API_IMPL const void* sapp_metal_get_depth_stencil_texture(void) { + SOKOL_ASSERT(_sapp.valid); + #if defined(SOKOL_METAL) + #if defined(_SAPP_MACOS) + const void* obj = (__bridge const void*) [_sapp.macos.view depthStencilTexture]; + #else + const void* obj = (__bridge const void*) [_sapp.ios.view depthStencilTexture]; + #endif + return obj; + #else + return 0; + #endif +} + +SOKOL_API_IMPL const void* sapp_metal_get_msaa_color_texture(void) { + SOKOL_ASSERT(_sapp.valid); + #if defined(SOKOL_METAL) + #if defined(_SAPP_MACOS) + const void* obj = (__bridge const void*) [_sapp.macos.view multisampleColorTexture]; + #else + const void* obj = (__bridge const void*) [_sapp.ios.view multisampleColorTexture]; + #endif + return obj; + #else + return 0; + #endif +} + +SOKOL_API_IMPL const void* sapp_macos_get_window(void) { + #if defined(_SAPP_MACOS) + const void* obj = (__bridge const void*) _sapp.macos.window; + SOKOL_ASSERT(obj); + return obj; + #else + return 0; + #endif +} + +SOKOL_API_IMPL const void* sapp_ios_get_window(void) { + #if defined(_SAPP_IOS) + const void* obj = (__bridge const void*) _sapp.ios.window; + SOKOL_ASSERT(obj); + return obj; + #else + return 0; + #endif +} + +SOKOL_API_IMPL const void* sapp_d3d11_get_device(void) { + SOKOL_ASSERT(_sapp.valid); + #if defined(SOKOL_D3D11) + return _sapp.d3d11.device; + #else + return 0; + #endif +} + +SOKOL_API_IMPL const void* sapp_d3d11_get_device_context(void) { + SOKOL_ASSERT(_sapp.valid); + #if defined(SOKOL_D3D11) + return _sapp.d3d11.device_context; + #else + return 0; + #endif +} + +SOKOL_API_IMPL const void* sapp_d3d11_get_swap_chain(void) { + SOKOL_ASSERT(_sapp.valid); +#if defined(SOKOL_D3D11) + return _sapp.d3d11.swap_chain; +#else + return 0; +#endif +} + +SOKOL_API_IMPL const void* sapp_d3d11_get_render_view(void) { + SOKOL_ASSERT(_sapp.valid); + #if defined(SOKOL_D3D11) + if (_sapp.sample_count > 1) { + SOKOL_ASSERT(_sapp.d3d11.msaa_rtv); + return _sapp.d3d11.msaa_rtv; + } else { + SOKOL_ASSERT(_sapp.d3d11.rtv); + return _sapp.d3d11.rtv; + } + #else + return 0; + #endif +} + +SOKOL_API_IMPL const void* sapp_d3d11_get_resolve_view(void) { + SOKOL_ASSERT(_sapp.valid); + #if defined(SOKOL_D3D11) + if (_sapp.sample_count > 1) { + SOKOL_ASSERT(_sapp.d3d11.rtv); + return _sapp.d3d11.rtv; + } else { + return 0; + } + #else + return 0; + #endif +} + +SOKOL_API_IMPL const void* sapp_d3d11_get_depth_stencil_view(void) { + SOKOL_ASSERT(_sapp.valid); + #if defined(SOKOL_D3D11) + return _sapp.d3d11.dsv; + #else + return 0; + #endif +} + +SOKOL_API_IMPL const void* sapp_win32_get_hwnd(void) { + SOKOL_ASSERT(_sapp.valid); + #if defined(_SAPP_WIN32) + return _sapp.win32.hwnd; + #else + return 0; + #endif +} + +SOKOL_API_IMPL const void* sapp_wgpu_get_device(void) { + SOKOL_ASSERT(_sapp.valid); + #if defined(SOKOL_WGPU) + return (const void*) _sapp.wgpu.device; + #else + return 0; + #endif +} + +SOKOL_API_IMPL const void* sapp_wgpu_get_render_view(void) { + SOKOL_ASSERT(_sapp.valid); + #if defined(SOKOL_WGPU) + if (_sapp.sample_count > 1) { + SOKOL_ASSERT(_sapp.wgpu.msaa_view); + return (const void*) _sapp.wgpu.msaa_view; + } else { + SOKOL_ASSERT(_sapp.wgpu.swapchain_view); + return (const void*) _sapp.wgpu.swapchain_view; + } + #else + return 0; + #endif +} + +SOKOL_API_IMPL const void* sapp_wgpu_get_resolve_view(void) { + SOKOL_ASSERT(_sapp.valid); + #if defined(SOKOL_WGPU) + if (_sapp.sample_count > 1) { + SOKOL_ASSERT(_sapp.wgpu.swapchain_view); + return (const void*) _sapp.wgpu.swapchain_view; + } else { + return 0; + } + #else + return 0; + #endif +} + +SOKOL_API_IMPL const void* sapp_wgpu_get_depth_stencil_view(void) { + SOKOL_ASSERT(_sapp.valid); + #if defined(SOKOL_WGPU) + return (const void*) _sapp.wgpu.depth_stencil_view; + #else + return 0; + #endif +} + +SOKOL_API_IMPL uint32_t sapp_gl_get_framebuffer(void) { + SOKOL_ASSERT(_sapp.valid); + #if defined(_SAPP_ANY_GL) + return _sapp.gl.framebuffer; + #else + return 0; + #endif +} + +SOKOL_API_IMPL int sapp_gl_get_major_version(void) { + SOKOL_ASSERT(_sapp.valid); + #if defined(_SAPP_ANY_GL) + return _sapp.desc.gl_major_version; + #else + return 0; + #endif +} + +SOKOL_API_IMPL int sapp_gl_get_minor_version(void) { + SOKOL_ASSERT(_sapp.valid); + #if defined(_SAPP_ANY_GL) + return _sapp.desc.gl_minor_version; + #else + return 0; + #endif +} + +SOKOL_API_IMPL bool sapp_gl_is_gles(void) { + #if defined(SOKOL_GLES3) + return true; + #else + return false; + #endif +} + +SOKOL_API_IMPL const void* sapp_x11_get_window(void) { + #if defined(_SAPP_LINUX) + return (void*)_sapp.x11.window; + #else + return 0; + #endif +} + +SOKOL_API_IMPL const void* sapp_x11_get_display(void) { + #if defined(_SAPP_LINUX) + return (void*)_sapp.x11.display; + #else + return 0; + #endif +} + +SOKOL_API_IMPL const void* sapp_android_get_native_activity(void) { + // NOTE: _sapp.valid is not asserted here because sapp_android_get_native_activity() + // needs to be callable from within sokol_main() (see: https://github.com/floooh/sokol/issues/708) + #if defined(_SAPP_ANDROID) + return (void*)_sapp.android.activity; + #else + return 0; + #endif +} + +SOKOL_API_IMPL void sapp_html5_ask_leave_site(bool ask) { + _sapp.html5_ask_leave_site = ask; +} + +#endif /* SOKOL_APP_IMPL */ diff --git a/third-party/sokol/sokol_clay.h b/third-party/sokol/sokol_clay.h new file mode 100644 index 0000000..58f04ac --- /dev/null +++ b/third-party/sokol/sokol_clay.h @@ -0,0 +1,611 @@ +#ifndef SOKOL_CLAY_INCLUDED +#define SOKOL_CLAY_INCLUDED (1) +/* + sokol_clay.h -- drop-in Clay renderer for sokol_gfx.h + + Do this: + #define SOKOL_CLAY_IMPL + + before you include this file in *one* C file to create the + implementation. + + Optionally provide the following configuration define both before including the + the declaration and implementation: + + SOKOL_CLAY_NO_SOKOL_APP - don't depend on sokol_app.h (see below for details) + + Include the following headers before sokol_clay.h (both before including + the declaration and implementation): + + sokol_gl.h + sokol_fontstash.h + sokol_app.h (except SOKOL_CLAY_NO_SOKOL_APP) + clay.h + + FEATURE OVERVIEW: + ================= + sokol_clay.h implements the rendering and event-handling code for Clay + (https://github.com/nicbarker/clay) on top of sokol_gl.h and (optionally) + sokol_app.h. + + Since sokol_fontstash.h already depends on sokol_gl.h, the rendering is + implemented using sokol_gl calls. (TODO: make fontstash optional?) + + The sokol_app.h dependency is optional and used for input event handling. + If you only use sokol_gfx.h but not sokol_app.h in your application, + define SOKOL_CLAY_NO_SOKOL_APP before including the implementation + of sokol_clay.h, this will remove any dependency to sokol_app.h, but + you must call sclay_set_layout_dimensions and handle input yourself. + + sokol_clay.h is not thread-safe, all calls must be made from the + same thread where sokol_gfx.h is running. + + HOWTO: + ====== + + --- To initialize sokol-clay, call sclay_setup(). This can be done + before or after Clay_Initialize. + + --- Create an array of sclay_font_t and fill it by calling one of: + + sclay_font_t sclay_add_font(const char *filename); + sclay_font_t sclay_add_font_mem(unsigned char *data, int dataLen); + + The fontId value in Clay corresponds to indices in this array. After calling + Clay_Initialize but before calling any layout code, do this: + + Clay_SetMeasureTextFunction(sclay_measure_text, &fonts); + + where `fonts` is the abovementioned array. + + --- At the start of a frame, call sclay_new_frame() if you're using sokol_app.h. + If you're not using sokol_app.h, call: + + void sclay_set_layout_dimensions(Clay_Dimensions size, float dpi_scale); + + at the start of the frame (or just when the window is resized.) + + Either way, do some layout, then at the end of the frame call sclay_render: + + sg_begin_pass(...) + // other rendering... + sclay_render(renderCommands, &fonts); + // other rendering... + sgl_draw(); + sg_end_pass(); + sg_commit(); + + One caveat: sclay_render assumes the default gl view matrix, and handles scaling + automatically. If you've adjusted the view matrix, remember to first call: + + sgl_matrix_mode_modelview(); + sgl_load_identity(); + + before calling sclay_render. + + --- if you're using sokol_app.h, from inside the sokol_app.h event callback, + call: + + void sclay_handle_event(const sapp_event* ev); + + Unfortunately Clay does not currently provide feedback on whether a mouse + click was handled or not. + + --- if you want to use images with clay, you should pass a pointer to a + sclay_image to the CLAY macro, like this: + CLAY({ + ... + .image = { .imageData = &(sclay_image){ .view = view, .sampler = 0 } }, + }) + Using 0 as a sampler uses the sokol default sampler with linear interpolation. + The image should be created using sg_make_image from sokol_gfx. + + --- finally, on application shutdown, call + + sclay_shutdown() + */ +#if !defined(SOKOL_CLAY_NO_SOKOL_APP) && !defined(SOKOL_APP_INCLUDED) +#error "Please include sokol_app.h before sokol_clay.h (or define SOKOL_CLAY_NO_SOKOL_APP)" +#endif + +typedef int sclay_font_t; + +typedef struct sclay_image { + sg_view view; + sg_sampler sampler; + struct { + float u0, v0, u1, v1; + } uv; +} sclay_image; + +typedef void (*sclay_custom_element_cb_t)(Clay_BoundingBox bbox, Clay_CustomRenderData *config); + +void sclay_setup(); +void sclay_shutdown(); + +sclay_font_t sclay_add_font(const char *filename); +sclay_font_t sclay_add_font_mem(unsigned char *data, int dataLen); +void sclay_set_custom_element_cb(sclay_custom_element_cb_t); +Clay_Dimensions sclay_measure_text(Clay_StringSlice text, Clay_TextElementConfig *config, void *userData); + +#ifndef SOKOL_CLAY_NO_SOKOL_APP +void sclay_new_frame(); +void sclay_handle_event(const sapp_event *ev); +#endif /* SOKOL_CLAY_NO_SOKOL_APP */ + +/* Use this if you don't call sclay_new_frame. `size` is the "virtual" size which + * your layout is relative to (ie. the actual framebuffer size divided by dpi_scale.) + * Set dpi_scale to 1 if you're not using high-dpi support. */ +void sclay_set_layout_dimensions(Clay_Dimensions size, float dpi_scale); + +void sclay_render(Clay_RenderCommandArray renderCommands, sclay_font_t *fonts); + +#endif /* SOKOL_CLAY_INCLUDED */ + +#ifdef SOKOL_CLAY_IMPL +#define SOKOL_CLAY_IMPL_INCLUDED (1) +#ifndef SOKOL_GL_INCLUDED +#error "Please include sokol_gl.h before sokol_clay.h" +#endif +#ifndef SOKOL_FONTSTASH_INCLUDED +#error "Please include sokol_fontstash.h before sokol_clay.h" +#endif +#ifndef CLAY_HEADER +#error "Please include clay.h before sokol_clay.h" +#endif + +typedef struct { + sgl_pipeline pip; +#ifndef SOKOL_CLAY_NO_SOKOL_APP + Clay_Vector2 mouse_pos, scroll; + bool mouse_down; +#endif + Clay_Dimensions size; + float dpi_scale; + FONScontext *fonts; + sclay_custom_element_cb_t custom_element_cb; +} _sclay_state_t; +static _sclay_state_t _sclay; + +void sclay_setup() { + _sclay.pip = sgl_make_pipeline(&(sg_pipeline_desc){ + .colors[0] = { + .blend = { + .enabled = true, + .src_factor_rgb = SG_BLENDFACTOR_SRC_ALPHA, + .dst_factor_rgb = SG_BLENDFACTOR_ONE_MINUS_SRC_ALPHA, + }, + } + }); +#ifndef SOKOL_CLAY_NO_SOKOL_APP + _sclay.mouse_pos = (Clay_Vector2){0, 0}; + _sclay.scroll = (Clay_Vector2){0, 0}; + _sclay.mouse_down = false; +#endif + _sclay.size = (Clay_Dimensions){1, 1}; + _sclay.dpi_scale = 1; + _sclay.fonts = sfons_create(&(sfons_desc_t){ 0 }); + //TODO clay error handler? +} + +void sclay_shutdown() { + sgl_destroy_pipeline(_sclay.pip); + sfons_destroy(_sclay.fonts); +} + +#ifndef SOKOL_CLAY_NO_SOKOL_APP +void sclay_handle_event(const sapp_event* ev) { + switch(ev->type){ + case SAPP_EVENTTYPE_MOUSE_MOVE: + _sclay.mouse_pos.x = ev->mouse_x / _sclay.dpi_scale; + _sclay.mouse_pos.y = ev->mouse_y / _sclay.dpi_scale; + break; + case SAPP_EVENTTYPE_MOUSE_DOWN: + _sclay.mouse_down = true; + break; + case SAPP_EVENTTYPE_MOUSE_UP: + _sclay.mouse_down = false; + break; + case SAPP_EVENTTYPE_MOUSE_SCROLL: + _sclay.scroll.x += ev->scroll_x; + _sclay.scroll.y += ev->scroll_y; + break; + default: break; + } +} + +void sclay_new_frame() { + sclay_set_layout_dimensions((Clay_Dimensions){ (float)sapp_width(), (float)sapp_height() }, + sapp_dpi_scale()); + Clay_SetPointerState(_sclay.mouse_pos, _sclay.mouse_down); + Clay_UpdateScrollContainers(true, _sclay.scroll, sapp_frame_duration()); + _sclay.scroll = (Clay_Vector2){0, 0}; +} +#endif /* SOKOL_CLAY_NO_SOKOL_APP */ + +void sclay_set_layout_dimensions(Clay_Dimensions size, float dpi_scale) { + size.width /= dpi_scale; + size.height /= dpi_scale; + _sclay.size = size; + if(_sclay.dpi_scale != dpi_scale){ + _sclay.dpi_scale = dpi_scale; + Clay_ResetMeasureTextCache(); + } + Clay_SetLayoutDimensions(size); +} + +sclay_font_t sclay_add_font(const char *filename) { + //TODO log something if we get FONS_INVALID + return fonsAddFont(_sclay.fonts, "", filename); +} + +sclay_font_t sclay_add_font_mem(unsigned char *data, int dataLen) { + //TODO log something if we get FONS_INVALID + return fonsAddFontMem(_sclay.fonts, "", data, dataLen, false); +} + +void sclay_set_custom_element_cb(sclay_custom_element_cb_t func) { + _sclay.custom_element_cb = func; +} + +Clay_Dimensions sclay_measure_text(Clay_StringSlice text, Clay_TextElementConfig *config, void *userData) { + sclay_font_t *fonts = (sclay_font_t *)userData; + if(!fonts) return (Clay_Dimensions){ 0 }; + fonsSetFont(_sclay.fonts, fonts[config->fontId]); + fonsSetSize(_sclay.fonts, config->fontSize * _sclay.dpi_scale); + fonsSetSpacing(_sclay.fonts, config->letterSpacing * _sclay.dpi_scale); + fonsSetAlign(_sclay.fonts, FONS_ALIGN_LEFT | FONS_ALIGN_TOP); + float ascent, descent, lineh; + fonsVertMetrics(_sclay.fonts, &ascent, &descent, &lineh); + return (Clay_Dimensions) { + .width = fonsTextBounds(_sclay.fonts, 0, 0, text.chars, text.chars + text.length, NULL) / _sclay.dpi_scale, + .height = (ascent - descent) / _sclay.dpi_scale + }; +} + +static void _draw_rect(float x, float y, float w, float h){ + sgl_v2f(x, y); + sgl_v2f(x, y); + sgl_v2f(x+w, y); + sgl_v2f(x, y+h); + sgl_v2f(x+w, y+h); + sgl_v2f(x+w, y+h); +} + +static void _draw_rect_textured(float x, float y, float w, float h, float u0, float v0, float u1, float v1){ + sgl_v2f_t2f(x, y, u0, v0); + sgl_v2f_t2f(x, y, u0, v0); + sgl_v2f_t2f(x+w, y, u1, v0); + sgl_v2f_t2f(x, y+h, u0, v1); + sgl_v2f_t2f(x+w, y+h, u1, v1); + sgl_v2f_t2f(x+w, y+h, u1, v1); +} + +static float _SIN[16] = { + 0.000000f, 0.104528f, 0.207912f, 0.309017f, + 0.406737f, 0.500000f, 0.587785f, 0.669131f, + 0.743145f, 0.809017f, 0.866025f, 0.913545f, + 0.951057f, 0.978148f, 0.994522f, 1.000000f, +}; + +/* rx,ry = radius */ +static void _draw_corner(float x, float y, float rx, float ry){ + x -= rx; + y -= ry; + sgl_v2f(x, y); + for(int i = 0; i < 16; ++i){ + sgl_v2f(x, y); + sgl_v2f(x+(rx*_SIN[15-i]), y+(ry*_SIN[i])); + } + sgl_v2f(x+(rx*_SIN[0]), y+(ry*_SIN[15])); +} + +static void _draw_corner_textured(float x, float y, float rx, float ry, float bx, float by, float bw, float bh, float u0, float v0, float u1, float v1) { + x -= rx; + y -= ry; +#define MAP_U(x) (u0+(((x)-bx)/bw)*(u1-u0)) +#define MAP_V(y) (v0+(((y)-by)/bh)*(v1-v0)) + sgl_v2f_t2f(x, y, MAP_U(x), MAP_V(y)); + for(int i = 0; i < 16; ++i){ + sgl_v2f_t2f(x, y, MAP_U(x), MAP_V(y)); + float px = x+(rx*_SIN[15-i]); + float py = y+(ry*_SIN[i]); + sgl_v2f_t2f(px, py, MAP_U(px), MAP_V(py)); + } + sgl_v2f_t2f(x+(rx*_SIN[0]), y+(ry*_SIN[15]), MAP_U(x+(rx*_SIN[0])), MAP_V(y+(ry*_SIN[15]))); +#undef MAP_U +#undef MAP_V +} + +/* rx,ry = radius ix,iy = inner radius */ +static void _draw_corner_border(float x, float y, float rx, float ry, float ix, float iy){ + x -= rx; + y -= ry; + sgl_v2f(x+(ix*_SIN[15]), y+(iy*_SIN[0])); + for(int i = 0; i < 16; ++i){ + sgl_v2f(x+(ix*_SIN[15-i]), y+(iy*_SIN[i])); + sgl_v2f(x+(rx*_SIN[15-i]), y+(ry*_SIN[i])); + } + sgl_v2f(x+(rx*_SIN[0]), y+(ry*_SIN[15])); +} + +void sclay_render(Clay_RenderCommandArray renderCommands, sclay_font_t *fonts) { + sgl_matrix_mode_modelview(); + sgl_translate(-1.0f, 1.0f, 0.0f); + sgl_scale(2.0f/_sclay.size.width, -2.0f/_sclay.size.height, 1.0f); + sgl_disable_texture(); + sgl_push_pipeline(); + sgl_load_pipeline(_sclay.pip); + for (int i = 0; i < renderCommands.length; i++) { + Clay_RenderCommand *renderCommand = Clay_RenderCommandArray_Get(&renderCommands, i); + Clay_BoundingBox bbox = renderCommand->boundingBox; + switch (renderCommand->commandType) { + case CLAY_RENDER_COMMAND_TYPE_RECTANGLE: { + Clay_RectangleRenderData *config = &renderCommand->renderData.rectangle; + sgl_c4f(config->backgroundColor.r / 255.0f, + config->backgroundColor.g / 255.0f, + config->backgroundColor.b / 255.0f, + config->backgroundColor.a / 255.0f); + Clay_CornerRadius r = config->cornerRadius; + sgl_begin_triangle_strip(); + if(r.topLeft > 0 || r.topRight > 0){ + _draw_corner(bbox.x, bbox.y, -r.topLeft, -r.topLeft); + _draw_corner(bbox.x+bbox.width, bbox.y, r.topRight, -r.topRight); + _draw_rect(bbox.x+r.topLeft, bbox.y, + bbox.width-r.topLeft-r.topRight, CLAY__MAX(r.topLeft, r.topRight)); + } + if(r.bottomLeft > 0 || r.bottomRight > 0){ + _draw_corner(bbox.x, bbox.y+bbox.height, -r.bottomLeft, r.bottomLeft); + _draw_corner(bbox.x+bbox.width, bbox.y+bbox.height, r.bottomRight, r.bottomRight); + _draw_rect(bbox.x+r.bottomLeft, + bbox.y+bbox.height-CLAY__MAX(r.bottomLeft, r.bottomRight), + bbox.width-r.bottomLeft-r.bottomRight, CLAY__MAX(r.bottomLeft, r.bottomRight)); + } + if(r.topLeft < r.bottomLeft){ + if(r.topLeft < r.topRight){ + _draw_rect(bbox.x, bbox.y+r.topLeft, r.topLeft, bbox.height-r.topLeft-r.bottomLeft); + _draw_rect(bbox.x+r.topLeft, bbox.y+r.topRight, + r.bottomLeft-r.topLeft, bbox.height-r.topRight-r.bottomLeft); + } else { + _draw_rect(bbox.x, bbox.y+r.topLeft, r.bottomLeft, bbox.height-r.topLeft-r.bottomLeft); + } + } else { + if(r.bottomLeft < r.bottomRight){ + _draw_rect(bbox.x, bbox.y+r.topLeft, r.bottomLeft, bbox.height-r.topLeft-r.bottomLeft); + _draw_rect(bbox.x+r.bottomLeft, bbox.y+r.topLeft, + r.topLeft-r.bottomLeft, bbox.height-r.topLeft-r.bottomRight); + } else { + _draw_rect(bbox.x, bbox.y+r.topLeft, r.topLeft, bbox.height-r.topLeft-r.bottomLeft); + } + } + if(r.topRight < r.bottomRight){ + if(r.topRight < r.topLeft){ + _draw_rect(bbox.x+bbox.width-r.bottomRight, bbox.y+r.topLeft, + r.bottomRight-r.topRight, bbox.height-r.topLeft-r.bottomRight); + _draw_rect(bbox.x+bbox.width-r.topRight, bbox.y+r.topRight, + r.topRight, bbox.height-r.topRight-r.bottomRight); + } else { + _draw_rect(bbox.x+bbox.width-r.bottomRight, bbox.y+r.topRight, + r.bottomRight, bbox.height-r.topRight-r.bottomRight); + } + } else { + if(r.bottomRight < r.bottomLeft){ + _draw_rect(bbox.x+bbox.width-r.topRight, bbox.y+r.topRight, + r.topRight-r.bottomRight, bbox.height-r.topRight-r.bottomLeft); + _draw_rect(bbox.x+bbox.width-r.bottomRight, bbox.y+r.topRight, + r.bottomRight, bbox.height-r.topRight-r.bottomRight); + } else { + _draw_rect(bbox.x+bbox.width-r.topRight, bbox.y+r.topRight, + r.topRight, bbox.height-r.topRight-r.bottomRight); + } + } + _draw_rect(bbox.x+CLAY__MAX(r.topLeft, r.bottomLeft), + bbox.y+CLAY__MAX(r.topLeft, r.topRight), + bbox.width-CLAY__MAX(r.topLeft, r.bottomLeft)-CLAY__MAX(r.topRight, r.bottomRight), + bbox.height-CLAY__MAX(r.topLeft, r.topRight)-CLAY__MAX(r.bottomLeft, r.bottomRight)); + sgl_end(); + break; + } + case CLAY_RENDER_COMMAND_TYPE_TEXT: { + if(!fonts) break; + Clay_TextRenderData *config = &renderCommand->renderData.text; + Clay_StringSlice text = config->stringContents; + fonsSetFont(_sclay.fonts, fonts[config->fontId]); + uint32_t color = sfons_rgba( + config->textColor.r, + config->textColor.g, + config->textColor.b, + config->textColor.a); + fonsSetColor(_sclay.fonts, color); + fonsSetSpacing(_sclay.fonts, config->letterSpacing * _sclay.dpi_scale); + fonsSetAlign(_sclay.fonts, FONS_ALIGN_LEFT | FONS_ALIGN_TOP); + fonsSetSize(_sclay.fonts, config->fontSize * _sclay.dpi_scale); + sgl_matrix_mode_modelview(); + sgl_push_matrix(); + sgl_scale(1.0f/_sclay.dpi_scale, 1.0f/_sclay.dpi_scale, 1.0f); + fonsDrawText(_sclay.fonts, bbox.x*_sclay.dpi_scale, bbox.y*_sclay.dpi_scale, + text.chars, text.chars + text.length); + sgl_pop_matrix(); + break; + } + case CLAY_RENDER_COMMAND_TYPE_SCISSOR_START: { + sgl_scissor_rectf(bbox.x*_sclay.dpi_scale, bbox.y*_sclay.dpi_scale, + bbox.width*_sclay.dpi_scale, bbox.height*_sclay.dpi_scale, + true); + break; + } + case CLAY_RENDER_COMMAND_TYPE_SCISSOR_END: { + sgl_scissor_rectf(0, 0, + _sclay.size.width*_sclay.dpi_scale, _sclay.size.height*_sclay.dpi_scale, + true); + break; + } + case CLAY_RENDER_COMMAND_TYPE_IMAGE: { + Clay_ImageRenderData *config = &renderCommand->renderData.image; + sclay_image* img = (sclay_image*)config->imageData; + // by default, u1 and v1 are 1. if we pass 0. + // note, we are modifying a copy ! + float u0 = img->uv.u0; + float v0 = img->uv.v0; + float u1 = img->uv.u1; + float v1 = img->uv.v1; + if (u1 == 0.f) { + u1 = 1.f; + } + if (v1 == 0.f) { + v1 = 1.f; + } + + int untinted = config->backgroundColor.r == 0 && config->backgroundColor.g == 0 && config->backgroundColor.b == 0 && config->backgroundColor.a == 0; + float cr = untinted ? 1.f : (config->backgroundColor.r / 255.0f); + float gr = untinted ? 1.f : (config->backgroundColor.g / 255.0f); + float br = untinted ? 1.f : (config->backgroundColor.b / 255.0f); + float ar = untinted ? 1.f : (config->backgroundColor.a / 255.0f); + + sgl_c4f(cr, gr, br, ar); + + Clay_CornerRadius r = config->cornerRadius; + + sgl_enable_texture(); + sgl_texture(img->view, img->sampler); + + sgl_begin_triangle_strip(); + if(r.topLeft > 0 || r.topRight > 0){ + _draw_corner_textured(bbox.x, bbox.y, -r.topLeft, -r.topLeft, bbox.x, bbox.y, bbox.width, bbox.height, u0, v0, u1, v1); + _draw_corner_textured(bbox.x+bbox.width, bbox.y, r.topRight, -r.topRight, bbox.x, bbox.y, bbox.width, bbox.height, u0, v0, u1, v1); + _draw_rect_textured(bbox.x+r.topLeft, bbox.y, + bbox.width-r.topLeft-r.topRight, CLAY__MAX(r.topLeft, r.topRight), + u0 + (r.topLeft/bbox.width)*(u1-u0), v0, u1 - (r.topRight/bbox.width)*(u1-u0), v0 + (CLAY__MAX(r.topLeft, r.topRight)/bbox.height)*(v1-v0)); + } + if(r.bottomLeft > 0 || r.bottomRight > 0){ + _draw_corner_textured(bbox.x, bbox.y+bbox.height, -r.bottomLeft, r.bottomLeft, bbox.x, bbox.y, bbox.width, bbox.height, u0, v0, u1, v1); + _draw_corner_textured(bbox.x+bbox.width, bbox.y+bbox.height, r.bottomRight, r.bottomRight, bbox.x, bbox.y, bbox.width, bbox.height, u0, v0, u1, v1); + _draw_rect_textured(bbox.x+r.bottomLeft, + bbox.y+bbox.height-CLAY__MAX(r.bottomLeft, r.bottomRight), + bbox.width-r.bottomLeft-r.bottomRight, CLAY__MAX(r.bottomLeft, r.bottomRight), + u0 + (r.bottomLeft/bbox.width)*(u1-u0), v1 - (CLAY__MAX(r.bottomLeft, r.bottomRight)/bbox.height)*(v1-v0), u1 - (r.bottomRight/bbox.width)*(u1-u0), v1); + } + if(r.topLeft < r.bottomLeft){ + if(r.topLeft < r.topRight){ + _draw_rect_textured(bbox.x, bbox.y+r.topLeft, r.topLeft, bbox.height-r.topLeft-r.bottomLeft, + u0, v0 + (r.topLeft/bbox.height)*(v1-v0), u0 + (r.topLeft/bbox.width)*(u1-u0), v1 - (r.bottomLeft/bbox.height)*(v1-v0)); + _draw_rect_textured(bbox.x+r.topLeft, bbox.y+r.topRight, + r.bottomLeft-r.topLeft, bbox.height-r.topRight-r.bottomLeft, + u0 + (r.topLeft/bbox.width)*(u1-u0), v0 + (r.topRight/bbox.height)*(v1-v0), u0 + (r.topLeft/bbox.width)*(u1-u0), v1 - (r.bottomLeft/bbox.height)*(v1-v0)); + } else { + _draw_rect_textured(bbox.x, bbox.y+r.topLeft, r.bottomLeft, bbox.height-r.topLeft-r.bottomLeft, + u0, v0 + (r.topLeft/bbox.height)*(v1-v0), u0 + (r.bottomLeft/bbox.width)*(u1-u0), v1 - (r.bottomLeft/bbox.height)*(v1-v0)); + } + } else { + if(r.bottomLeft < r.bottomRight){ + _draw_rect_textured(bbox.x, bbox.y+r.topLeft, r.bottomLeft, bbox.height-r.topLeft-r.bottomLeft, + u0, v0 + (r.topLeft/bbox.height)*(v1-v0), u0 + (r.bottomLeft/bbox.width)*(u1-u0), v1 - (r.bottomLeft/bbox.height)*(v1-v0)); + _draw_rect_textured(bbox.x+r.bottomLeft, bbox.y+r.topLeft, + r.topLeft-r.bottomLeft, bbox.height-r.topLeft-r.bottomRight, + u0 + (r.bottomLeft/bbox.width)*(u1-u0), v0 + (r.topLeft/bbox.height)*(v1-v0), u0 + (r.topLeft/bbox.width)*(u1-u0), v1 - (r.bottomRight/bbox.height)*(v1-v0)); + } else { + _draw_rect_textured(bbox.x, bbox.y+r.topLeft, r.topLeft, bbox.height-r.topLeft-r.bottomLeft, + u0, v0 + (r.topLeft/bbox.height)*(v1-v0), u0 + (r.topLeft/bbox.width)*(u1-u0), v1 - (r.bottomLeft/bbox.height)*(v1-v0)); + } + } + if(r.topRight < r.bottomRight){ + if(r.topRight < r.topLeft){ + _draw_rect_textured(bbox.x+bbox.width-r.bottomRight, bbox.y+r.topLeft, + r.bottomRight-r.topRight, bbox.height-r.topLeft-r.bottomRight, + u1 - (r.bottomRight/bbox.width)*(u1-u0), v0 + (r.topLeft/bbox.height)*(v1-v0), u1 - (r.topRight/bbox.width)*(u1-u0), v1 - (r.bottomRight/bbox.height)*(v1-v0)); + _draw_rect_textured(bbox.x+bbox.width-r.topRight, bbox.y+r.topRight, + r.topRight, bbox.height-r.topRight-r.bottomRight, + u1 - (r.topRight/bbox.width)*(u1-u0), v0 + (r.topRight/bbox.height)*(v1-v0), u1, v1 - (r.bottomRight/bbox.height)*(v1-v0)); + } else { + _draw_rect_textured(bbox.x+bbox.width-r.bottomRight, bbox.y+r.topRight, + r.bottomRight, bbox.height-r.topRight-r.bottomRight, + u1 - (r.bottomRight/bbox.width)*(u1-u0), v0 + (r.topRight/bbox.height)*(v1-v0), u1, v1 - (r.bottomRight/bbox.height)*(v1-v0)); + } + } else { + if(r.bottomRight < r.bottomLeft){ + _draw_rect_textured(bbox.x+bbox.width-r.topRight, bbox.y+r.topRight, + r.topRight-r.bottomRight, bbox.height-r.topRight-r.bottomLeft, + u1 - (r.topRight/bbox.width)*(u1-u0), v0 + (r.topRight/bbox.height)*(v1-v0), u1 - (r.bottomRight/bbox.width)*(u1-u0), v1 - (r.bottomLeft/bbox.height)*(v1-v0)); + _draw_rect_textured(bbox.x+bbox.width-r.bottomRight, bbox.y+r.topRight, + r.bottomRight, bbox.height-r.topRight-r.bottomRight, + u1 - (r.bottomRight/bbox.width)*(u1-u0), v0 + (r.topRight/bbox.height)*(v1-v0), u1, v1 - (r.bottomRight/bbox.height)*(v1-v0)); + } else { + _draw_rect_textured(bbox.x+bbox.width-r.topRight, bbox.y+r.topRight, + r.topRight, bbox.height-r.topRight-r.bottomRight, + u1 - (r.topRight/bbox.width)*(u1-u0), v0 + (r.topRight/bbox.height)*(v1-v0), u1, v1 - (r.bottomRight/bbox.height)*(v1-v0)); + } + } + _draw_rect_textured(bbox.x+CLAY__MAX(r.topLeft, r.bottomLeft), + bbox.y+CLAY__MAX(r.topLeft, r.topRight), + bbox.width-CLAY__MAX(r.topLeft, r.bottomLeft)-CLAY__MAX(r.topRight, r.bottomRight), + bbox.height-CLAY__MAX(r.topLeft, r.topRight)-CLAY__MAX(r.bottomLeft, r.bottomRight), + u0+CLAY__MAX(r.topLeft,r.bottomLeft)/bbox.width*(u1-u0), v0+CLAY__MAX(r.topLeft,r.topRight)/bbox.height*(v1-v0), + u1-CLAY__MAX(r.topRight,r.bottomRight)/bbox.width*(u1-u0), v1-CLAY__MAX(r.bottomLeft,r.bottomRight)/bbox.height*(v1-v0)); + sgl_end(); + sgl_disable_texture(); + break; + } + case CLAY_RENDER_COMMAND_TYPE_BORDER: { + Clay_BorderRenderData *config = &renderCommand->renderData.border; + sgl_c4f(config->color.r / 255.0f, + config->color.g / 255.0f, + config->color.b / 255.0f, + config->color.a / 255.0f); + Clay_BorderWidth w = config->width; + Clay_CornerRadius r = config->cornerRadius; + sgl_begin_triangle_strip(); + if(w.left > 0){ + _draw_rect(bbox.x, bbox.y + r.topLeft, + w.left, bbox.height - r.topLeft - r.bottomLeft); + } + if(w.right > 0){ + _draw_rect(bbox.x + bbox.width - w.right, bbox.y + r.topRight, + w.right, bbox.height - r.topRight - r.bottomRight); + } + if(w.top > 0){ + _draw_rect(bbox.x + r.topLeft, bbox.y, + bbox.width - r.topLeft - r.topRight, w.top); + } + if(w.bottom > 0){ + _draw_rect(bbox.x + r.bottomLeft, bbox.y + bbox.height - w.bottom, + bbox.width - r.bottomLeft - r.bottomRight, w.bottom); + } + if(r.topLeft > 0 && (w.top > 0 || w.left > 0)){ + _draw_corner_border(bbox.x, bbox.y, + -r.topLeft, -r.topLeft, + -r.topLeft+w.left, -r.topLeft+w.top); + } + if(r.topRight > 0 && (w.top > 0 || w.right > 0)){ + _draw_corner_border(bbox.x+bbox.width, bbox.y, + r.topRight, -r.topRight, + r.topRight-w.right, -r.topRight+w.top); + } + if(r.bottomLeft > 0 && (w.bottom > 0 || w.left > 0)){ + _draw_corner_border(bbox.x, bbox.y+bbox.height, + -r.bottomLeft, r.bottomLeft, + -r.bottomLeft+w.left, r.bottomLeft-w.bottom); + } + if(r.bottomRight > 0 && (w.bottom > 0 || w.right > 0)){ + _draw_corner_border(bbox.x+bbox.width, bbox.y+bbox.height, + r.bottomRight, r.bottomRight, + r.bottomRight-w.right, r.bottomRight-w.bottom); + } + sgl_end(); + break; + } + case CLAY_RENDER_COMMAND_TYPE_CUSTOM: { + if (_sclay.custom_element_cb == NULL) { + break; + } + Clay_CustomRenderData *config = &renderCommand->renderData.custom; + _sclay.custom_element_cb(bbox, config); + break; + } + default: + break; + } + } + sgl_pop_pipeline(); + sfons_flush(_sclay.fonts); +} +#endif /* SOKOL_CLAY_IMPL */ diff --git a/third-party/sokol/sokol_fontstash.h b/third-party/sokol/sokol_fontstash.h new file mode 100644 index 0000000..aa8d209 --- /dev/null +++ b/third-party/sokol/sokol_fontstash.h @@ -0,0 +1,2289 @@ +#if defined(SOKOL_IMPL) && !defined(SOKOL_FONTSTASH_IMPL) +#define SOKOL_FONTSTASH_IMPL +#endif +#ifndef SOKOL_FONTSTASH_INCLUDED +/* + sokol_fontstash.h -- renderer for https://github.com/memononen/fontstash + on top of sokol_gl.h + + Project URL: https://github.com/floooh/sokol + + Do this: + #define SOKOL_IMPL or + #define SOKOL_FONTSTASH_IMPL + + before you include this file in *one* C or C++ file to create the + implementation. + + The following defines are used by the implementation to select the + platform-specific embedded shader code (these are the same defines as + used by sokol_gfx.h and sokol_app.h): + + SOKOL_GLCORE + SOKOL_GLES3 + SOKOL_D3D11 + SOKOL_METAL + + ...optionally provide the following macros to override defaults: + + SOKOL_ASSERT(c) - your own assert macro (default: assert(c)) + SOKOL_FONTSTASH_API_DECL - public function declaration prefix (default: extern) + SOKOL_API_DECL - same as SOKOL_FONTSTASH_API_DECL + SOKOL_API_IMPL - public function implementation prefix (default: -) + SOKOL_UNREACHABLE() - a guard macro for unreachable code (default: assert(false)) + + Include the following headers before including sokol_fontstash.h: + + sokol_gfx.h + + Additionally include the following headers for including the sokol_fontstash.h + implementation: + + sokol_gl.h + + HOW TO + ====== + --- First initialize sokol-gfx and sokol-gl as usual: + + sg_setup(&(sg_desc){...}); + sgl_setup(&(sgl_desc){...}); + + --- Create at least one fontstash context with sfons_create() (this replaces + glfonsCreate() from fontstash.h's example GL renderer: + + FONScontext* ctx = sfons_create(&(sfons_desc_t){ + .width = atlas_width, + .height = atlas_height, + }); + + Each FONScontext manages one font atlas texture which can hold rasterized + glyphs for multiple fonts. + + --- From here on, use fontstash.h's functions "as usual" to add TTF + font data and draw text. Note that (just like with sokol-gl), text + rendering can happen anywhere in the frame, not only inside + a sokol-gfx rendering pass. + + --- You can use the helper function + + uint32_t sfons_rgba(uint8_t r, uint8_t g, uint8_t b, uint8_t a) + + To convert a 0..255 RGBA color into a packed uint32_t color value + expected by fontstash.h. + + --- Once per frame before calling sgl_draw(), call: + + sfons_flush(FONScontext* ctx) + + ...this will update the dynamic sokol-gfx texture with the latest font + atlas content. + + --- To actually render the text (and any other sokol-gl draw commands), + call sgl_draw() inside a sokol-gfx frame. + + --- NOTE that you can mix fontstash.h calls with sokol-gl calls to mix + text rendering with sokol-gl rendering. You can also use + sokol-gl's matrix stack to position fontstash.h text in 3D. + + --- finally on application shutdown, call: + + sfons_destroy(FONScontext* ctx) + + before sgl_shutdown() and sg_shutdown() + + + WHAT HAPPENS UNDER THE HOOD: + ============================ + + FONScontext* sfons_create(const sfons_desc_t* desc) + - creates a sokol-gfx shader compatible with sokol-gl + - creates an sgl_pipeline object with alpha-blending using + this shader + - creates a 1-byte-per-pixel font atlas texture via sokol-gfx + (pixel format SG_PIXELFORMAT_R8) + + fonsDrawText(): + - this will call the following sequence of sokol-gl functions: + + sgl_enable_texture(); + sgl_texture(...); + sgl_push_pipeline(); + sgl_load_pipeline(...); + sgl_begin_triangles(); + for each vertex: + sgl_v2f_t2f_c1i(...); + sgl_end(); + sgl_pop_pipeline(); + sgl_disable_texture(); + + - note that sokol-gl will merge several sgl_*_begin/sgl_end pairs + into a single draw call if no relevant state has changed, typically + all calls to fonsDrawText() will be merged into a single draw call + as long as all calls use the same FONScontext + + sfons_flush(FONScontext* ctx): + - this will call sg_update_image() on the font atlas texture + if fontstash.h has added any rasterized glyphs since the last + frame + + sfons_destroy(FONScontext* ctx): + - destroy the font atlas texture, sgl_pipeline and sg_shader objects + + + MEMORY ALLOCATION OVERRIDE + ========================== + You can override the memory allocation functions at initialization time + like this: + + void* my_alloc(size_t size, void* user_data) { + return malloc(size); + } + + void my_free(void* ptr, void* user_data) { + free(ptr); + } + + ... + FONScontext* fons_context = sfons_create(&(sfons_desc_t){ + ... + .allocator = { + .alloc_fn = my_alloc, + .free_fn = my_free, + .user_data = ..., + } + }); + ... + + If no overrides are provided, malloc and free will be used. Please + note that this doesn't affect any memory allocation performed + in fontstash.h (unfortunately those are hardwired to malloc/free). + + LICENSE + ======= + zlib/libpng license + + Copyright (c) 2018 Andre Weissflog + + This software is provided 'as-is', without any express or implied warranty. + In no event will the authors be held liable for any damages arising from the + use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software in a + product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not + be misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ +#define SOKOL_FONTSTASH_INCLUDED (1) +#include +#include +#include // size_t + +#if !defined(SOKOL_GFX_INCLUDED) +#error "Please include sokol_gfx.h before sokol_fontstash.h" +#endif + +#if defined(SOKOL_API_DECL) && !defined(SOKOL_FONTSTASH_API_DECL) +#define SOKOL_FONTSTASH_API_DECL SOKOL_API_DECL +#endif +#ifndef SOKOL_FONTSTASH_API_DECL +#if defined(_WIN32) && defined(SOKOL_DLL) && defined(SOKOL_FONTSTASH_IMPL) +#define SOKOL_FONTSTASH_API_DECL __declspec(dllexport) +#elif defined(_WIN32) && defined(SOKOL_DLL) +#define SOKOL_FONTSTASH_API_DECL __declspec(dllimport) +#else +#define SOKOL_FONTSTASH_API_DECL extern +#endif +#endif +#ifdef __cplusplus +extern "C" { +#endif + +/* + sfonst_allocator_t + + Used in sfons_desc_t to provide custom memory-alloc and -free functions + to sokol_fontstash.h. If memory management should be overridden, both the + alloc_fn and free_fn function must be provided (e.g. it's not valid to + override one function but not the other). + + NOTE that this does not affect memory allocation calls inside + fontstash.h +*/ +typedef struct sfons_allocator_t { + void* (*alloc_fn)(size_t size, void* user_data); + void (*free_fn)(void* ptr, void* user_data); + void* user_data; +} sfons_allocator_t; + +typedef struct sfons_desc_t { + int width; // initial width of font atlas texture (default: 512, must be power of 2) + int height; // initial height of font atlas texture (default: 512, must be power of 2) + sfons_allocator_t allocator; // optional memory allocation overrides +} sfons_desc_t; + +SOKOL_FONTSTASH_API_DECL FONScontext* sfons_create(const sfons_desc_t* desc); +SOKOL_FONTSTASH_API_DECL void sfons_destroy(FONScontext* ctx); +SOKOL_FONTSTASH_API_DECL void sfons_flush(FONScontext* ctx); +SOKOL_FONTSTASH_API_DECL uint32_t sfons_rgba(uint8_t r, uint8_t g, uint8_t b, uint8_t a); + +#ifdef __cplusplus +} /* extern "C" */ +#endif +#endif /* SOKOL_FONTSTASH_INCLUDED */ + +/*-- IMPLEMENTATION ----------------------------------------------------------*/ +#ifdef SOKOL_FONTSTASH_IMPL +#define SOKOL_FONTSTASH_IMPL_INCLUDED (1) + +#if defined(SOKOL_MALLOC) || defined(SOKOL_CALLOC) || defined(SOKOL_FREE) +#error "SOKOL_MALLOC/CALLOC/FREE macros are no longer supported, please use sfons_desc_t.allocator to override memory allocation functions" +#endif + +#include // memset, memcpy +#include // malloc, free + +#if !defined(SOKOL_GL_INCLUDED) +#error "Please include sokol_gl.h before sokol_fontstash.h" +#endif +#if !defined(FONS_H) +#error "Please include fontstash.h before sokol_fontstash.h" +#endif + +#ifndef SOKOL_API_IMPL + #define SOKOL_API_IMPL +#endif +#ifndef SOKOL_DEBUG + #ifndef NDEBUG + #define SOKOL_DEBUG + #endif +#endif +#ifndef SOKOL_ASSERT + #include + #define SOKOL_ASSERT(c) assert(c) +#endif +#ifndef SOKOL_UNREACHABLE + #define SOKOL_UNREACHABLE SOKOL_ASSERT(false) +#endif +#ifndef _SOKOL_UNUSED + #define _SOKOL_UNUSED(x) (void)(x) +#endif + +/* + Embedded source code compiled with: + + sokol-shdc -i sfons.glsl -o sfons.h -l glsl410:glsl300es:hlsl4:metal_macos:metal_ios:metal_sim:wgsl -b + + (not that for Metal and D3D11 byte code, sokol-shdc must be run + on macOS and Windows) + + @vs vs + layout(binding=0) uniform vs_params { + uniform mat4 mvp; + uniform mat4 tm; + }; + in vec4 position; + in vec2 texcoord0; + in vec4 color0; + in float psize; + out vec4 uv; + out vec4 color; + void main() { + gl_Position = mvp * position; + #ifndef SOKOL_WGSL + gl_PointSize = psize; + #endif + uv = tm * vec4(texcoord0, 0.0, 1.0); + color = color0; + } + @end + + @fs fs + layout(binding=0) uniform texture2D tex; + layout(binding=0) uniform sampler smp; + in vec4 uv; + in vec4 color; + out vec4 frag_color; + void main() { + frag_color = vec4(1.0, 1.0, 1.0, texture(sampler2D(tex, smp), uv.xy).r) * color; + } + @end + + @program sfontstash vs fs +*/ +#if defined(SOKOL_GLCORE) +/* + #version 410 + + uniform vec4 vs_params[8]; + layout(location = 0) in vec4 position; + layout(location = 3) in float psize; + layout(location = 0) out vec4 uv; + layout(location = 1) in vec2 texcoord0; + layout(location = 1) out vec4 color; + layout(location = 2) in vec4 color0; + + void main() + { + gl_Position = mat4(vs_params[0], vs_params[1], vs_params[2], vs_params[3]) * position; + gl_PointSize = psize; + uv = mat4(vs_params[4], vs_params[5], vs_params[6], vs_params[7]) * vec4(texcoord0, 0.0, 1.0); + color = color0; + } +*/ +static const uint8_t _sfons_vs_source_glsl410[520] = { + 0x23,0x76,0x65,0x72,0x73,0x69,0x6f,0x6e,0x20,0x34,0x31,0x30,0x0a,0x0a,0x75,0x6e, + 0x69,0x66,0x6f,0x72,0x6d,0x20,0x76,0x65,0x63,0x34,0x20,0x76,0x73,0x5f,0x70,0x61, + 0x72,0x61,0x6d,0x73,0x5b,0x38,0x5d,0x3b,0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74,0x28, + 0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x30,0x29,0x20,0x69,0x6e, + 0x20,0x76,0x65,0x63,0x34,0x20,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x3b,0x0a, + 0x6c,0x61,0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x20, + 0x3d,0x20,0x33,0x29,0x20,0x69,0x6e,0x20,0x66,0x6c,0x6f,0x61,0x74,0x20,0x70,0x73, + 0x69,0x7a,0x65,0x3b,0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,0x61, + 0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x30,0x29,0x20,0x6f,0x75,0x74,0x20,0x76,0x65, + 0x63,0x34,0x20,0x75,0x76,0x3b,0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f, + 0x63,0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x31,0x29,0x20,0x69,0x6e,0x20,0x76, + 0x65,0x63,0x32,0x20,0x74,0x65,0x78,0x63,0x6f,0x6f,0x72,0x64,0x30,0x3b,0x0a,0x6c, + 0x61,0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d, + 0x20,0x31,0x29,0x20,0x6f,0x75,0x74,0x20,0x76,0x65,0x63,0x34,0x20,0x63,0x6f,0x6c, + 0x6f,0x72,0x3b,0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,0x61,0x74, + 0x69,0x6f,0x6e,0x20,0x3d,0x20,0x32,0x29,0x20,0x69,0x6e,0x20,0x76,0x65,0x63,0x34, + 0x20,0x63,0x6f,0x6c,0x6f,0x72,0x30,0x3b,0x0a,0x0a,0x76,0x6f,0x69,0x64,0x20,0x6d, + 0x61,0x69,0x6e,0x28,0x29,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x67,0x6c,0x5f,0x50, + 0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x6d,0x61,0x74,0x34,0x28,0x76, + 0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x30,0x5d,0x2c,0x20,0x76,0x73,0x5f, + 0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x31,0x5d,0x2c,0x20,0x76,0x73,0x5f,0x70,0x61, + 0x72,0x61,0x6d,0x73,0x5b,0x32,0x5d,0x2c,0x20,0x76,0x73,0x5f,0x70,0x61,0x72,0x61, + 0x6d,0x73,0x5b,0x33,0x5d,0x29,0x20,0x2a,0x20,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f, + 0x6e,0x3b,0x0a,0x20,0x20,0x20,0x20,0x67,0x6c,0x5f,0x50,0x6f,0x69,0x6e,0x74,0x53, + 0x69,0x7a,0x65,0x20,0x3d,0x20,0x70,0x73,0x69,0x7a,0x65,0x3b,0x0a,0x20,0x20,0x20, + 0x20,0x75,0x76,0x20,0x3d,0x20,0x6d,0x61,0x74,0x34,0x28,0x76,0x73,0x5f,0x70,0x61, + 0x72,0x61,0x6d,0x73,0x5b,0x34,0x5d,0x2c,0x20,0x76,0x73,0x5f,0x70,0x61,0x72,0x61, + 0x6d,0x73,0x5b,0x35,0x5d,0x2c,0x20,0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73, + 0x5b,0x36,0x5d,0x2c,0x20,0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x37, + 0x5d,0x29,0x20,0x2a,0x20,0x76,0x65,0x63,0x34,0x28,0x74,0x65,0x78,0x63,0x6f,0x6f, + 0x72,0x64,0x30,0x2c,0x20,0x30,0x2e,0x30,0x2c,0x20,0x31,0x2e,0x30,0x29,0x3b,0x0a, + 0x20,0x20,0x20,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20,0x63,0x6f,0x6c,0x6f, + 0x72,0x30,0x3b,0x0a,0x7d,0x0a,0x0a,0x00, +}; +/* + #version 410 + + uniform sampler2D tex_smp; + + layout(location = 0) out vec4 frag_color; + layout(location = 0) in vec4 uv; + layout(location = 1) in vec4 color; + + void main() + { + frag_color = vec4(1.0, 1.0, 1.0, texture(tex_smp, uv.xy).x) * color; + } +*/ +static const uint8_t _sfons_fs_source_glsl410[245] = { + 0x23,0x76,0x65,0x72,0x73,0x69,0x6f,0x6e,0x20,0x34,0x31,0x30,0x0a,0x0a,0x75,0x6e, + 0x69,0x66,0x6f,0x72,0x6d,0x20,0x73,0x61,0x6d,0x70,0x6c,0x65,0x72,0x32,0x44,0x20, + 0x74,0x65,0x78,0x5f,0x73,0x6d,0x70,0x3b,0x0a,0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74, + 0x28,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x30,0x29,0x20,0x6f, + 0x75,0x74,0x20,0x76,0x65,0x63,0x34,0x20,0x66,0x72,0x61,0x67,0x5f,0x63,0x6f,0x6c, + 0x6f,0x72,0x3b,0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,0x61,0x74, + 0x69,0x6f,0x6e,0x20,0x3d,0x20,0x30,0x29,0x20,0x69,0x6e,0x20,0x76,0x65,0x63,0x34, + 0x20,0x75,0x76,0x3b,0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,0x61, + 0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x31,0x29,0x20,0x69,0x6e,0x20,0x76,0x65,0x63, + 0x34,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x0a,0x76,0x6f,0x69,0x64,0x20,0x6d, + 0x61,0x69,0x6e,0x28,0x29,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66,0x72,0x61,0x67, + 0x5f,0x63,0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20,0x76,0x65,0x63,0x34,0x28,0x31,0x2e, + 0x30,0x2c,0x20,0x31,0x2e,0x30,0x2c,0x20,0x31,0x2e,0x30,0x2c,0x20,0x74,0x65,0x78, + 0x74,0x75,0x72,0x65,0x28,0x74,0x65,0x78,0x5f,0x73,0x6d,0x70,0x2c,0x20,0x75,0x76, + 0x2e,0x78,0x79,0x29,0x2e,0x78,0x29,0x20,0x2a,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x3b, + 0x0a,0x7d,0x0a,0x0a,0x00, +}; +#elif defined(SOKOL_GLES3) +/* + #version 300 es + + uniform vec4 vs_params[8]; + layout(location = 0) in vec4 position; + layout(location = 3) in float psize; + out vec4 uv; + layout(location = 1) in vec2 texcoord0; + out vec4 color; + layout(location = 2) in vec4 color0; + + void main() + { + gl_Position = mat4(vs_params[0], vs_params[1], vs_params[2], vs_params[3]) * position; + gl_PointSize = psize; + uv = mat4(vs_params[4], vs_params[5], vs_params[6], vs_params[7]) * vec4(texcoord0, 0.0, 1.0); + color = color0; + } +*/ +static const uint8_t _sfons_vs_source_glsl300es[481] = { + 0x23,0x76,0x65,0x72,0x73,0x69,0x6f,0x6e,0x20,0x33,0x30,0x30,0x20,0x65,0x73,0x0a, + 0x0a,0x75,0x6e,0x69,0x66,0x6f,0x72,0x6d,0x20,0x76,0x65,0x63,0x34,0x20,0x76,0x73, + 0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x38,0x5d,0x3b,0x0a,0x6c,0x61,0x79,0x6f, + 0x75,0x74,0x28,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x30,0x29, + 0x20,0x69,0x6e,0x20,0x76,0x65,0x63,0x34,0x20,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f, + 0x6e,0x3b,0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,0x61,0x74,0x69, + 0x6f,0x6e,0x20,0x3d,0x20,0x33,0x29,0x20,0x69,0x6e,0x20,0x66,0x6c,0x6f,0x61,0x74, + 0x20,0x70,0x73,0x69,0x7a,0x65,0x3b,0x0a,0x6f,0x75,0x74,0x20,0x76,0x65,0x63,0x34, + 0x20,0x75,0x76,0x3b,0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,0x61, + 0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x31,0x29,0x20,0x69,0x6e,0x20,0x76,0x65,0x63, + 0x32,0x20,0x74,0x65,0x78,0x63,0x6f,0x6f,0x72,0x64,0x30,0x3b,0x0a,0x6f,0x75,0x74, + 0x20,0x76,0x65,0x63,0x34,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x6c,0x61,0x79, + 0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x32, + 0x29,0x20,0x69,0x6e,0x20,0x76,0x65,0x63,0x34,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x30, + 0x3b,0x0a,0x0a,0x76,0x6f,0x69,0x64,0x20,0x6d,0x61,0x69,0x6e,0x28,0x29,0x0a,0x7b, + 0x0a,0x20,0x20,0x20,0x20,0x67,0x6c,0x5f,0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e, + 0x20,0x3d,0x20,0x6d,0x61,0x74,0x34,0x28,0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d, + 0x73,0x5b,0x30,0x5d,0x2c,0x20,0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b, + 0x31,0x5d,0x2c,0x20,0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x32,0x5d, + 0x2c,0x20,0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x33,0x5d,0x29,0x20, + 0x2a,0x20,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x3b,0x0a,0x20,0x20,0x20,0x20, + 0x67,0x6c,0x5f,0x50,0x6f,0x69,0x6e,0x74,0x53,0x69,0x7a,0x65,0x20,0x3d,0x20,0x70, + 0x73,0x69,0x7a,0x65,0x3b,0x0a,0x20,0x20,0x20,0x20,0x75,0x76,0x20,0x3d,0x20,0x6d, + 0x61,0x74,0x34,0x28,0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x34,0x5d, + 0x2c,0x20,0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x35,0x5d,0x2c,0x20, + 0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x36,0x5d,0x2c,0x20,0x76,0x73, + 0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x37,0x5d,0x29,0x20,0x2a,0x20,0x76,0x65, + 0x63,0x34,0x28,0x74,0x65,0x78,0x63,0x6f,0x6f,0x72,0x64,0x30,0x2c,0x20,0x30,0x2e, + 0x30,0x2c,0x20,0x31,0x2e,0x30,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x63,0x6f,0x6c, + 0x6f,0x72,0x20,0x3d,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x30,0x3b,0x0a,0x7d,0x0a,0x0a, + 0x00, +}; +/* + #version 300 es + precision mediump float; + precision highp int; + + uniform highp sampler2D tex_smp; + + layout(location = 0) out highp vec4 frag_color; + in highp vec4 uv; + in highp vec4 color; + + void main() + { + frag_color = vec4(1.0, 1.0, 1.0, texture(tex_smp, uv.xy).x) * color; + } + +*/ +static const uint8_t _sfons_fs_source_glsl300es[276] = { + 0x23,0x76,0x65,0x72,0x73,0x69,0x6f,0x6e,0x20,0x33,0x30,0x30,0x20,0x65,0x73,0x0a, + 0x70,0x72,0x65,0x63,0x69,0x73,0x69,0x6f,0x6e,0x20,0x6d,0x65,0x64,0x69,0x75,0x6d, + 0x70,0x20,0x66,0x6c,0x6f,0x61,0x74,0x3b,0x0a,0x70,0x72,0x65,0x63,0x69,0x73,0x69, + 0x6f,0x6e,0x20,0x68,0x69,0x67,0x68,0x70,0x20,0x69,0x6e,0x74,0x3b,0x0a,0x0a,0x75, + 0x6e,0x69,0x66,0x6f,0x72,0x6d,0x20,0x68,0x69,0x67,0x68,0x70,0x20,0x73,0x61,0x6d, + 0x70,0x6c,0x65,0x72,0x32,0x44,0x20,0x74,0x65,0x78,0x5f,0x73,0x6d,0x70,0x3b,0x0a, + 0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e, + 0x20,0x3d,0x20,0x30,0x29,0x20,0x6f,0x75,0x74,0x20,0x68,0x69,0x67,0x68,0x70,0x20, + 0x76,0x65,0x63,0x34,0x20,0x66,0x72,0x61,0x67,0x5f,0x63,0x6f,0x6c,0x6f,0x72,0x3b, + 0x0a,0x69,0x6e,0x20,0x68,0x69,0x67,0x68,0x70,0x20,0x76,0x65,0x63,0x34,0x20,0x75, + 0x76,0x3b,0x0a,0x69,0x6e,0x20,0x68,0x69,0x67,0x68,0x70,0x20,0x76,0x65,0x63,0x34, + 0x20,0x63,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x0a,0x76,0x6f,0x69,0x64,0x20,0x6d,0x61, + 0x69,0x6e,0x28,0x29,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66,0x72,0x61,0x67,0x5f, + 0x63,0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20,0x76,0x65,0x63,0x34,0x28,0x31,0x2e,0x30, + 0x2c,0x20,0x31,0x2e,0x30,0x2c,0x20,0x31,0x2e,0x30,0x2c,0x20,0x74,0x65,0x78,0x74, + 0x75,0x72,0x65,0x28,0x74,0x65,0x78,0x5f,0x73,0x6d,0x70,0x2c,0x20,0x75,0x76,0x2e, + 0x78,0x79,0x29,0x2e,0x78,0x29,0x20,0x2a,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x3b,0x0a, + 0x7d,0x0a,0x0a,0x00, +}; +#elif defined(SOKOL_METAL) +/* + #include + #include + + using namespace metal; + + struct vs_params + { + float4x4 mvp; + float4x4 tm; + }; + + struct main0_out + { + float4 uv [[user(locn0)]]; + float4 color [[user(locn1)]]; + float4 gl_Position [[position]]; + float gl_PointSize [[point_size]]; + }; + + struct main0_in + { + float4 position [[attribute(0)]]; + float2 texcoord0 [[attribute(1)]]; + float4 color0 [[attribute(2)]]; + float psize [[attribute(3)]]; + }; + + vertex main0_out main0(main0_in in [[stage_in]], constant vs_params& _19 [[buffer(0)]]) + { + main0_out out = {}; + out.gl_Position = _19.mvp * in.position; + out.gl_PointSize = in.psize; + out.uv = _19.tm * float4(in.texcoord0, 0.0, 1.0); + out.color = in.color0; + return out; + } +*/ +static const uint8_t _sfons_vs_bytecode_metal_macos[3381] = { + 0x4d,0x54,0x4c,0x42,0x01,0x80,0x02,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x35,0x0d,0x00,0x00,0x00,0x00,0x00,0x00,0x58,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x6d,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc9,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x44,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0d,0x01,0x00,0x00,0x00,0x00,0x00,0x00, + 0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x15,0x01,0x00,0x00,0x00,0x00,0x00,0x00, + 0x20,0x0c,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x6d,0x00,0x00,0x00, + 0x4e,0x41,0x4d,0x45,0x06,0x00,0x6d,0x61,0x69,0x6e,0x30,0x00,0x54,0x59,0x50,0x45, + 0x01,0x00,0x00,0x48,0x41,0x53,0x48,0x20,0x00,0x59,0xf1,0xe4,0x22,0x0f,0x75,0x42, + 0x53,0x9c,0x2a,0x05,0xe4,0xbd,0x91,0x28,0x82,0x06,0x3f,0x0d,0xb9,0xef,0x34,0xbd, + 0xe8,0xfa,0x46,0x5c,0x66,0x1f,0xc6,0xde,0x3a,0x4f,0x46,0x46,0x54,0x18,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x56,0x45,0x52,0x53,0x08,0x00,0x01,0x00,0x08, + 0x00,0x01,0x00,0x01,0x00,0x45,0x4e,0x44,0x54,0x40,0x00,0x00,0x00,0x56,0x41,0x54, + 0x54,0x2a,0x00,0x04,0x00,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x00,0x00,0x80, + 0x74,0x65,0x78,0x63,0x6f,0x6f,0x72,0x64,0x30,0x00,0x01,0x80,0x63,0x6f,0x6c,0x6f, + 0x72,0x30,0x00,0x02,0x80,0x70,0x73,0x69,0x7a,0x65,0x00,0x03,0x80,0x56,0x41,0x54, + 0x59,0x06,0x00,0x04,0x00,0x06,0x04,0x06,0x03,0x45,0x4e,0x44,0x54,0x04,0x00,0x00, + 0x00,0x45,0x4e,0x44,0x54,0xde,0xc0,0x17,0x0b,0x00,0x00,0x00,0x00,0x14,0x00,0x00, + 0x00,0x04,0x0c,0x00,0x00,0xff,0xff,0xff,0xff,0x42,0x43,0xc0,0xde,0x21,0x0c,0x00, + 0x00,0xfe,0x02,0x00,0x00,0x0b,0x82,0x20,0x00,0x02,0x00,0x00,0x00,0x12,0x00,0x00, + 0x00,0x07,0x81,0x23,0x91,0x41,0xc8,0x04,0x49,0x06,0x10,0x32,0x39,0x92,0x01,0x84, + 0x0c,0x25,0x05,0x08,0x19,0x1e,0x04,0x8b,0x62,0x80,0x14,0x45,0x02,0x42,0x92,0x0b, + 0x42,0xa4,0x10,0x32,0x14,0x38,0x08,0x18,0x49,0x0a,0x32,0x44,0x24,0x48,0x0a,0x90, + 0x21,0x23,0xc4,0x52,0x80,0x0c,0x19,0x21,0x72,0x24,0x07,0xc8,0x48,0x11,0x62,0xa8, + 0xa0,0xa8,0x40,0xc6,0xf0,0x01,0x00,0x00,0x00,0x51,0x18,0x00,0x00,0x68,0x00,0x00, + 0x00,0x1b,0x7e,0x24,0xf8,0xff,0xff,0xff,0xff,0x01,0x90,0x00,0x8a,0x08,0x07,0x78, + 0x80,0x07,0x79,0x78,0x07,0x7c,0x68,0x03,0x73,0xa8,0x07,0x77,0x18,0x87,0x36,0x30, + 0x07,0x78,0x68,0x83,0x76,0x08,0x07,0x7a,0x40,0x07,0x80,0x1e,0xe4,0xa1,0x1e,0xca, + 0x01,0x20,0xda,0x21,0x1d,0xdc,0xa1,0x0d,0xd8,0xa1,0x1c,0xce,0x21,0x1c,0xd8,0xa1, + 0x0d,0xec,0xa1,0x1c,0xc6,0x81,0x1e,0xde,0x41,0x1e,0xda,0xe0,0x1e,0xd2,0x81,0x1c, + 0xe8,0x01,0x1d,0x80,0x38,0x90,0x03,0x3c,0x00,0x06,0x77,0x78,0x87,0x36,0x10,0x87, + 0x7a,0x48,0x07,0x76,0xa0,0x87,0x74,0x70,0x87,0x79,0x00,0x08,0x77,0x78,0x87,0x36, + 0x30,0x07,0x79,0x08,0x87,0x76,0x28,0x87,0x36,0x80,0x87,0x77,0x48,0x07,0x77,0xa0, + 0x87,0x72,0x90,0x87,0x36,0x28,0x07,0x76,0x48,0x87,0x76,0x00,0xe8,0x41,0x1e,0xea, + 0xa1,0x1c,0x00,0xc2,0x1d,0xde,0xa1,0x0d,0xcc,0x41,0x1e,0xc2,0xa1,0x1d,0xca,0xa1, + 0x0d,0xe0,0xe1,0x1d,0xd2,0xc1,0x1d,0xe8,0xa1,0x1c,0xe4,0xa1,0x0d,0xca,0x81,0x1d, + 0xd2,0xa1,0x1d,0xda,0xc0,0x1d,0xde,0xc1,0x1d,0xda,0x80,0x1d,0xca,0x21,0x1c,0xcc, + 0x01,0xa0,0x07,0x79,0xa8,0x87,0x72,0x00,0x08,0x77,0x78,0x87,0x36,0x48,0x07,0x77, + 0x30,0x87,0x79,0x68,0x03,0x73,0x80,0x87,0x36,0x68,0x87,0x70,0xa0,0x07,0x74,0x00, + 0xe8,0x41,0x1e,0xea,0xa1,0x1c,0x00,0xc2,0x1d,0xde,0xa1,0x0d,0xdc,0x21,0x1c,0xdc, + 0x61,0x1e,0xda,0xc0,0x1c,0xe0,0xa1,0x0d,0xda,0x21,0x1c,0xe8,0x01,0x1d,0x00,0x7a, + 0x90,0x87,0x7a,0x28,0x07,0x80,0x70,0x87,0x77,0x68,0x83,0x79,0x48,0x87,0x73,0x70, + 0x87,0x72,0x20,0x87,0x36,0xd0,0x87,0x72,0x90,0x87,0x77,0x98,0x87,0x36,0x30,0x07, + 0x78,0x68,0x83,0x76,0x08,0x07,0x7a,0x40,0x07,0x80,0x1e,0xe4,0xa1,0x1e,0xca,0x01, + 0x20,0xdc,0xe1,0x1d,0xda,0x80,0x1e,0xe4,0x21,0x1c,0xe0,0x01,0x1e,0xd2,0xc1,0x1d, + 0xce,0xa1,0x0d,0xda,0x21,0x1c,0xe8,0x01,0x1d,0x00,0x7a,0x90,0x87,0x7a,0x28,0x07, + 0x80,0x98,0x07,0x7a,0x08,0x87,0x71,0x58,0x87,0x36,0x80,0x07,0x79,0x78,0x07,0x7a, + 0x28,0x87,0x71,0xa0,0x87,0x77,0x90,0x87,0x36,0x10,0x87,0x7a,0x30,0x07,0x73,0x28, + 0x07,0x79,0x68,0x83,0x79,0x48,0x07,0x7d,0x28,0x07,0x00,0x0f,0x00,0xa2,0x1e,0xdc, + 0x61,0x1e,0xc2,0xc1,0x1c,0xca,0xa1,0x0d,0xcc,0x01,0x1e,0xda,0xa0,0x1d,0xc2,0x81, + 0x1e,0xd0,0x01,0xa0,0x07,0x79,0xa8,0x87,0x72,0x00,0x36,0x18,0x42,0x01,0x2c,0x40, + 0x05,0x49,0x18,0x00,0x00,0x01,0x00,0x00,0x00,0x13,0x84,0x40,0x00,0x89,0x20,0x00, + 0x00,0x20,0x00,0x00,0x00,0x32,0x22,0x48,0x09,0x20,0x64,0x85,0x04,0x93,0x22,0xa4, + 0x84,0x04,0x93,0x22,0xe3,0x84,0xa1,0x90,0x14,0x12,0x4c,0x8a,0x8c,0x0b,0x84,0xa4, + 0x4c,0x10,0x44,0x33,0x00,0xc3,0x08,0x04,0x60,0x89,0x10,0x02,0x18,0x46,0x10,0x80, + 0x24,0x08,0x33,0x51,0xf3,0x40,0x0f,0xf2,0x50,0x0f,0xe3,0x40,0x0f,0x6e,0xd0,0x0e, + 0xe5,0x40,0x0f,0xe1,0xc0,0x0e,0x7a,0xa0,0x07,0xed,0x10,0x0e,0xf4,0x20,0x0f,0xe9, + 0x80,0x0f,0x28,0x20,0x07,0x49,0x53,0x44,0x09,0x93,0x5f,0x49,0xff,0x03,0x44,0x00, + 0x23,0x21,0xa1,0x94,0x41,0x04,0x43,0x28,0x86,0x08,0x23,0x80,0x43,0x68,0x20,0x60, + 0x8e,0x00,0x0c,0x52,0x60,0xcd,0x11,0x80,0xc2,0x20,0x42,0x20,0x0c,0x23,0x10,0xcb, + 0x08,0x00,0x00,0x00,0x00,0x13,0xb2,0x70,0x48,0x07,0x79,0xb0,0x03,0x3a,0x68,0x83, + 0x70,0x80,0x07,0x78,0x60,0x87,0x72,0x68,0x83,0x76,0x08,0x87,0x71,0x78,0x87,0x79, + 0xc0,0x87,0x38,0x80,0x03,0x37,0x88,0x83,0x38,0x70,0x03,0x38,0xd8,0x70,0x1b,0xe5, + 0xd0,0x06,0xf0,0xa0,0x07,0x76,0x40,0x07,0x7a,0x60,0x07,0x74,0xa0,0x07,0x76,0x40, + 0x07,0x6d,0x90,0x0e,0x71,0xa0,0x07,0x78,0xa0,0x07,0x78,0xd0,0x06,0xe9,0x80,0x07, + 0x7a,0x80,0x07,0x7a,0x80,0x07,0x6d,0x90,0x0e,0x71,0x60,0x07,0x7a,0x10,0x07,0x76, + 0xa0,0x07,0x71,0x60,0x07,0x6d,0x90,0x0e,0x73,0x20,0x07,0x7a,0x30,0x07,0x72,0xa0, + 0x07,0x73,0x20,0x07,0x6d,0x90,0x0e,0x76,0x40,0x07,0x7a,0x60,0x07,0x74,0xa0,0x07, + 0x76,0x40,0x07,0x6d,0x60,0x0e,0x73,0x20,0x07,0x7a,0x30,0x07,0x72,0xa0,0x07,0x73, + 0x20,0x07,0x6d,0x60,0x0e,0x76,0x40,0x07,0x7a,0x60,0x07,0x74,0xa0,0x07,0x76,0x40, + 0x07,0x6d,0x60,0x0f,0x71,0x60,0x07,0x7a,0x10,0x07,0x76,0xa0,0x07,0x71,0x60,0x07, + 0x6d,0x60,0x0f,0x72,0x40,0x07,0x7a,0x30,0x07,0x72,0xa0,0x07,0x73,0x20,0x07,0x6d, + 0x60,0x0f,0x73,0x20,0x07,0x7a,0x30,0x07,0x72,0xa0,0x07,0x73,0x20,0x07,0x6d,0x60, + 0x0f,0x74,0x80,0x07,0x7a,0x60,0x07,0x74,0xa0,0x07,0x76,0x40,0x07,0x6d,0x60,0x0f, + 0x76,0x40,0x07,0x7a,0x60,0x07,0x74,0xa0,0x07,0x76,0x40,0x07,0x6d,0x60,0x0f,0x79, + 0x60,0x07,0x7a,0x10,0x07,0x72,0x80,0x07,0x7a,0x10,0x07,0x72,0x80,0x07,0x6d,0x60, + 0x0f,0x71,0x20,0x07,0x78,0xa0,0x07,0x71,0x20,0x07,0x78,0xa0,0x07,0x71,0x20,0x07, + 0x78,0xd0,0x06,0xf6,0x10,0x07,0x79,0x20,0x07,0x7a,0x20,0x07,0x75,0x60,0x07,0x7a, + 0x20,0x07,0x75,0x60,0x07,0x6d,0x60,0x0f,0x72,0x50,0x07,0x76,0xa0,0x07,0x72,0x50, + 0x07,0x76,0xa0,0x07,0x72,0x50,0x07,0x76,0xd0,0x06,0xf6,0x50,0x07,0x71,0x20,0x07, + 0x7a,0x50,0x07,0x71,0x20,0x07,0x7a,0x50,0x07,0x71,0x20,0x07,0x6d,0x60,0x0f,0x71, + 0x00,0x07,0x72,0x40,0x07,0x7a,0x10,0x07,0x70,0x20,0x07,0x74,0xa0,0x07,0x71,0x00, + 0x07,0x72,0x40,0x07,0x6d,0xe0,0x0e,0x78,0xa0,0x07,0x71,0x60,0x07,0x7a,0x30,0x07, + 0x72,0x30,0x84,0x49,0x00,0x00,0x08,0x00,0x00,0x00,0x00,0x00,0xc8,0x02,0x01,0x00, + 0x00,0x0b,0x00,0x00,0x00,0x32,0x1e,0x98,0x10,0x19,0x11,0x4c,0x90,0x8c,0x09,0x26, + 0x47,0xc6,0x04,0x43,0x5a,0x25,0x30,0x02,0x50,0x04,0x05,0x18,0x50,0x08,0x65,0x50, + 0x80,0x02,0x05,0x51,0x20,0xd4,0x46,0x00,0x88,0x8d,0x25,0x3c,0x00,0x00,0x00,0x00, + 0x00,0x79,0x18,0x00,0x00,0x01,0x01,0x00,0x00,0x1a,0x03,0x4c,0x10,0x97,0x29,0xa2, + 0x25,0x10,0xab,0x32,0xb9,0xb9,0xb4,0x37,0xb7,0x21,0xc6,0x32,0x28,0x00,0xb3,0x50, + 0xb9,0x1b,0x43,0x0b,0x93,0xfb,0x9a,0x4b,0xd3,0x2b,0x1b,0x62,0x2c,0x81,0x22,0x2c, + 0x05,0xe3,0x20,0x08,0x0e,0x8e,0xad,0x0c,0xa4,0xad,0x8c,0x2e,0x8c,0x0d,0xc4,0xae, + 0x4c,0x6e,0x2e,0xed,0xcd,0x0d,0x64,0x46,0x06,0x46,0x66,0xc6,0x65,0x66,0xa6,0x06, + 0x04,0xa5,0xad,0x8c,0x2e,0x8c,0xcd,0xac,0xac,0x65,0x46,0x06,0x46,0x66,0xc6,0x65, + 0x66,0xa6,0x26,0x65,0x88,0xa0,0x10,0x43,0x8c,0x25,0x58,0x90,0x45,0x60,0xd1,0x54, + 0x46,0x17,0xc6,0x36,0x04,0x51,0x8e,0x25,0x58,0x82,0x45,0xe0,0x16,0x96,0x26,0xe7, + 0x32,0xf6,0xd6,0x06,0x97,0xc6,0x56,0xe6,0x42,0x56,0xe6,0xf6,0x26,0xd7,0x36,0xf7, + 0x45,0x96,0x36,0x17,0x26,0xc6,0x56,0x36,0x44,0x50,0x12,0x72,0x61,0x69,0x72,0x2e, + 0x63,0x6f,0x6d,0x70,0x69,0x6c,0x65,0x2e,0x66,0x61,0x73,0x74,0x5f,0x6d,0x61,0x74, + 0x68,0x5f,0x65,0x6e,0x61,0x62,0x6c,0x65,0x43,0x04,0x65,0x61,0x19,0x84,0xa5,0xc9, + 0xb9,0x8c,0xbd,0xb5,0xc1,0xa5,0xb1,0x95,0xb9,0x98,0xc9,0x85,0xb5,0x95,0x89,0xd5, + 0x99,0x99,0x95,0xc9,0x7d,0x99,0x95,0xd1,0x8d,0xa1,0x7d,0x91,0xa5,0xcd,0x85,0x89, + 0xb1,0x95,0x0d,0x11,0x94,0x86,0x51,0x58,0x9a,0x9c,0x8b,0x5d,0x99,0x1c,0x5d,0x19, + 0xde,0xd7,0x5b,0x1d,0x1d,0x5c,0x1d,0x1d,0x97,0xba,0xb9,0x32,0x39,0x14,0xb6,0xb7, + 0x31,0x37,0x98,0x14,0x46,0x61,0x69,0x72,0x2e,0x61,0x72,0x67,0x5f,0x74,0x79,0x70, + 0x65,0x5f,0x6e,0x61,0x6d,0x65,0x34,0xcc,0xd8,0xde,0xc2,0xe8,0x68,0xc8,0x84,0xa5, + 0xc9,0xb9,0x84,0xc9,0x9d,0x7d,0xb9,0x85,0xb5,0x95,0x51,0xa8,0xb3,0x1b,0xc2,0x28, + 0x8f,0x02,0x29,0x91,0x22,0x29,0x93,0x42,0x71,0xa9,0x9b,0x2b,0x93,0x43,0x61,0x7b, + 0x1b,0x73,0x8b,0x49,0x61,0x31,0xf6,0xc6,0xf6,0x26,0x37,0x84,0x51,0x1e,0xc5,0x52, + 0x22,0x45,0x52,0x26,0xe5,0x22,0x13,0x96,0x26,0xe7,0x02,0xf7,0x36,0x97,0x46,0x97, + 0xf6,0xe6,0xc6,0xe5,0x8c,0xed,0x0b,0xea,0x6d,0x2e,0x8d,0x2e,0xed,0xcd,0x6d,0x88, + 0xa2,0x64,0x4a,0xa4,0x48,0xca,0xa4,0x68,0x74,0xc2,0xd2,0xe4,0x5c,0xe0,0xde,0xd2, + 0xdc,0xe8,0xbe,0xe6,0xd2,0xf4,0xca,0x58,0x98,0xb1,0xbd,0x85,0xd1,0x91,0x39,0x63, + 0xfb,0x82,0x7a,0x4b,0x73,0xa3,0x9b,0x4a,0xd3,0x2b,0x1b,0xa2,0x28,0x9c,0x12,0x29, + 0x9d,0x32,0x29,0xde,0x10,0x44,0xa9,0x14,0x4c,0xd9,0x94,0x8f,0x50,0x58,0x9a,0x9c, + 0x8b,0x5d,0x99,0x1c,0x5d,0x19,0xde,0x57,0x9a,0x1b,0x5c,0x1d,0x1d,0xa5,0xb0,0x34, + 0x39,0x17,0xb6,0xb7,0xb1,0x30,0xba,0xb4,0x37,0xb7,0xaf,0x34,0x37,0xb2,0x32,0x3c, + 0x7a,0x67,0x65,0x6e,0x65,0x72,0x61,0x74,0x65,0x64,0x28,0x5f,0x5f,0x61,0x69,0x72, + 0x5f,0x70,0x6c,0x61,0x63,0x65,0x68,0x6f,0x6c,0x64,0x65,0x72,0x5f,0x5f,0x29,0x44, + 0xe0,0xde,0xe6,0xd2,0xe8,0xd2,0xde,0xdc,0x86,0x50,0x8b,0xa0,0x84,0x81,0x22,0x06, + 0x8b,0xb0,0x04,0xca,0x18,0x28,0x91,0x22,0x29,0x93,0x42,0x06,0x34,0xcc,0xd8,0xde, + 0xc2,0xe8,0x64,0x98,0xd0,0x95,0xe1,0x8d,0xbd,0xbd,0xc9,0x91,0xc1,0x0c,0xa1,0x96, + 0x40,0x09,0x03,0x45,0x0c,0x96,0x60,0x09,0x94,0x31,0x50,0x22,0xc5,0x0c,0x94,0x49, + 0x39,0x03,0x1a,0x63,0x6f,0x6c,0x6f,0x72,0x30,0x43,0xa8,0x65,0x50,0xc2,0x40,0x11, + 0x83,0x65,0x58,0x02,0x65,0x0c,0x94,0x48,0x91,0x94,0x49,0x49,0x03,0x16,0x70,0x73, + 0x69,0x7a,0x65,0x43,0xa8,0xc5,0x50,0xc2,0x40,0x11,0x83,0xc5,0x58,0x02,0x65,0x0c, + 0x94,0x48,0xe9,0x94,0x49,0x59,0x03,0x2a,0x61,0x69,0x72,0x2e,0x62,0x75,0x66,0x66, + 0x65,0x72,0x7c,0xc2,0xd2,0xe4,0x5c,0xc4,0xea,0xcc,0xcc,0xca,0xe4,0xbe,0xe6,0xd2, + 0xf4,0xca,0x88,0x84,0xa5,0xc9,0xb9,0xc8,0x95,0x85,0x91,0x91,0x0a,0x4b,0x93,0x73, + 0x99,0xa3,0x93,0xab,0x1b,0xa3,0xfb,0xa2,0xcb,0x83,0x2b,0xfb,0x4a,0x73,0x33,0x7b, + 0x23,0x62,0xc6,0xf6,0x16,0x46,0x47,0x83,0x47,0xc3,0xa1,0xcd,0x0e,0x8e,0x02,0x5d, + 0xdb,0x10,0x6a,0x11,0x16,0x62,0x11,0x94,0x38,0x50,0xe4,0x60,0x21,0x16,0x62,0x11, + 0x94,0x38,0x50,0xe6,0x80,0x51,0x58,0x9a,0x9c,0x4b,0x98,0xdc,0xd9,0x17,0x5d,0x1e, + 0x5c,0xd9,0xd7,0x5c,0x9a,0x5e,0x19,0xaf,0xb0,0x34,0x39,0x97,0x30,0xb9,0xb3,0x2f, + 0xba,0x3c,0xb8,0xb2,0xaf,0x30,0xb6,0xb4,0x33,0xb7,0xaf,0xb9,0x34,0xbd,0x32,0x26, + 0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x1c,0xbe,0x62,0x72,0x86,0x90,0xc1, + 0x52,0x28,0x6d,0xa0,0xb8,0xc1,0x72,0x28,0x62,0xb0,0x08,0x4b,0xa0,0xbc,0x81,0x02, + 0x07,0x0a,0x1d,0x28,0x75,0xb0,0x1c,0x8a,0x1d,0x2c,0x89,0x12,0x29,0x77,0xa0,0x4c, + 0x0a,0x1e,0x0c,0x51,0x94,0x32,0x50,0xd0,0x40,0x51,0x03,0x85,0x0d,0x94,0x3c,0x18, + 0x62,0x24,0x80,0x02,0x06,0x8a,0x1e,0xf0,0x79,0x6b,0x73,0x4b,0x83,0x7b,0xa3,0x2b, + 0x73,0xa3,0x03,0x19,0x43,0x0b,0x93,0xe3,0x33,0x95,0xd6,0x06,0xc7,0x56,0x06,0x32, + 0xb4,0xb2,0x02,0x42,0x25,0x14,0x14,0x34,0x44,0x50,0xfa,0x60,0x88,0xa1,0xf0,0x81, + 0xe2,0x07,0x8d,0x32,0xc4,0x50,0xfe,0x40,0xf9,0x83,0x46,0x19,0x11,0xb1,0x03,0x3b, + 0xd8,0x43,0x3b,0xb8,0x41,0x3b,0xbc,0x03,0x39,0xd4,0x03,0x3b,0x94,0x83,0x1b,0x98, + 0x03,0x3b,0x84,0xc3,0x39,0xcc,0xc3,0x14,0x21,0x18,0x46,0x28,0xec,0xc0,0x0e,0xf6, + 0xd0,0x0e,0x6e,0x90,0x0e,0xe4,0x50,0x0e,0xee,0x40,0x0f,0x53,0x82,0x62,0xc4,0x12, + 0x0e,0xe9,0x20,0x0f,0x6e,0x60,0x0f,0xe5,0x20,0x0f,0xf3,0x90,0x0e,0xef,0xe0,0x0e, + 0x53,0x02,0x63,0x04,0x15,0x0e,0xe9,0x20,0x0f,0x6e,0xc0,0x0e,0xe1,0xe0,0x0e,0xe7, + 0x50,0x0f,0xe1,0x70,0x0e,0xe5,0xf0,0x0b,0xf6,0x50,0x0e,0xf2,0x30,0x0f,0xe9,0xf0, + 0x0e,0xee,0x30,0x25,0x40,0x46,0x4c,0xe1,0x90,0x0e,0xf2,0xe0,0x06,0xe3,0xf0,0x0e, + 0xed,0x00,0x0f,0xe9,0xc0,0x0e,0xe5,0xf0,0x0b,0xef,0x00,0x0f,0xf4,0x90,0x0e,0xef, + 0xe0,0x0e,0xf3,0x30,0x65,0x50,0x18,0x67,0x84,0x12,0x0e,0xe9,0x20,0x0f,0x6e,0x60, + 0x0f,0xe5,0x20,0x0f,0xf4,0x50,0x0e,0xf8,0x30,0x25,0xd8,0x03,0x00,0x79,0x18,0x00, + 0x00,0xa5,0x00,0x00,0x00,0x33,0x08,0x80,0x1c,0xc4,0xe1,0x1c,0x66,0x14,0x01,0x3d, + 0x88,0x43,0x38,0x84,0xc3,0x8c,0x42,0x80,0x07,0x79,0x78,0x07,0x73,0x98,0x71,0x0c, + 0xe6,0x00,0x0f,0xed,0x10,0x0e,0xf4,0x80,0x0e,0x33,0x0c,0x42,0x1e,0xc2,0xc1,0x1d, + 0xce,0xa1,0x1c,0x66,0x30,0x05,0x3d,0x88,0x43,0x38,0x84,0x83,0x1b,0xcc,0x03,0x3d, + 0xc8,0x43,0x3d,0x8c,0x03,0x3d,0xcc,0x78,0x8c,0x74,0x70,0x07,0x7b,0x08,0x07,0x79, + 0x48,0x87,0x70,0x70,0x07,0x7a,0x70,0x03,0x76,0x78,0x87,0x70,0x20,0x87,0x19,0xcc, + 0x11,0x0e,0xec,0x90,0x0e,0xe1,0x30,0x0f,0x6e,0x30,0x0f,0xe3,0xf0,0x0e,0xf0,0x50, + 0x0e,0x33,0x10,0xc4,0x1d,0xde,0x21,0x1c,0xd8,0x21,0x1d,0xc2,0x61,0x1e,0x66,0x30, + 0x89,0x3b,0xbc,0x83,0x3b,0xd0,0x43,0x39,0xb4,0x03,0x3c,0xbc,0x83,0x3c,0x84,0x03, + 0x3b,0xcc,0xf0,0x14,0x76,0x60,0x07,0x7b,0x68,0x07,0x37,0x68,0x87,0x72,0x68,0x07, + 0x37,0x80,0x87,0x70,0x90,0x87,0x70,0x60,0x07,0x76,0x28,0x07,0x76,0xf8,0x05,0x76, + 0x78,0x87,0x77,0x80,0x87,0x5f,0x08,0x87,0x71,0x18,0x87,0x72,0x98,0x87,0x79,0x98, + 0x81,0x2c,0xee,0xf0,0x0e,0xee,0xe0,0x0e,0xf5,0xc0,0x0e,0xec,0x30,0x03,0x62,0xc8, + 0xa1,0x1c,0xe4,0xa1,0x1c,0xcc,0xa1,0x1c,0xe4,0xa1,0x1c,0xdc,0x61,0x1c,0xca,0x21, + 0x1c,0xc4,0x81,0x1d,0xca,0x61,0x06,0xd6,0x90,0x43,0x39,0xc8,0x43,0x39,0x98,0x43, + 0x39,0xc8,0x43,0x39,0xb8,0xc3,0x38,0x94,0x43,0x38,0x88,0x03,0x3b,0x94,0xc3,0x2f, + 0xbc,0x83,0x3c,0xfc,0x82,0x3b,0xd4,0x03,0x3b,0xb0,0xc3,0x0c,0xc7,0x69,0x87,0x70, + 0x58,0x87,0x72,0x70,0x83,0x74,0x68,0x07,0x78,0x60,0x87,0x74,0x18,0x87,0x74,0xa0, + 0x87,0x19,0xce,0x53,0x0f,0xee,0x00,0x0f,0xf2,0x50,0x0e,0xe4,0x90,0x0e,0xe3,0x40, + 0x0f,0xe1,0x20,0x0e,0xec,0x50,0x0e,0x33,0x20,0x28,0x1d,0xdc,0xc1,0x1e,0xc2,0x41, + 0x1e,0xd2,0x21,0x1c,0xdc,0x81,0x1e,0xdc,0xe0,0x1c,0xe4,0xe1,0x1d,0xea,0x01,0x1e, + 0x66,0x18,0x51,0x38,0xb0,0x43,0x3a,0x9c,0x83,0x3b,0xcc,0x50,0x24,0x76,0x60,0x07, + 0x7b,0x68,0x07,0x37,0x60,0x87,0x77,0x78,0x07,0x78,0x98,0x51,0x4c,0xf4,0x90,0x0f, + 0xf0,0x50,0x0e,0x33,0x1e,0x6a,0x1e,0xca,0x61,0x1c,0xe8,0x21,0x1d,0xde,0xc1,0x1d, + 0x7e,0x01,0x1e,0xe4,0xa1,0x1c,0xcc,0x21,0x1d,0xf0,0x61,0x06,0x54,0x85,0x83,0x38, + 0xcc,0xc3,0x3b,0xb0,0x43,0x3d,0xd0,0x43,0x39,0xfc,0xc2,0x3c,0xe4,0x43,0x3b,0x88, + 0xc3,0x3b,0xb0,0xc3,0x8c,0xc5,0x0a,0x87,0x79,0x98,0x87,0x77,0x18,0x87,0x74,0x08, + 0x07,0x7a,0x28,0x07,0x72,0x98,0x81,0x5c,0xe3,0x10,0x0e,0xec,0xc0,0x0e,0xe5,0x50, + 0x0e,0xf3,0x30,0x23,0xc1,0xd2,0x41,0x1e,0xe4,0xe1,0x17,0xd8,0xe1,0x1d,0xde,0x01, + 0x1e,0x66,0x48,0x19,0x3b,0xb0,0x83,0x3d,0xb4,0x83,0x1b,0x84,0xc3,0x38,0x8c,0x43, + 0x39,0xcc,0xc3,0x3c,0xb8,0xc1,0x39,0xc8,0xc3,0x3b,0xd4,0x03,0x3c,0xcc,0x48,0xb4, + 0x71,0x08,0x07,0x76,0x60,0x07,0x71,0x08,0x87,0x71,0x58,0x87,0x19,0xdb,0xc6,0x0e, + 0xec,0x60,0x0f,0xed,0xe0,0x06,0xf0,0x20,0x0f,0xe5,0x30,0x0f,0xe5,0x20,0x0f,0xf6, + 0x50,0x0e,0x6e,0x10,0x0e,0xe3,0x30,0x0e,0xe5,0x30,0x0f,0xf3,0xe0,0x06,0xe9,0xe0, + 0x0e,0xe4,0x50,0x0e,0xf8,0x30,0x23,0xe2,0xec,0x61,0x1c,0xc2,0x81,0x1d,0xd8,0xe1, + 0x17,0xec,0x21,0x1d,0xe6,0x21,0x1d,0xc4,0x21,0x1d,0xd8,0x21,0x1d,0xe8,0x21,0x1f, + 0x66,0x20,0x9d,0x3b,0xbc,0x43,0x3d,0xb8,0x03,0x39,0x94,0x83,0x39,0xcc,0x58,0xbc, + 0x70,0x70,0x07,0x77,0x78,0x07,0x7a,0x08,0x07,0x7a,0x48,0x87,0x77,0x70,0x87,0x19, + 0xce,0x87,0x0e,0xe5,0x10,0x0e,0xf0,0x10,0x0e,0xec,0xc0,0x0e,0xef,0x30,0x0e,0xf3, + 0x90,0x0e,0xf4,0x50,0x0e,0x33,0x28,0x30,0x08,0x87,0x74,0x90,0x07,0x37,0x30,0x87, + 0x7a,0x70,0x87,0x71,0xa0,0x87,0x74,0x78,0x07,0x77,0xf8,0x85,0x73,0x90,0x87,0x77, + 0xa8,0x07,0x78,0x98,0x07,0x00,0x00,0x00,0x00,0x71,0x20,0x00,0x00,0x02,0x00,0x00, + 0x00,0x06,0x50,0x30,0x00,0xd2,0xd0,0x00,0x00,0x61,0x20,0x00,0x00,0x3e,0x00,0x00, + 0x00,0x13,0x04,0x41,0x2c,0x10,0x00,0x00,0x00,0x09,0x00,0x00,0x00,0xf4,0xc6,0x22, + 0x86,0x61,0x18,0xc6,0x22,0x04,0x41,0x10,0xc6,0x22,0x82,0x20,0x08,0x46,0x00,0xa8, + 0x95,0x40,0x19,0x14,0x01,0x8d,0x19,0x00,0x12,0x33,0x00,0x14,0x66,0x00,0x66,0x00, + 0x00,0xe3,0x15,0x8c,0xa4,0x69,0x12,0x05,0x65,0x90,0x41,0x22,0x14,0x13,0x02,0xf9, + 0x8c,0x57,0x40,0x96,0xe7,0x2d,0x14,0x94,0x41,0x06,0xeb,0x78,0x4c,0x08,0xe4,0x63, + 0x41,0x01,0x9f,0xf1,0x8a,0x6a,0x1b,0x83,0x31,0x70,0x28,0x28,0x83,0x0c,0x1b,0x53, + 0x99,0x10,0xc8,0xc7,0x8a,0x00,0x3e,0xe3,0x15,0x1a,0x18,0xa0,0x01,0x1a,0x50,0x14, + 0x94,0x41,0x06,0x30,0x88,0x36,0x13,0x02,0xf9,0x58,0x11,0xc0,0x67,0xbc,0xe2,0x2b, + 0x03,0x37,0x68,0x83,0x32,0xa0,0xa0,0x0c,0x32,0x90,0x41,0xd6,0x99,0x10,0xc8,0x67, + 0xbc,0x62,0x0c,0xd2,0x40,0x0e,0xe2,0xc0,0xa3,0xa0,0x0c,0x32,0xa0,0x41,0x27,0x06, + 0x26,0x04,0xf2,0xb1,0xa0,0x80,0xcf,0x78,0x05,0x1a,0xb8,0xc1,0x1d,0xd8,0x81,0x18, + 0x50,0x50,0x6c,0x08,0xe0,0x33,0xdb,0x20,0x06,0x01,0x30,0xdb,0x10,0xb8,0x41,0x30, + 0xdb,0x10,0x3c,0xc2,0x6c,0x43,0xf0,0x06,0x43,0x06,0x01,0x31,0x00,0x09,0x00,0x00, + 0x00,0x5b,0x86,0x20,0x00,0x85,0x2d,0x43,0x11,0x80,0xc2,0x96,0x41,0x09,0x40,0x61, + 0xcb,0xf0,0x04,0xa0,0xb0,0x65,0xa0,0x02,0x50,0xd8,0x32,0x60,0x01,0x28,0x6c,0x19, + 0xba,0x00,0x14,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00, +}; +/* + #include + #include + + using namespace metal; + + struct main0_out + { + float4 frag_color [[color(0)]]; + }; + + struct main0_in + { + float4 uv [[user(locn0)]]; + float4 color [[user(locn1)]]; + }; + + fragment main0_out main0(main0_in in [[stage_in]], texture2d tex [[texture(0)]], sampler smp [[sampler(0)]]) + { + main0_out out = {}; + out.frag_color = float4(1.0, 1.0, 1.0, tex.sample(smp, in.uv.xy).x) * in.color; + return out; + } +*/ +static const uint8_t _sfons_fs_bytecode_metal_macos[3065] = { + 0x4d,0x54,0x4c,0x42,0x01,0x80,0x02,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xf9,0x0b,0x00,0x00,0x00,0x00,0x00,0x00,0x58,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x6d,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc9,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xd1,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xd9,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x20,0x0b,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x6d,0x00,0x00,0x00, + 0x4e,0x41,0x4d,0x45,0x06,0x00,0x6d,0x61,0x69,0x6e,0x30,0x00,0x54,0x59,0x50,0x45, + 0x01,0x00,0x01,0x48,0x41,0x53,0x48,0x20,0x00,0x00,0x93,0xa3,0x32,0x70,0x27,0x04, + 0x4a,0x88,0x6f,0x1e,0x08,0x67,0xd0,0x5a,0xba,0xd2,0x72,0x48,0xca,0xe7,0x64,0x0b, + 0xdf,0x26,0xd3,0x77,0xf1,0x3d,0xde,0x93,0x55,0x4f,0x46,0x46,0x54,0x18,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x56,0x45,0x52,0x53,0x08,0x00,0x01,0x00,0x08, + 0x00,0x01,0x00,0x01,0x00,0x45,0x4e,0x44,0x54,0x04,0x00,0x00,0x00,0x45,0x4e,0x44, + 0x54,0x04,0x00,0x00,0x00,0x45,0x4e,0x44,0x54,0xde,0xc0,0x17,0x0b,0x00,0x00,0x00, + 0x00,0x14,0x00,0x00,0x00,0x00,0x0b,0x00,0x00,0xff,0xff,0xff,0xff,0x42,0x43,0xc0, + 0xde,0x21,0x0c,0x00,0x00,0xbd,0x02,0x00,0x00,0x0b,0x82,0x20,0x00,0x02,0x00,0x00, + 0x00,0x12,0x00,0x00,0x00,0x07,0x81,0x23,0x91,0x41,0xc8,0x04,0x49,0x06,0x10,0x32, + 0x39,0x92,0x01,0x84,0x0c,0x25,0x05,0x08,0x19,0x1e,0x04,0x8b,0x62,0x80,0x14,0x45, + 0x02,0x42,0x92,0x0b,0x42,0xa4,0x10,0x32,0x14,0x38,0x08,0x18,0x49,0x0a,0x32,0x44, + 0x24,0x48,0x0a,0x90,0x21,0x23,0xc4,0x52,0x80,0x0c,0x19,0x21,0x72,0x24,0x07,0xc8, + 0x48,0x11,0x62,0xa8,0xa0,0xa8,0x40,0xc6,0xf0,0x01,0x00,0x00,0x00,0x51,0x18,0x00, + 0x00,0x74,0x00,0x00,0x00,0x1b,0xc2,0x24,0xf8,0xff,0xff,0xff,0xff,0x01,0x60,0x00, + 0x09,0xa8,0x88,0x70,0x80,0x07,0x78,0x90,0x87,0x77,0xc0,0x87,0x36,0x30,0x87,0x7a, + 0x70,0x87,0x71,0x68,0x03,0x73,0x80,0x87,0x36,0x68,0x87,0x70,0xa0,0x07,0x74,0x00, + 0xe8,0x41,0x1e,0xea,0xa1,0x1c,0x00,0xa2,0x1d,0xd2,0xc1,0x1d,0xda,0x80,0x1d,0xca, + 0xe1,0x1c,0xc2,0x81,0x1d,0xda,0xc0,0x1e,0xca,0x61,0x1c,0xe8,0xe1,0x1d,0xe4,0xa1, + 0x0d,0xee,0x21,0x1d,0xc8,0x81,0x1e,0xd0,0x01,0x88,0x03,0x39,0xc0,0x03,0x60,0x70, + 0x87,0x77,0x68,0x03,0x71,0xa8,0x87,0x74,0x60,0x07,0x7a,0x48,0x07,0x77,0x98,0x07, + 0x80,0x70,0x87,0x77,0x68,0x03,0x73,0x90,0x87,0x70,0x68,0x87,0x72,0x68,0x03,0x78, + 0x78,0x87,0x74,0x70,0x07,0x7a,0x28,0x07,0x79,0x68,0x83,0x72,0x60,0x87,0x74,0x68, + 0x07,0x80,0x1e,0xe4,0xa1,0x1e,0xca,0x01,0x20,0xdc,0xe1,0x1d,0xda,0xc0,0x1c,0xe4, + 0x21,0x1c,0xda,0xa1,0x1c,0xda,0x00,0x1e,0xde,0x21,0x1d,0xdc,0x81,0x1e,0xca,0x41, + 0x1e,0xda,0xa0,0x1c,0xd8,0x21,0x1d,0xda,0xa1,0x0d,0xdc,0xe1,0x1d,0xdc,0xa1,0x0d, + 0xd8,0xa1,0x1c,0xc2,0xc1,0x1c,0x00,0x7a,0x90,0x87,0x7a,0x28,0x07,0x80,0x70,0x87, + 0x77,0x68,0x83,0x74,0x70,0x07,0x73,0x98,0x87,0x36,0x30,0x07,0x78,0x68,0x83,0x76, + 0x08,0x07,0x7a,0x40,0x07,0x80,0x1e,0xe4,0xa1,0x1e,0xca,0x01,0x20,0xdc,0xe1,0x1d, + 0xda,0xc0,0x1d,0xc2,0xc1,0x1d,0xe6,0xa1,0x0d,0xcc,0x01,0x1e,0xda,0xa0,0x1d,0xc2, + 0x81,0x1e,0xd0,0x01,0xa0,0x07,0x79,0xa8,0x87,0x72,0x00,0x08,0x77,0x78,0x87,0x36, + 0x98,0x87,0x74,0x38,0x07,0x77,0x28,0x07,0x72,0x68,0x03,0x7d,0x28,0x07,0x79,0x78, + 0x87,0x79,0x68,0x03,0x73,0x80,0x87,0x36,0x68,0x87,0x70,0xa0,0x07,0x74,0x00,0xe8, + 0x41,0x1e,0xea,0xa1,0x1c,0x00,0xc2,0x1d,0xde,0xa1,0x0d,0xe8,0x41,0x1e,0xc2,0x01, + 0x1e,0xe0,0x21,0x1d,0xdc,0xe1,0x1c,0xda,0xa0,0x1d,0xc2,0x81,0x1e,0xd0,0x01,0xa0, + 0x07,0x79,0xa8,0x87,0x72,0x00,0x88,0x79,0xa0,0x87,0x70,0x18,0x87,0x75,0x68,0x03, + 0x78,0x90,0x87,0x77,0xa0,0x87,0x72,0x18,0x07,0x7a,0x78,0x07,0x79,0x68,0x03,0x71, + 0xa8,0x07,0x73,0x30,0x87,0x72,0x90,0x87,0x36,0x98,0x87,0x74,0xd0,0x87,0x72,0x00, + 0xf0,0x00,0x20,0xea,0xc1,0x1d,0xe6,0x21,0x1c,0xcc,0xa1,0x1c,0xda,0xc0,0x1c,0xe0, + 0xa1,0x0d,0xda,0x21,0x1c,0xe8,0x01,0x1d,0x00,0x7a,0x90,0x87,0x7a,0x28,0x07,0x60, + 0x83,0x21,0x0c,0xc0,0x02,0x54,0x1b,0x8c,0x81,0x00,0x16,0xa0,0xda,0x80,0x10,0xff, + 0xff,0xff,0xff,0x3f,0x00,0x0c,0x20,0x01,0xd5,0x06,0xa3,0x08,0x80,0x05,0xa8,0x36, + 0x18,0x86,0x00,0x2c,0x40,0xb5,0x01,0x39,0xfe,0xff,0xff,0xff,0x7f,0x00,0x18,0x40, + 0x02,0x2a,0x00,0x00,0x00,0x49,0x18,0x00,0x00,0x04,0x00,0x00,0x00,0x13,0x86,0x40, + 0x18,0x26,0x0c,0x44,0x61,0x4c,0x18,0x8e,0xc2,0x00,0x00,0x00,0x00,0x89,0x20,0x00, + 0x00,0x1e,0x00,0x00,0x00,0x32,0x22,0x48,0x09,0x20,0x64,0x85,0x04,0x93,0x22,0xa4, + 0x84,0x04,0x93,0x22,0xe3,0x84,0xa1,0x90,0x14,0x12,0x4c,0x8a,0x8c,0x0b,0x84,0xa4, + 0x4c,0x10,0x4c,0x33,0x00,0xc3,0x08,0x04,0x60,0x83,0x70,0x94,0x34,0x45,0x94,0x30, + 0xf9,0xff,0x44,0x5c,0x13,0x15,0x11,0xbf,0x3d,0xfc,0xd3,0x18,0x01,0x30,0x88,0x30, + 0x04,0x17,0x49,0x53,0x44,0x09,0x93,0xff,0x4b,0x00,0xf3,0x2c,0x44,0xf4,0x4f,0x63, + 0x04,0xc0,0x20,0x42,0x21,0x94,0x42,0x84,0x40,0x0c,0x9d,0x61,0x04,0x01,0x98,0x23, + 0x08,0xe6,0x08,0xc0,0x60,0x18,0x41,0x58,0x0a,0x12,0x88,0x49,0x8a,0x29,0x40,0x6d, + 0x20,0x20,0x05,0xd6,0x30,0x02,0xb1,0x8c,0x00,0x00,0x00,0x00,0x00,0x13,0xb2,0x70, + 0x48,0x07,0x79,0xb0,0x03,0x3a,0x68,0x83,0x70,0x80,0x07,0x78,0x60,0x87,0x72,0x68, + 0x83,0x76,0x08,0x87,0x71,0x78,0x87,0x79,0xc0,0x87,0x38,0x80,0x03,0x37,0x88,0x83, + 0x38,0x70,0x03,0x38,0xd8,0x70,0x1b,0xe5,0xd0,0x06,0xf0,0xa0,0x07,0x76,0x40,0x07, + 0x7a,0x60,0x07,0x74,0xa0,0x07,0x76,0x40,0x07,0x6d,0x90,0x0e,0x71,0xa0,0x07,0x78, + 0xa0,0x07,0x78,0xd0,0x06,0xe9,0x80,0x07,0x7a,0x80,0x07,0x7a,0x80,0x07,0x6d,0x90, + 0x0e,0x71,0x60,0x07,0x7a,0x10,0x07,0x76,0xa0,0x07,0x71,0x60,0x07,0x6d,0x90,0x0e, + 0x73,0x20,0x07,0x7a,0x30,0x07,0x72,0xa0,0x07,0x73,0x20,0x07,0x6d,0x90,0x0e,0x76, + 0x40,0x07,0x7a,0x60,0x07,0x74,0xa0,0x07,0x76,0x40,0x07,0x6d,0x60,0x0e,0x73,0x20, + 0x07,0x7a,0x30,0x07,0x72,0xa0,0x07,0x73,0x20,0x07,0x6d,0x60,0x0e,0x76,0x40,0x07, + 0x7a,0x60,0x07,0x74,0xa0,0x07,0x76,0x40,0x07,0x6d,0x60,0x0f,0x71,0x60,0x07,0x7a, + 0x10,0x07,0x76,0xa0,0x07,0x71,0x60,0x07,0x6d,0x60,0x0f,0x72,0x40,0x07,0x7a,0x30, + 0x07,0x72,0xa0,0x07,0x73,0x20,0x07,0x6d,0x60,0x0f,0x73,0x20,0x07,0x7a,0x30,0x07, + 0x72,0xa0,0x07,0x73,0x20,0x07,0x6d,0x60,0x0f,0x74,0x80,0x07,0x7a,0x60,0x07,0x74, + 0xa0,0x07,0x76,0x40,0x07,0x6d,0x60,0x0f,0x76,0x40,0x07,0x7a,0x60,0x07,0x74,0xa0, + 0x07,0x76,0x40,0x07,0x6d,0x60,0x0f,0x79,0x60,0x07,0x7a,0x10,0x07,0x72,0x80,0x07, + 0x7a,0x10,0x07,0x72,0x80,0x07,0x6d,0x60,0x0f,0x71,0x20,0x07,0x78,0xa0,0x07,0x71, + 0x20,0x07,0x78,0xa0,0x07,0x71,0x20,0x07,0x78,0xd0,0x06,0xf6,0x10,0x07,0x79,0x20, + 0x07,0x7a,0x20,0x07,0x75,0x60,0x07,0x7a,0x20,0x07,0x75,0x60,0x07,0x6d,0x60,0x0f, + 0x72,0x50,0x07,0x76,0xa0,0x07,0x72,0x50,0x07,0x76,0xa0,0x07,0x72,0x50,0x07,0x76, + 0xd0,0x06,0xf6,0x50,0x07,0x71,0x20,0x07,0x7a,0x50,0x07,0x71,0x20,0x07,0x7a,0x50, + 0x07,0x71,0x20,0x07,0x6d,0x60,0x0f,0x71,0x00,0x07,0x72,0x40,0x07,0x7a,0x10,0x07, + 0x70,0x20,0x07,0x74,0xa0,0x07,0x71,0x00,0x07,0x72,0x40,0x07,0x6d,0xe0,0x0e,0x78, + 0xa0,0x07,0x71,0x60,0x07,0x7a,0x30,0x07,0x72,0x30,0x84,0x41,0x00,0x00,0x08,0x00, + 0x00,0x00,0x00,0x00,0x18,0xc2,0x38,0x40,0x00,0x08,0x00,0x00,0x00,0x00,0x00,0x64, + 0x81,0x00,0x00,0x00,0x00,0x08,0x00,0x00,0x00,0x32,0x1e,0x98,0x10,0x19,0x11,0x4c, + 0x90,0x8c,0x09,0x26,0x47,0xc6,0x04,0x43,0x5a,0x25,0x30,0x02,0x50,0x04,0x85,0x50, + 0x10,0x65,0x40,0x70,0x2c,0xe1,0x01,0x00,0x00,0x79,0x18,0x00,0x00,0xd1,0x00,0x00, + 0x00,0x1a,0x03,0x4c,0x10,0x97,0x29,0xa2,0x25,0x10,0xab,0x32,0xb9,0xb9,0xb4,0x37, + 0xb7,0x21,0xc6,0x42,0x3c,0x00,0x84,0x50,0xb9,0x1b,0x43,0x0b,0x93,0xfb,0x9a,0x4b, + 0xd3,0x2b,0x1b,0x62,0x2c,0xc2,0x23,0x2c,0x05,0xe3,0x20,0x08,0x0e,0x8e,0xad,0x0c, + 0xa4,0xad,0x8c,0x2e,0x8c,0x0d,0xc4,0xae,0x4c,0x6e,0x2e,0xed,0xcd,0x0d,0x64,0x46, + 0x06,0x46,0x66,0xc6,0x65,0x66,0xa6,0x06,0x04,0xa5,0xad,0x8c,0x2e,0x8c,0xcd,0xac, + 0xac,0x65,0x46,0x06,0x46,0x66,0xc6,0x65,0x66,0xa6,0x26,0x65,0x88,0xf0,0x10,0x43, + 0x8c,0x45,0x58,0x8c,0x65,0x60,0xd1,0x54,0x46,0x17,0xc6,0x36,0x04,0x79,0x8e,0x45, + 0x58,0x84,0x65,0xe0,0x16,0x96,0x26,0xe7,0x32,0xf6,0xd6,0x06,0x97,0xc6,0x56,0xe6, + 0x42,0x56,0xe6,0xf6,0x26,0xd7,0x36,0xf7,0x45,0x96,0x36,0x17,0x26,0xc6,0x56,0x36, + 0x44,0x78,0x12,0x72,0x61,0x69,0x72,0x2e,0x63,0x6f,0x6d,0x70,0x69,0x6c,0x65,0x2e, + 0x66,0x61,0x73,0x74,0x5f,0x6d,0x61,0x74,0x68,0x5f,0x65,0x6e,0x61,0x62,0x6c,0x65, + 0x43,0x84,0x67,0x61,0x19,0x84,0xa5,0xc9,0xb9,0x8c,0xbd,0xb5,0xc1,0xa5,0xb1,0x95, + 0xb9,0x98,0xc9,0x85,0xb5,0x95,0x89,0xd5,0x99,0x99,0x95,0xc9,0x7d,0x99,0x95,0xd1, + 0x8d,0xa1,0x7d,0x91,0xa5,0xcd,0x85,0x89,0xb1,0x95,0x0d,0x11,0x9e,0x86,0x51,0x58, + 0x9a,0x9c,0x8b,0x5c,0x99,0x1b,0x59,0x99,0xdc,0x17,0x5d,0x98,0xdc,0x59,0x19,0x1d, + 0xa3,0xb0,0x34,0x39,0x97,0x30,0xb9,0xb3,0x2f,0xba,0x3c,0xb8,0xb2,0x2f,0xb7,0xb0, + 0xb6,0x32,0x1a,0x66,0x6c,0x6f,0x61,0x74,0x34,0x64,0xc2,0xd2,0xe4,0x5c,0xc2,0xe4, + 0xce,0xbe,0xdc,0xc2,0xda,0xca,0xa8,0x98,0xc9,0x85,0x9d,0x7d,0x8d,0xbd,0xb1,0xbd, + 0xc9,0x0d,0x61,0x9e,0x67,0x19,0x1e,0xe8,0x89,0x1e,0xe9,0x99,0x86,0x08,0x0f,0x45, + 0x29,0x2c,0x4d,0xce,0xc5,0x4c,0x2e,0xec,0xac,0xad,0xcc,0x8d,0xee,0x2b,0xcd,0x0d, + 0xae,0x8e,0x8e,0x4b,0xdd,0x5c,0x99,0x1c,0x0a,0xdb,0xdb,0x98,0x1b,0x4c,0x0a,0x95, + 0xb0,0x34,0x39,0x97,0xb1,0x32,0x37,0xba,0x32,0x39,0x3e,0x61,0x69,0x72,0x2e,0x70, + 0x65,0x72,0x73,0x70,0x65,0x63,0x74,0x69,0x76,0x65,0x14,0xea,0xec,0x86,0x48,0xcb, + 0xf0,0x58,0xcf,0xf5,0x60,0x4f,0xf6,0x40,0x4f,0xf4,0x48,0x8f,0xc6,0xa5,0x6e,0xae, + 0x4c,0x0e,0x85,0xed,0x6d,0xcc,0x2d,0x26,0x85,0xc5,0xd8,0x1b,0xdb,0x9b,0xdc,0x10, + 0x69,0x11,0x1e,0xeb,0xe1,0x1e,0xec,0xc9,0x1e,0xe8,0x89,0x1e,0xe9,0xe9,0xb8,0x84, + 0xa5,0xc9,0xb9,0xd0,0x95,0xe1,0xd1,0xd5,0xc9,0x95,0x51,0x0a,0x4b,0x93,0x73,0x61, + 0x7b,0x1b,0x0b,0xa3,0x4b,0x7b,0x73,0xfb,0x4a,0x73,0x23,0x2b,0xc3,0xa3,0x12,0x96, + 0x26,0xe7,0x32,0x17,0xd6,0x06,0xc7,0x56,0x46,0x8c,0xae,0x0c,0x8f,0xae,0x4e,0xae, + 0x4c,0x86,0x8c,0xc7,0x8c,0xed,0x2d,0x8c,0x8e,0x05,0x64,0x2e,0xac,0x0d,0x8e,0xad, + 0xcc,0x87,0x03,0x5d,0x19,0xde,0x10,0x6a,0x21,0x9e,0xef,0x01,0x83,0x65,0x58,0x84, + 0x27,0x0c,0x1e,0xe8,0x11,0x83,0x47,0x7a,0xc6,0x80,0x4b,0x58,0x9a,0x9c,0xcb,0x5c, + 0x58,0x1b,0x1c,0x5b,0x99,0x1c,0x8f,0xb9,0xb0,0x36,0x38,0xb6,0x32,0x39,0x0e,0x73, + 0x6d,0x70,0x43,0xa4,0xe5,0x78,0xca,0xe0,0x01,0x83,0x65,0x58,0x84,0x07,0x7a,0xcc, + 0xe0,0x91,0x9e,0x33,0x18,0x82,0x3c,0xdb,0xe3,0x3d,0x64,0xf0,0xa0,0xc1,0x10,0x03, + 0x01,0x9e,0xea,0x49,0x03,0x5e,0x61,0x69,0x72,0x2d,0x61,0x6c,0x69,0x61,0x73,0x2d, + 0x73,0x63,0x6f,0x70,0x65,0x73,0x28,0x6d,0x61,0x69,0x6e,0x30,0x29,0x43,0x88,0x87, + 0x0d,0x9e,0x35,0x20,0x16,0x96,0x26,0xd7,0x12,0xc6,0x96,0x16,0x36,0xd7,0x32,0x37, + 0xf6,0x06,0x57,0xd6,0x42,0x57,0x86,0x47,0x57,0x27,0x57,0x36,0x37,0xc4,0x78,0xdc, + 0xe0,0x61,0x83,0xa7,0x0d,0x88,0x85,0xa5,0xc9,0xb5,0x84,0xb1,0xa5,0x85,0xcd,0xb5, + 0xcc,0x8d,0xbd,0xc1,0x95,0xb5,0xcc,0x85,0xb5,0xc1,0xb1,0x95,0xc9,0xcd,0x0d,0x31, + 0x1e,0x38,0x78,0xd8,0xe0,0x79,0x83,0x21,0xc4,0xe3,0x06,0x0f,0x1c,0x8c,0x88,0xd8, + 0x81,0x1d,0xec,0xa1,0x1d,0xdc,0xa0,0x1d,0xde,0x81,0x1c,0xea,0x81,0x1d,0xca,0xc1, + 0x0d,0xcc,0x81,0x1d,0xc2,0xe1,0x1c,0xe6,0x61,0x8a,0x10,0x0c,0x23,0x14,0x76,0x60, + 0x07,0x7b,0x68,0x07,0x37,0x48,0x07,0x72,0x28,0x07,0x77,0xa0,0x87,0x29,0x41,0x31, + 0x62,0x09,0x87,0x74,0x90,0x07,0x37,0xb0,0x87,0x72,0x90,0x87,0x79,0x48,0x87,0x77, + 0x70,0x87,0x29,0x81,0x31,0x82,0x0a,0x87,0x74,0x90,0x07,0x37,0x60,0x87,0x70,0x70, + 0x87,0x73,0xa8,0x87,0x70,0x38,0x87,0x72,0xf8,0x05,0x7b,0x28,0x07,0x79,0x98,0x87, + 0x74,0x78,0x07,0x77,0x98,0x12,0x20,0x23,0xa6,0x70,0x48,0x07,0x79,0x70,0x83,0x71, + 0x78,0x87,0x76,0x80,0x87,0x74,0x60,0x87,0x72,0xf8,0x85,0x77,0x80,0x07,0x7a,0x48, + 0x87,0x77,0x70,0x87,0x79,0x98,0x32,0x28,0x8c,0x33,0x82,0x09,0x87,0x74,0x90,0x07, + 0x37,0x30,0x07,0x79,0x08,0x87,0x73,0x68,0x87,0x72,0x70,0x07,0x7a,0x98,0x12,0xa8, + 0x01,0x00,0x00,0x00,0x00,0x79,0x18,0x00,0x00,0xa5,0x00,0x00,0x00,0x33,0x08,0x80, + 0x1c,0xc4,0xe1,0x1c,0x66,0x14,0x01,0x3d,0x88,0x43,0x38,0x84,0xc3,0x8c,0x42,0x80, + 0x07,0x79,0x78,0x07,0x73,0x98,0x71,0x0c,0xe6,0x00,0x0f,0xed,0x10,0x0e,0xf4,0x80, + 0x0e,0x33,0x0c,0x42,0x1e,0xc2,0xc1,0x1d,0xce,0xa1,0x1c,0x66,0x30,0x05,0x3d,0x88, + 0x43,0x38,0x84,0x83,0x1b,0xcc,0x03,0x3d,0xc8,0x43,0x3d,0x8c,0x03,0x3d,0xcc,0x78, + 0x8c,0x74,0x70,0x07,0x7b,0x08,0x07,0x79,0x48,0x87,0x70,0x70,0x07,0x7a,0x70,0x03, + 0x76,0x78,0x87,0x70,0x20,0x87,0x19,0xcc,0x11,0x0e,0xec,0x90,0x0e,0xe1,0x30,0x0f, + 0x6e,0x30,0x0f,0xe3,0xf0,0x0e,0xf0,0x50,0x0e,0x33,0x10,0xc4,0x1d,0xde,0x21,0x1c, + 0xd8,0x21,0x1d,0xc2,0x61,0x1e,0x66,0x30,0x89,0x3b,0xbc,0x83,0x3b,0xd0,0x43,0x39, + 0xb4,0x03,0x3c,0xbc,0x83,0x3c,0x84,0x03,0x3b,0xcc,0xf0,0x14,0x76,0x60,0x07,0x7b, + 0x68,0x07,0x37,0x68,0x87,0x72,0x68,0x07,0x37,0x80,0x87,0x70,0x90,0x87,0x70,0x60, + 0x07,0x76,0x28,0x07,0x76,0xf8,0x05,0x76,0x78,0x87,0x77,0x80,0x87,0x5f,0x08,0x87, + 0x71,0x18,0x87,0x72,0x98,0x87,0x79,0x98,0x81,0x2c,0xee,0xf0,0x0e,0xee,0xe0,0x0e, + 0xf5,0xc0,0x0e,0xec,0x30,0x03,0x62,0xc8,0xa1,0x1c,0xe4,0xa1,0x1c,0xcc,0xa1,0x1c, + 0xe4,0xa1,0x1c,0xdc,0x61,0x1c,0xca,0x21,0x1c,0xc4,0x81,0x1d,0xca,0x61,0x06,0xd6, + 0x90,0x43,0x39,0xc8,0x43,0x39,0x98,0x43,0x39,0xc8,0x43,0x39,0xb8,0xc3,0x38,0x94, + 0x43,0x38,0x88,0x03,0x3b,0x94,0xc3,0x2f,0xbc,0x83,0x3c,0xfc,0x82,0x3b,0xd4,0x03, + 0x3b,0xb0,0xc3,0x0c,0xc7,0x69,0x87,0x70,0x58,0x87,0x72,0x70,0x83,0x74,0x68,0x07, + 0x78,0x60,0x87,0x74,0x18,0x87,0x74,0xa0,0x87,0x19,0xce,0x53,0x0f,0xee,0x00,0x0f, + 0xf2,0x50,0x0e,0xe4,0x90,0x0e,0xe3,0x40,0x0f,0xe1,0x20,0x0e,0xec,0x50,0x0e,0x33, + 0x20,0x28,0x1d,0xdc,0xc1,0x1e,0xc2,0x41,0x1e,0xd2,0x21,0x1c,0xdc,0x81,0x1e,0xdc, + 0xe0,0x1c,0xe4,0xe1,0x1d,0xea,0x01,0x1e,0x66,0x18,0x51,0x38,0xb0,0x43,0x3a,0x9c, + 0x83,0x3b,0xcc,0x50,0x24,0x76,0x60,0x07,0x7b,0x68,0x07,0x37,0x60,0x87,0x77,0x78, + 0x07,0x78,0x98,0x51,0x4c,0xf4,0x90,0x0f,0xf0,0x50,0x0e,0x33,0x1e,0x6a,0x1e,0xca, + 0x61,0x1c,0xe8,0x21,0x1d,0xde,0xc1,0x1d,0x7e,0x01,0x1e,0xe4,0xa1,0x1c,0xcc,0x21, + 0x1d,0xf0,0x61,0x06,0x54,0x85,0x83,0x38,0xcc,0xc3,0x3b,0xb0,0x43,0x3d,0xd0,0x43, + 0x39,0xfc,0xc2,0x3c,0xe4,0x43,0x3b,0x88,0xc3,0x3b,0xb0,0xc3,0x8c,0xc5,0x0a,0x87, + 0x79,0x98,0x87,0x77,0x18,0x87,0x74,0x08,0x07,0x7a,0x28,0x07,0x72,0x98,0x81,0x5c, + 0xe3,0x10,0x0e,0xec,0xc0,0x0e,0xe5,0x50,0x0e,0xf3,0x30,0x23,0xc1,0xd2,0x41,0x1e, + 0xe4,0xe1,0x17,0xd8,0xe1,0x1d,0xde,0x01,0x1e,0x66,0x48,0x19,0x3b,0xb0,0x83,0x3d, + 0xb4,0x83,0x1b,0x84,0xc3,0x38,0x8c,0x43,0x39,0xcc,0xc3,0x3c,0xb8,0xc1,0x39,0xc8, + 0xc3,0x3b,0xd4,0x03,0x3c,0xcc,0x48,0xb4,0x71,0x08,0x07,0x76,0x60,0x07,0x71,0x08, + 0x87,0x71,0x58,0x87,0x19,0xdb,0xc6,0x0e,0xec,0x60,0x0f,0xed,0xe0,0x06,0xf0,0x20, + 0x0f,0xe5,0x30,0x0f,0xe5,0x20,0x0f,0xf6,0x50,0x0e,0x6e,0x10,0x0e,0xe3,0x30,0x0e, + 0xe5,0x30,0x0f,0xf3,0xe0,0x06,0xe9,0xe0,0x0e,0xe4,0x50,0x0e,0xf8,0x30,0x23,0xe2, + 0xec,0x61,0x1c,0xc2,0x81,0x1d,0xd8,0xe1,0x17,0xec,0x21,0x1d,0xe6,0x21,0x1d,0xc4, + 0x21,0x1d,0xd8,0x21,0x1d,0xe8,0x21,0x1f,0x66,0x20,0x9d,0x3b,0xbc,0x43,0x3d,0xb8, + 0x03,0x39,0x94,0x83,0x39,0xcc,0x58,0xbc,0x70,0x70,0x07,0x77,0x78,0x07,0x7a,0x08, + 0x07,0x7a,0x48,0x87,0x77,0x70,0x87,0x19,0xce,0x87,0x0e,0xe5,0x10,0x0e,0xf0,0x10, + 0x0e,0xec,0xc0,0x0e,0xef,0x30,0x0e,0xf3,0x90,0x0e,0xf4,0x50,0x0e,0x33,0x28,0x30, + 0x08,0x87,0x74,0x90,0x07,0x37,0x30,0x87,0x7a,0x70,0x87,0x71,0xa0,0x87,0x74,0x78, + 0x07,0x77,0xf8,0x85,0x73,0x90,0x87,0x77,0xa8,0x07,0x78,0x98,0x07,0x00,0x00,0x00, + 0x00,0x71,0x20,0x00,0x00,0x08,0x00,0x00,0x00,0x16,0xb0,0x01,0x48,0xe4,0x4b,0x00, + 0xf3,0x2c,0xc4,0x3f,0x11,0xd7,0x44,0x45,0xc4,0x6f,0x0f,0x7e,0x85,0x17,0xb7,0x6d, + 0x00,0x05,0x03,0x20,0x0d,0x0d,0x00,0x00,0x00,0x61,0x20,0x00,0x00,0x1a,0x00,0x00, + 0x00,0x13,0x04,0x41,0x2c,0x10,0x00,0x00,0x00,0x0b,0x00,0x00,0x00,0x14,0xc7,0x22, + 0x80,0x40,0x20,0x88,0x8d,0x00,0x8c,0x25,0x00,0x01,0xa9,0x11,0x80,0x1a,0x20,0x31, + 0x03,0x40,0x61,0x0e,0xe2,0xba,0xae,0x6a,0x06,0x80,0xc0,0x0c,0xc0,0x08,0xc0,0x18, + 0x01,0x08,0x82,0x20,0xfe,0x01,0x00,0x00,0x00,0x83,0x0c,0x0f,0x91,0x8c,0x18,0x28, + 0x43,0x80,0x39,0x4d,0x80,0x2c,0xc9,0x30,0xc8,0x70,0x04,0x8d,0x05,0x91,0x7c,0x66, + 0x1b,0x94,0x00,0xc8,0x20,0x20,0x06,0x00,0x00,0x02,0x00,0x00,0x00,0x5b,0x86,0xe0, + 0x88,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +}; +/* + #include + #include + + using namespace metal; + + struct vs_params + { + float4x4 mvp; + float4x4 tm; + }; + + struct main0_out + { + float4 uv [[user(locn0)]]; + float4 color [[user(locn1)]]; + float4 gl_Position [[position]]; + float gl_PointSize [[point_size]]; + }; + + struct main0_in + { + float4 position [[attribute(0)]]; + float2 texcoord0 [[attribute(1)]]; + float4 color0 [[attribute(2)]]; + float psize [[attribute(3)]]; + }; + + vertex main0_out main0(main0_in in [[stage_in]], constant vs_params& _19 [[buffer(0)]]) + { + main0_out out = {}; + out.gl_Position = _19.mvp * in.position; + out.gl_PointSize = in.psize; + out.uv = _19.tm * float4(in.texcoord0, 0.0, 1.0); + out.color = in.color0; + return out; + } +*/ +static const uint8_t _sfons_vs_bytecode_metal_ios[3493] = { + 0x4d,0x54,0x4c,0x42,0x01,0x00,0x02,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xa5,0x0d,0x00,0x00,0x00,0x00,0x00,0x00,0x58,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x6d,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc9,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x44,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0d,0x01,0x00,0x00,0x00,0x00,0x00,0x00, + 0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x15,0x01,0x00,0x00,0x00,0x00,0x00,0x00, + 0x90,0x0c,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x6d,0x00,0x00,0x00, + 0x4e,0x41,0x4d,0x45,0x06,0x00,0x6d,0x61,0x69,0x6e,0x30,0x00,0x54,0x59,0x50,0x45, + 0x01,0x00,0x00,0x48,0x41,0x53,0x48,0x20,0x00,0x81,0x80,0x75,0xbc,0xa9,0xd0,0xb9, + 0x4f,0xee,0x63,0x10,0x8f,0xfe,0x66,0xa5,0x3b,0x40,0xb7,0x43,0x44,0x12,0xba,0x69, + 0x7d,0xf3,0x83,0x9a,0xda,0x47,0x00,0x29,0xec,0x4f,0x46,0x46,0x54,0x18,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x56,0x45,0x52,0x53,0x08,0x00,0x01,0x00,0x08, + 0x00,0x01,0x00,0x01,0x00,0x45,0x4e,0x44,0x54,0x40,0x00,0x00,0x00,0x56,0x41,0x54, + 0x54,0x2a,0x00,0x04,0x00,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x00,0x00,0x80, + 0x74,0x65,0x78,0x63,0x6f,0x6f,0x72,0x64,0x30,0x00,0x01,0x80,0x63,0x6f,0x6c,0x6f, + 0x72,0x30,0x00,0x02,0x80,0x70,0x73,0x69,0x7a,0x65,0x00,0x03,0x80,0x56,0x41,0x54, + 0x59,0x06,0x00,0x04,0x00,0x06,0x04,0x06,0x03,0x45,0x4e,0x44,0x54,0x04,0x00,0x00, + 0x00,0x45,0x4e,0x44,0x54,0xde,0xc0,0x17,0x0b,0x00,0x00,0x00,0x00,0x14,0x00,0x00, + 0x00,0x70,0x0c,0x00,0x00,0xff,0xff,0xff,0xff,0x42,0x43,0xc0,0xde,0x21,0x0c,0x00, + 0x00,0x19,0x03,0x00,0x00,0x0b,0x82,0x20,0x00,0x02,0x00,0x00,0x00,0x12,0x00,0x00, + 0x00,0x07,0x81,0x23,0x91,0x41,0xc8,0x04,0x49,0x06,0x10,0x32,0x39,0x92,0x01,0x84, + 0x0c,0x25,0x05,0x08,0x19,0x1e,0x04,0x8b,0x62,0x80,0x14,0x45,0x02,0x42,0x92,0x0b, + 0x42,0xa4,0x10,0x32,0x14,0x38,0x08,0x18,0x49,0x0a,0x32,0x44,0x24,0x48,0x0a,0x90, + 0x21,0x23,0xc4,0x52,0x80,0x0c,0x19,0x21,0x72,0x24,0x07,0xc8,0x48,0x11,0x62,0xa8, + 0xa0,0xa8,0x40,0xc6,0xf0,0x01,0x00,0x00,0x00,0x51,0x18,0x00,0x00,0x70,0x00,0x00, + 0x00,0x1b,0x7e,0x24,0xf8,0xff,0xff,0xff,0xff,0x01,0x90,0x00,0x8a,0x08,0x07,0x78, + 0x80,0x07,0x79,0x78,0x07,0x7c,0x68,0x03,0x73,0xa8,0x07,0x77,0x18,0x87,0x36,0x30, + 0x07,0x78,0x68,0x83,0x76,0x08,0x07,0x7a,0x40,0x07,0x80,0x1e,0xe4,0xa1,0x1e,0xca, + 0x01,0x20,0xda,0x21,0x1d,0xdc,0xa1,0x0d,0xd8,0xa1,0x1c,0xce,0x21,0x1c,0xd8,0xa1, + 0x0d,0xec,0xa1,0x1c,0xc6,0x81,0x1e,0xde,0x41,0x1e,0xda,0xe0,0x1e,0xd2,0x81,0x1c, + 0xe8,0x01,0x1d,0x80,0x38,0x90,0x03,0x3c,0x00,0x06,0x77,0x78,0x87,0x36,0x10,0x87, + 0x7a,0x48,0x07,0x76,0xa0,0x87,0x74,0x70,0x87,0x79,0x00,0x08,0x77,0x78,0x87,0x36, + 0x30,0x07,0x79,0x08,0x87,0x76,0x28,0x87,0x36,0x80,0x87,0x77,0x48,0x07,0x77,0xa0, + 0x87,0x72,0x90,0x87,0x36,0x28,0x07,0x76,0x48,0x87,0x76,0x00,0xe8,0x41,0x1e,0xea, + 0xa1,0x1c,0x00,0xc2,0x1d,0xde,0xa1,0x0d,0xcc,0x41,0x1e,0xc2,0xa1,0x1d,0xca,0xa1, + 0x0d,0xe0,0xe1,0x1d,0xd2,0xc1,0x1d,0xe8,0xa1,0x1c,0xe4,0xa1,0x0d,0xca,0x81,0x1d, + 0xd2,0xa1,0x1d,0xda,0xc0,0x1d,0xde,0xc1,0x1d,0xda,0x80,0x1d,0xca,0x21,0x1c,0xcc, + 0x01,0xa0,0x07,0x79,0xa8,0x87,0x72,0x00,0x08,0x77,0x78,0x87,0x36,0x48,0x07,0x77, + 0x30,0x87,0x79,0x68,0x03,0x73,0x80,0x87,0x36,0x68,0x87,0x70,0xa0,0x07,0x74,0x00, + 0xe8,0x41,0x1e,0xea,0xa1,0x1c,0x00,0xc2,0x1d,0xde,0xa1,0x0d,0xdc,0x21,0x1c,0xdc, + 0x61,0x1e,0xda,0xc0,0x1c,0xe0,0xa1,0x0d,0xda,0x21,0x1c,0xe8,0x01,0x1d,0x00,0x7a, + 0x90,0x87,0x7a,0x28,0x07,0x80,0x70,0x87,0x77,0x68,0x83,0x79,0x48,0x87,0x73,0x70, + 0x87,0x72,0x20,0x87,0x36,0xd0,0x87,0x72,0x90,0x87,0x77,0x98,0x87,0x36,0x30,0x07, + 0x78,0x68,0x83,0x76,0x08,0x07,0x7a,0x40,0x07,0x80,0x1e,0xe4,0xa1,0x1e,0xca,0x01, + 0x20,0xdc,0xe1,0x1d,0xda,0x80,0x1e,0xe4,0x21,0x1c,0xe0,0x01,0x1e,0xd2,0xc1,0x1d, + 0xce,0xa1,0x0d,0xda,0x21,0x1c,0xe8,0x01,0x1d,0x00,0x7a,0x90,0x87,0x7a,0x28,0x07, + 0x80,0x98,0x07,0x7a,0x08,0x87,0x71,0x58,0x87,0x36,0x80,0x07,0x79,0x78,0x07,0x7a, + 0x28,0x87,0x71,0xa0,0x87,0x77,0x90,0x87,0x36,0x10,0x87,0x7a,0x30,0x07,0x73,0x28, + 0x07,0x79,0x68,0x83,0x79,0x48,0x07,0x7d,0x28,0x07,0x00,0x0f,0x00,0xa2,0x1e,0xdc, + 0x61,0x1e,0xc2,0xc1,0x1c,0xca,0xa1,0x0d,0xcc,0x01,0x1e,0xda,0xa0,0x1d,0xc2,0x81, + 0x1e,0xd0,0x01,0xa0,0x07,0x79,0xa8,0x87,0x72,0x00,0x36,0x6c,0x42,0x01,0x2c,0x40, + 0x35,0x84,0x43,0x3a,0xc8,0x43,0x1b,0x88,0x43,0x3d,0x98,0x83,0x39,0x94,0x83,0x3c, + 0xb4,0x81,0x3b,0xbc,0x43,0x1b,0x84,0x03,0x3b,0xa4,0x43,0x38,0xcc,0x03,0x00,0x00, + 0x00,0x49,0x18,0x00,0x00,0x01,0x00,0x00,0x00,0x13,0x84,0x40,0x00,0x89,0x20,0x00, + 0x00,0x20,0x00,0x00,0x00,0x32,0x22,0x48,0x09,0x20,0x64,0x85,0x04,0x93,0x22,0xa4, + 0x84,0x04,0x93,0x22,0xe3,0x84,0xa1,0x90,0x14,0x12,0x4c,0x8a,0x8c,0x0b,0x84,0xa4, + 0x4c,0x10,0x44,0x33,0x00,0xc3,0x08,0x04,0x60,0x89,0x10,0x02,0x18,0x46,0x10,0x80, + 0x24,0x08,0x33,0x51,0xf3,0x40,0x0f,0xf2,0x50,0x0f,0xe3,0x40,0x0f,0x6e,0xd0,0x0e, + 0xe5,0x40,0x0f,0xe1,0xc0,0x0e,0x7a,0xa0,0x07,0xed,0x10,0x0e,0xf4,0x20,0x0f,0xe9, + 0x80,0x0f,0x28,0x20,0x07,0x49,0x53,0x44,0x09,0x93,0x5f,0x49,0xff,0x03,0x44,0x00, + 0x23,0x21,0xa1,0x94,0x41,0x04,0x43,0x28,0x86,0x08,0x23,0x80,0x43,0x68,0x20,0x60, + 0x8e,0x00,0x0c,0x52,0x60,0xcd,0x11,0x80,0xc2,0x20,0x42,0x20,0x0c,0x23,0x10,0xcb, + 0x08,0x00,0x00,0x00,0x00,0x13,0xa8,0x70,0x48,0x07,0x79,0xb0,0x03,0x3a,0x68,0x83, + 0x70,0x80,0x07,0x78,0x60,0x87,0x72,0x68,0x83,0x74,0x78,0x87,0x79,0xc8,0x03,0x37, + 0x80,0x03,0x37,0x80,0x83,0x0d,0xb7,0x51,0x0e,0x6d,0x00,0x0f,0x7a,0x60,0x07,0x74, + 0xa0,0x07,0x76,0x40,0x07,0x7a,0x60,0x07,0x74,0xd0,0x06,0xe9,0x10,0x07,0x7a,0x80, + 0x07,0x7a,0x80,0x07,0x6d,0x90,0x0e,0x78,0xa0,0x07,0x78,0xa0,0x07,0x78,0xd0,0x06, + 0xe9,0x10,0x07,0x76,0xa0,0x07,0x71,0x60,0x07,0x7a,0x10,0x07,0x76,0xd0,0x06,0xe9, + 0x30,0x07,0x72,0xa0,0x07,0x73,0x20,0x07,0x7a,0x30,0x07,0x72,0xd0,0x06,0xe9,0x60, + 0x07,0x74,0xa0,0x07,0x76,0x40,0x07,0x7a,0x60,0x07,0x74,0xd0,0x06,0xe6,0x30,0x07, + 0x72,0xa0,0x07,0x73,0x20,0x07,0x7a,0x30,0x07,0x72,0xd0,0x06,0xe6,0x60,0x07,0x74, + 0xa0,0x07,0x76,0x40,0x07,0x7a,0x60,0x07,0x74,0xd0,0x06,0xf6,0x10,0x07,0x76,0xa0, + 0x07,0x71,0x60,0x07,0x7a,0x10,0x07,0x76,0xd0,0x06,0xf6,0x20,0x07,0x74,0xa0,0x07, + 0x73,0x20,0x07,0x7a,0x30,0x07,0x72,0xd0,0x06,0xf6,0x30,0x07,0x72,0xa0,0x07,0x73, + 0x20,0x07,0x7a,0x30,0x07,0x72,0xd0,0x06,0xf6,0x40,0x07,0x78,0xa0,0x07,0x76,0x40, + 0x07,0x7a,0x60,0x07,0x74,0xd0,0x06,0xf6,0x60,0x07,0x74,0xa0,0x07,0x76,0x40,0x07, + 0x7a,0x60,0x07,0x74,0xd0,0x06,0xf6,0x90,0x07,0x76,0xa0,0x07,0x71,0x20,0x07,0x78, + 0xa0,0x07,0x71,0x20,0x07,0x78,0xd0,0x06,0xf6,0x10,0x07,0x72,0x80,0x07,0x7a,0x10, + 0x07,0x72,0x80,0x07,0x7a,0x10,0x07,0x72,0x80,0x07,0x6d,0x60,0x0f,0x71,0x90,0x07, + 0x72,0xa0,0x07,0x72,0x50,0x07,0x76,0xa0,0x07,0x72,0x50,0x07,0x76,0xd0,0x06,0xf6, + 0x20,0x07,0x75,0x60,0x07,0x7a,0x20,0x07,0x75,0x60,0x07,0x7a,0x20,0x07,0x75,0x60, + 0x07,0x6d,0x60,0x0f,0x75,0x10,0x07,0x72,0xa0,0x07,0x75,0x10,0x07,0x72,0xa0,0x07, + 0x75,0x10,0x07,0x72,0xd0,0x06,0xf6,0x10,0x07,0x70,0x20,0x07,0x74,0xa0,0x07,0x71, + 0x00,0x07,0x72,0x40,0x07,0x7a,0x10,0x07,0x70,0x20,0x07,0x74,0xd0,0x06,0xee,0x80, + 0x07,0x7a,0x10,0x07,0x76,0xa0,0x07,0x73,0x20,0x07,0x43,0x98,0x04,0x00,0x80,0x00, + 0x00,0x00,0x00,0x00,0x80,0x2c,0x10,0x00,0x00,0x0b,0x00,0x00,0x00,0x32,0x1e,0x98, + 0x10,0x19,0x11,0x4c,0x90,0x8c,0x09,0x26,0x47,0xc6,0x04,0x43,0x5a,0x25,0x30,0x02, + 0x50,0x04,0x05,0x18,0x50,0x08,0x65,0x50,0x80,0x02,0x05,0x51,0x20,0xd4,0x46,0x00, + 0x88,0x8d,0x25,0x48,0x00,0x00,0x00,0x00,0x00,0x79,0x18,0x00,0x00,0x12,0x01,0x00, + 0x00,0x1a,0x03,0x4c,0x10,0x97,0x29,0xa2,0x25,0x10,0xab,0x32,0xb9,0xb9,0xb4,0x37, + 0xb7,0x21,0xc6,0x32,0x28,0x00,0xb3,0x50,0xb9,0x1b,0x43,0x0b,0x93,0xfb,0x9a,0x4b, + 0xd3,0x2b,0x1b,0x62,0x2c,0x81,0x22,0x2c,0x05,0xe3,0x20,0x08,0x0e,0x8e,0xad,0x0c, + 0xa4,0xad,0x8c,0x2e,0x8c,0x0d,0xc4,0xae,0x4c,0x6e,0x2e,0xed,0xcd,0x0d,0x64,0x46, + 0x06,0x46,0x66,0xc6,0x65,0x66,0xa6,0x06,0x04,0xa5,0xad,0x8c,0x2e,0x8c,0xcd,0xac, + 0xac,0x65,0x46,0x06,0x46,0x66,0xc6,0x65,0x66,0xa6,0x26,0x65,0x88,0xa0,0x10,0x43, + 0x8c,0x25,0x58,0x90,0x45,0x60,0xd1,0x54,0x46,0x17,0xc6,0x36,0x04,0x51,0x8e,0x25, + 0x58,0x82,0x45,0xe0,0x16,0x96,0x26,0xe7,0x32,0xf6,0xd6,0x06,0x97,0xc6,0x56,0xe6, + 0x42,0x56,0xe6,0xf6,0x26,0xd7,0x36,0xf7,0x45,0x96,0x36,0x17,0x26,0xc6,0x56,0x36, + 0x44,0x50,0x12,0x72,0x61,0x69,0x72,0x2e,0x63,0x6f,0x6d,0x70,0x69,0x6c,0x65,0x2e, + 0x66,0x61,0x73,0x74,0x5f,0x6d,0x61,0x74,0x68,0x5f,0x65,0x6e,0x61,0x62,0x6c,0x65, + 0x43,0x04,0x65,0x21,0x19,0x84,0xa5,0xc9,0xb9,0x8c,0xbd,0xb5,0xc1,0xa5,0xb1,0x95, + 0xb9,0x98,0xc9,0x85,0xb5,0x95,0x89,0xd5,0x99,0x99,0x95,0xc9,0x7d,0x99,0x95,0xd1, + 0x8d,0xa1,0x7d,0x95,0xb9,0x85,0x89,0xb1,0x95,0x0d,0x11,0x94,0x86,0x51,0x58,0x9a, + 0x9c,0x8b,0x5d,0x99,0x1c,0x5d,0x19,0xde,0xd7,0x5b,0x1d,0x1d,0x5c,0x1d,0x1d,0x97, + 0xba,0xb9,0x32,0x39,0x14,0xb6,0xb7,0x31,0x37,0x98,0x14,0x46,0x61,0x69,0x72,0x2e, + 0x61,0x72,0x67,0x5f,0x74,0x79,0x70,0x65,0x5f,0x6e,0x61,0x6d,0x65,0x34,0xcc,0xd8, + 0xde,0xc2,0xe8,0x68,0xc8,0x84,0xa5,0xc9,0xb9,0x84,0xc9,0x9d,0x7d,0xb9,0x85,0xb5, + 0x95,0x51,0xa8,0xb3,0x1b,0xc2,0x28,0x8f,0x02,0x29,0x91,0x22,0x29,0x93,0x42,0x71, + 0xa9,0x9b,0x2b,0x93,0x43,0x61,0x7b,0x1b,0x73,0x8b,0x49,0x61,0x31,0xf6,0xc6,0xf6, + 0x26,0x37,0x84,0x51,0x1e,0xc5,0x52,0x22,0x45,0x52,0x26,0xe5,0x22,0x13,0x96,0x26, + 0xe7,0x02,0xf7,0x36,0x97,0x46,0x97,0xf6,0xe6,0xc6,0xe5,0x8c,0xed,0x0b,0xea,0x6d, + 0x2e,0x8d,0x2e,0xed,0xcd,0x6d,0x88,0xa2,0x64,0x4a,0xa4,0x48,0xca,0xa4,0x68,0x74, + 0xc2,0xd2,0xe4,0x5c,0xe0,0xde,0xd2,0xdc,0xe8,0xbe,0xe6,0xd2,0xf4,0xca,0x58,0x98, + 0xb1,0xbd,0x85,0xd1,0x91,0x39,0x63,0xfb,0x82,0x7a,0x4b,0x73,0xa3,0x9b,0x4a,0xd3, + 0x2b,0x1b,0xa2,0x28,0x9c,0x12,0x29,0x9d,0x32,0x29,0xde,0x10,0x44,0xa9,0x14,0x4c, + 0xd9,0x94,0x8f,0x50,0x58,0x9a,0x9c,0x8b,0x5d,0x99,0x1c,0x5d,0x19,0xde,0x57,0x9a, + 0x1b,0x5c,0x1d,0x1d,0xa5,0xb0,0x34,0x39,0x17,0xb6,0xb7,0xb1,0x30,0xba,0xb4,0x37, + 0xb7,0xaf,0x34,0x37,0xb2,0x32,0x3c,0x7a,0x67,0x65,0x6e,0x65,0x72,0x61,0x74,0x65, + 0x64,0x28,0x5f,0x5f,0x61,0x69,0x72,0x5f,0x70,0x6c,0x61,0x63,0x65,0x68,0x6f,0x6c, + 0x64,0x65,0x72,0x5f,0x5f,0x29,0x44,0xe0,0xde,0xe6,0xd2,0xe8,0xd2,0xde,0xdc,0x86, + 0x50,0x8b,0xa0,0x84,0x81,0x22,0x06,0x8b,0xb0,0x04,0xca,0x18,0x28,0x91,0x22,0x29, + 0x93,0x42,0x06,0x34,0xcc,0xd8,0xde,0xc2,0xe8,0x64,0x98,0xd0,0x95,0xe1,0x8d,0xbd, + 0xbd,0xc9,0x91,0xc1,0x0c,0xa1,0x96,0x40,0x09,0x03,0x45,0x0c,0x96,0x60,0x09,0x94, + 0x31,0x50,0x22,0xc5,0x0c,0x94,0x49,0x39,0x03,0x1a,0x63,0x6f,0x6c,0x6f,0x72,0x30, + 0x43,0xa8,0x65,0x50,0xc2,0x40,0x11,0x83,0x65,0x58,0x02,0x65,0x0c,0x94,0x48,0x91, + 0x94,0x49,0x49,0x03,0x16,0x70,0x73,0x69,0x7a,0x65,0x43,0xa8,0xc5,0x50,0xc2,0x40, + 0x11,0x83,0xc5,0x58,0x02,0x65,0x0c,0x94,0x48,0xe9,0x94,0x49,0x59,0x03,0x2a,0x61, + 0x69,0x72,0x2e,0x62,0x75,0x66,0x66,0x65,0x72,0x7c,0xc2,0xd2,0xe4,0x5c,0xc4,0xea, + 0xcc,0xcc,0xca,0xe4,0xbe,0xe6,0xd2,0xf4,0xca,0x88,0x84,0xa5,0xc9,0xb9,0xc8,0x95, + 0x85,0x91,0x91,0x0a,0x4b,0x93,0x73,0x99,0xa3,0x93,0xab,0x1b,0xa3,0xfb,0xa2,0xcb, + 0x83,0x2b,0xfb,0x4a,0x73,0x33,0x7b,0x23,0x62,0xc6,0xf6,0x16,0x46,0x47,0x83,0x47, + 0xc3,0xa1,0xcd,0x0e,0x8e,0x02,0x5d,0xdb,0x10,0x6a,0x11,0x16,0x62,0x11,0x94,0x38, + 0x50,0xe4,0x60,0x21,0x16,0x62,0x11,0x94,0x38,0x50,0xe6,0x80,0x51,0x58,0x9a,0x9c, + 0x4b,0x98,0xdc,0xd9,0x17,0x5d,0x1e,0x5c,0xd9,0xd7,0x5c,0x9a,0x5e,0x19,0xaf,0xb0, + 0x34,0x39,0x97,0x30,0xb9,0xb3,0x2f,0xba,0x3c,0xb8,0xb2,0xaf,0x30,0xb6,0xb4,0x33, + 0xb7,0xaf,0xb9,0x34,0xbd,0x32,0x26,0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73, + 0x1c,0xbe,0x62,0x72,0x86,0x90,0xc1,0x52,0x28,0x6d,0xa0,0xb8,0xc1,0x72,0x28,0x62, + 0xb0,0x08,0x4b,0xa0,0xbc,0x81,0x02,0x07,0x0a,0x1d,0x28,0x75,0xb0,0x1c,0x8a,0x1d, + 0x2c,0x89,0x12,0x29,0x77,0xa0,0x4c,0x0a,0x1e,0x0c,0x51,0x94,0x32,0x50,0xd0,0x40, + 0x51,0x03,0x85,0x0d,0x94,0x3c,0x18,0x62,0x24,0x80,0x02,0x06,0x8a,0x1e,0xf0,0x79, + 0x6b,0x73,0x4b,0x83,0x7b,0xa3,0x2b,0x73,0xa3,0x03,0x19,0x43,0x0b,0x93,0xe3,0x33, + 0x95,0xd6,0x06,0xc7,0x56,0x06,0x32,0xb4,0xb2,0x02,0x42,0x25,0x14,0x14,0x34,0x44, + 0x50,0xfa,0x60,0x88,0xa1,0xf0,0x81,0xe2,0x07,0x8d,0x32,0xc4,0x50,0xfe,0x40,0xf9, + 0x83,0x46,0xe1,0x15,0x96,0x26,0xd7,0x12,0xc6,0x96,0x16,0x36,0xd7,0x32,0x37,0xf6, + 0x06,0x57,0x36,0x87,0xd2,0x16,0x96,0xe6,0x06,0x93,0x32,0x84,0x50,0x44,0x41,0x09, + 0x05,0x5a,0x61,0x69,0x72,0x2d,0x61,0x6c,0x69,0x61,0x73,0x2d,0x73,0x63,0x6f,0x70, + 0x65,0x2d,0x61,0x72,0x67,0x28,0x34,0x29,0x43,0x0c,0x85,0x14,0x14,0x51,0x50,0x46, + 0x61,0x88,0xa0,0x90,0xc2,0x88,0x88,0x1d,0xd8,0xc1,0x1e,0xda,0xc1,0x0d,0xda,0xe1, + 0x1d,0xc8,0xa1,0x1e,0xd8,0xa1,0x1c,0xdc,0xc0,0x1c,0xd8,0x21,0x1c,0xce,0x61,0x1e, + 0xa6,0x08,0xc1,0x30,0x42,0x61,0x07,0x76,0xb0,0x87,0x76,0x70,0x83,0x74,0x20,0x87, + 0x72,0x70,0x07,0x7a,0x98,0x12,0x14,0x23,0x96,0x70,0x48,0x07,0x79,0x70,0x03,0x7b, + 0x28,0x07,0x79,0x98,0x87,0x74,0x78,0x07,0x77,0x98,0x12,0x18,0x23,0xa8,0x70,0x48, + 0x07,0x79,0x70,0x03,0x76,0x08,0x07,0x77,0x38,0x87,0x7a,0x08,0x87,0x73,0x28,0x87, + 0x5f,0xb0,0x87,0x72,0x90,0x87,0x79,0x48,0x87,0x77,0x70,0x87,0x29,0x01,0x32,0x62, + 0x0a,0x87,0x74,0x90,0x07,0x37,0x18,0x87,0x77,0x68,0x07,0x78,0x48,0x07,0x76,0x28, + 0x87,0x5f,0x78,0x07,0x78,0xa0,0x87,0x74,0x78,0x07,0x77,0x98,0x87,0x29,0x83,0xc2, + 0x38,0x23,0x94,0x70,0x48,0x07,0x79,0x70,0x03,0x7b,0x28,0x07,0x79,0xa0,0x87,0x72, + 0xc0,0x87,0x29,0xc1,0x1e,0x00,0x00,0x00,0x00,0x79,0x18,0x00,0x00,0xa5,0x00,0x00, + 0x00,0x33,0x08,0x80,0x1c,0xc4,0xe1,0x1c,0x66,0x14,0x01,0x3d,0x88,0x43,0x38,0x84, + 0xc3,0x8c,0x42,0x80,0x07,0x79,0x78,0x07,0x73,0x98,0x71,0x0c,0xe6,0x00,0x0f,0xed, + 0x10,0x0e,0xf4,0x80,0x0e,0x33,0x0c,0x42,0x1e,0xc2,0xc1,0x1d,0xce,0xa1,0x1c,0x66, + 0x30,0x05,0x3d,0x88,0x43,0x38,0x84,0x83,0x1b,0xcc,0x03,0x3d,0xc8,0x43,0x3d,0x8c, + 0x03,0x3d,0xcc,0x78,0x8c,0x74,0x70,0x07,0x7b,0x08,0x07,0x79,0x48,0x87,0x70,0x70, + 0x07,0x7a,0x70,0x03,0x76,0x78,0x87,0x70,0x20,0x87,0x19,0xcc,0x11,0x0e,0xec,0x90, + 0x0e,0xe1,0x30,0x0f,0x6e,0x30,0x0f,0xe3,0xf0,0x0e,0xf0,0x50,0x0e,0x33,0x10,0xc4, + 0x1d,0xde,0x21,0x1c,0xd8,0x21,0x1d,0xc2,0x61,0x1e,0x66,0x30,0x89,0x3b,0xbc,0x83, + 0x3b,0xd0,0x43,0x39,0xb4,0x03,0x3c,0xbc,0x83,0x3c,0x84,0x03,0x3b,0xcc,0xf0,0x14, + 0x76,0x60,0x07,0x7b,0x68,0x07,0x37,0x68,0x87,0x72,0x68,0x07,0x37,0x80,0x87,0x70, + 0x90,0x87,0x70,0x60,0x07,0x76,0x28,0x07,0x76,0xf8,0x05,0x76,0x78,0x87,0x77,0x80, + 0x87,0x5f,0x08,0x87,0x71,0x18,0x87,0x72,0x98,0x87,0x79,0x98,0x81,0x2c,0xee,0xf0, + 0x0e,0xee,0xe0,0x0e,0xf5,0xc0,0x0e,0xec,0x30,0x03,0x62,0xc8,0xa1,0x1c,0xe4,0xa1, + 0x1c,0xcc,0xa1,0x1c,0xe4,0xa1,0x1c,0xdc,0x61,0x1c,0xca,0x21,0x1c,0xc4,0x81,0x1d, + 0xca,0x61,0x06,0xd6,0x90,0x43,0x39,0xc8,0x43,0x39,0x98,0x43,0x39,0xc8,0x43,0x39, + 0xb8,0xc3,0x38,0x94,0x43,0x38,0x88,0x03,0x3b,0x94,0xc3,0x2f,0xbc,0x83,0x3c,0xfc, + 0x82,0x3b,0xd4,0x03,0x3b,0xb0,0xc3,0x0c,0xc7,0x69,0x87,0x70,0x58,0x87,0x72,0x70, + 0x83,0x74,0x68,0x07,0x78,0x60,0x87,0x74,0x18,0x87,0x74,0xa0,0x87,0x19,0xce,0x53, + 0x0f,0xee,0x00,0x0f,0xf2,0x50,0x0e,0xe4,0x90,0x0e,0xe3,0x40,0x0f,0xe1,0x20,0x0e, + 0xec,0x50,0x0e,0x33,0x20,0x28,0x1d,0xdc,0xc1,0x1e,0xc2,0x41,0x1e,0xd2,0x21,0x1c, + 0xdc,0x81,0x1e,0xdc,0xe0,0x1c,0xe4,0xe1,0x1d,0xea,0x01,0x1e,0x66,0x18,0x51,0x38, + 0xb0,0x43,0x3a,0x9c,0x83,0x3b,0xcc,0x50,0x24,0x76,0x60,0x07,0x7b,0x68,0x07,0x37, + 0x60,0x87,0x77,0x78,0x07,0x78,0x98,0x51,0x4c,0xf4,0x90,0x0f,0xf0,0x50,0x0e,0x33, + 0x1e,0x6a,0x1e,0xca,0x61,0x1c,0xe8,0x21,0x1d,0xde,0xc1,0x1d,0x7e,0x01,0x1e,0xe4, + 0xa1,0x1c,0xcc,0x21,0x1d,0xf0,0x61,0x06,0x54,0x85,0x83,0x38,0xcc,0xc3,0x3b,0xb0, + 0x43,0x3d,0xd0,0x43,0x39,0xfc,0xc2,0x3c,0xe4,0x43,0x3b,0x88,0xc3,0x3b,0xb0,0xc3, + 0x8c,0xc5,0x0a,0x87,0x79,0x98,0x87,0x77,0x18,0x87,0x74,0x08,0x07,0x7a,0x28,0x07, + 0x72,0x98,0x81,0x5c,0xe3,0x10,0x0e,0xec,0xc0,0x0e,0xe5,0x50,0x0e,0xf3,0x30,0x23, + 0xc1,0xd2,0x41,0x1e,0xe4,0xe1,0x17,0xd8,0xe1,0x1d,0xde,0x01,0x1e,0x66,0x48,0x19, + 0x3b,0xb0,0x83,0x3d,0xb4,0x83,0x1b,0x84,0xc3,0x38,0x8c,0x43,0x39,0xcc,0xc3,0x3c, + 0xb8,0xc1,0x39,0xc8,0xc3,0x3b,0xd4,0x03,0x3c,0xcc,0x48,0xb4,0x71,0x08,0x07,0x76, + 0x60,0x07,0x71,0x08,0x87,0x71,0x58,0x87,0x19,0xdb,0xc6,0x0e,0xec,0x60,0x0f,0xed, + 0xe0,0x06,0xf0,0x20,0x0f,0xe5,0x30,0x0f,0xe5,0x20,0x0f,0xf6,0x50,0x0e,0x6e,0x10, + 0x0e,0xe3,0x30,0x0e,0xe5,0x30,0x0f,0xf3,0xe0,0x06,0xe9,0xe0,0x0e,0xe4,0x50,0x0e, + 0xf8,0x30,0x23,0xe2,0xec,0x61,0x1c,0xc2,0x81,0x1d,0xd8,0xe1,0x17,0xec,0x21,0x1d, + 0xe6,0x21,0x1d,0xc4,0x21,0x1d,0xd8,0x21,0x1d,0xe8,0x21,0x1f,0x66,0x20,0x9d,0x3b, + 0xbc,0x43,0x3d,0xb8,0x03,0x39,0x94,0x83,0x39,0xcc,0x58,0xbc,0x70,0x70,0x07,0x77, + 0x78,0x07,0x7a,0x08,0x07,0x7a,0x48,0x87,0x77,0x70,0x87,0x19,0xce,0x87,0x0e,0xe5, + 0x10,0x0e,0xf0,0x10,0x0e,0xec,0xc0,0x0e,0xef,0x30,0x0e,0xf3,0x90,0x0e,0xf4,0x50, + 0x0e,0x33,0x28,0x30,0x08,0x87,0x74,0x90,0x07,0x37,0x30,0x87,0x7a,0x70,0x87,0x71, + 0xa0,0x87,0x74,0x78,0x07,0x77,0xf8,0x85,0x73,0x90,0x87,0x77,0xa8,0x07,0x78,0x98, + 0x07,0x00,0x00,0x00,0x00,0x71,0x20,0x00,0x00,0x02,0x00,0x00,0x00,0x06,0x50,0x30, + 0x00,0xd2,0xd0,0x00,0x00,0x61,0x20,0x00,0x00,0x42,0x00,0x00,0x00,0x13,0x04,0x41, + 0x2c,0x10,0x00,0x00,0x00,0x09,0x00,0x00,0x00,0xf4,0xc6,0x22,0x86,0x61,0x18,0xc6, + 0x22,0x04,0x41,0x10,0xc6,0x22,0x82,0x20,0x08,0x46,0x00,0xa8,0x95,0x40,0x19,0x14, + 0x01,0x8d,0x19,0x00,0x12,0x33,0x00,0x14,0x66,0x00,0x66,0x00,0x00,0xe3,0x15,0x8c, + 0xa4,0x69,0x12,0x05,0x65,0x90,0x41,0x22,0x14,0x13,0x02,0xf9,0x8c,0x57,0x40,0x96, + 0xe7,0x2d,0x14,0x94,0x41,0x06,0xeb,0x78,0x4c,0x08,0xe4,0x63,0x41,0x01,0x9f,0xf1, + 0x8a,0x6a,0x1b,0x83,0x31,0x70,0x28,0x28,0x83,0x0c,0x1b,0x53,0x99,0x10,0xc8,0xc7, + 0x8a,0x00,0x3e,0xe3,0x15,0x1a,0x18,0xa0,0x01,0x1a,0x50,0x14,0x94,0x41,0x06,0x30, + 0x88,0x36,0x13,0x02,0xf9,0x58,0x11,0xc0,0x67,0xbc,0xe2,0x2b,0x03,0x37,0x68,0x83, + 0x32,0xa0,0xa0,0x0c,0x32,0x90,0x41,0xd6,0x99,0x10,0xc8,0x67,0xbc,0x62,0x0c,0xd2, + 0x40,0x0e,0xe2,0xc0,0xa3,0xa0,0x0c,0x32,0xa0,0x41,0x27,0x06,0x26,0x04,0xf2,0xb1, + 0xa0,0x80,0xcf,0x78,0x05,0x1a,0xb8,0xc1,0x1d,0xd8,0x81,0x18,0x50,0x50,0x6c,0x08, + 0xe0,0x33,0xdb,0x20,0x06,0x01,0x30,0xdb,0x10,0xb8,0x41,0x30,0xdb,0x10,0x3c,0xc2, + 0x6c,0x43,0xf0,0x06,0x43,0x06,0x01,0x31,0x00,0x0d,0x00,0x00,0x00,0x5b,0x8a,0x20, + 0x00,0x85,0xa3,0x14,0xb6,0x14,0x45,0x00,0x0a,0x47,0x29,0x6c,0x29,0x94,0x00,0x14, + 0x8e,0x52,0xd8,0x52,0x3c,0x01,0x28,0x1c,0xa5,0xb0,0xa5,0xa0,0x02,0x50,0x38,0x4a, + 0x61,0x4b,0x81,0x05,0xa0,0x70,0x94,0xc2,0x96,0xa2,0x0b,0x40,0xe1,0x28,0x05,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00, +}; +/* + #include + #include + + using namespace metal; + + struct main0_out + { + float4 frag_color [[color(0)]]; + }; + + struct main0_in + { + float4 uv [[user(locn0)]]; + float4 color [[user(locn1)]]; + }; + + fragment main0_out main0(main0_in in [[stage_in]], texture2d tex [[texture(0)]], sampler smp [[sampler(0)]]) + { + main0_out out = {}; + out.frag_color = float4(1.0, 1.0, 1.0, tex.sample(smp, in.uv.xy).x) * in.color; + return out; + } +*/ +static const uint8_t _sfons_fs_bytecode_metal_ios[3049] = { + 0x4d,0x54,0x4c,0x42,0x01,0x00,0x02,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xe9,0x0b,0x00,0x00,0x00,0x00,0x00,0x00,0x58,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x6d,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc9,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xd1,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xd9,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x10,0x0b,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x6d,0x00,0x00,0x00, + 0x4e,0x41,0x4d,0x45,0x06,0x00,0x6d,0x61,0x69,0x6e,0x30,0x00,0x54,0x59,0x50,0x45, + 0x01,0x00,0x01,0x48,0x41,0x53,0x48,0x20,0x00,0x1c,0x34,0xc1,0x68,0x6e,0xbc,0x1a, + 0x30,0x92,0x74,0x28,0x57,0x7d,0x71,0xda,0x98,0x7b,0x20,0xf9,0x6e,0x40,0x73,0x89, + 0x90,0x99,0xa5,0x20,0xbe,0x88,0x63,0xc5,0x97,0x4f,0x46,0x46,0x54,0x18,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x56,0x45,0x52,0x53,0x08,0x00,0x01,0x00,0x08, + 0x00,0x01,0x00,0x01,0x00,0x45,0x4e,0x44,0x54,0x04,0x00,0x00,0x00,0x45,0x4e,0x44, + 0x54,0x04,0x00,0x00,0x00,0x45,0x4e,0x44,0x54,0xde,0xc0,0x17,0x0b,0x00,0x00,0x00, + 0x00,0x14,0x00,0x00,0x00,0xf4,0x0a,0x00,0x00,0xff,0xff,0xff,0xff,0x42,0x43,0xc0, + 0xde,0x21,0x0c,0x00,0x00,0xba,0x02,0x00,0x00,0x0b,0x82,0x20,0x00,0x02,0x00,0x00, + 0x00,0x12,0x00,0x00,0x00,0x07,0x81,0x23,0x91,0x41,0xc8,0x04,0x49,0x06,0x10,0x32, + 0x39,0x92,0x01,0x84,0x0c,0x25,0x05,0x08,0x19,0x1e,0x04,0x8b,0x62,0x80,0x14,0x45, + 0x02,0x42,0x92,0x0b,0x42,0xa4,0x10,0x32,0x14,0x38,0x08,0x18,0x49,0x0a,0x32,0x44, + 0x24,0x48,0x0a,0x90,0x21,0x23,0xc4,0x52,0x80,0x0c,0x19,0x21,0x72,0x24,0x07,0xc8, + 0x48,0x11,0x62,0xa8,0xa0,0xa8,0x40,0xc6,0xf0,0x01,0x00,0x00,0x00,0x51,0x18,0x00, + 0x00,0x74,0x00,0x00,0x00,0x1b,0xc2,0x24,0xf8,0xff,0xff,0xff,0xff,0x01,0x60,0x00, + 0x09,0xa8,0x88,0x70,0x80,0x07,0x78,0x90,0x87,0x77,0xc0,0x87,0x36,0x30,0x87,0x7a, + 0x70,0x87,0x71,0x68,0x03,0x73,0x80,0x87,0x36,0x68,0x87,0x70,0xa0,0x07,0x74,0x00, + 0xe8,0x41,0x1e,0xea,0xa1,0x1c,0x00,0xa2,0x1d,0xd2,0xc1,0x1d,0xda,0x80,0x1d,0xca, + 0xe1,0x1c,0xc2,0x81,0x1d,0xda,0xc0,0x1e,0xca,0x61,0x1c,0xe8,0xe1,0x1d,0xe4,0xa1, + 0x0d,0xee,0x21,0x1d,0xc8,0x81,0x1e,0xd0,0x01,0x88,0x03,0x39,0xc0,0x03,0x60,0x70, + 0x87,0x77,0x68,0x03,0x71,0xa8,0x87,0x74,0x60,0x07,0x7a,0x48,0x07,0x77,0x98,0x07, + 0x80,0x70,0x87,0x77,0x68,0x03,0x73,0x90,0x87,0x70,0x68,0x87,0x72,0x68,0x03,0x78, + 0x78,0x87,0x74,0x70,0x07,0x7a,0x28,0x07,0x79,0x68,0x83,0x72,0x60,0x87,0x74,0x68, + 0x07,0x80,0x1e,0xe4,0xa1,0x1e,0xca,0x01,0x20,0xdc,0xe1,0x1d,0xda,0xc0,0x1c,0xe4, + 0x21,0x1c,0xda,0xa1,0x1c,0xda,0x00,0x1e,0xde,0x21,0x1d,0xdc,0x81,0x1e,0xca,0x41, + 0x1e,0xda,0xa0,0x1c,0xd8,0x21,0x1d,0xda,0xa1,0x0d,0xdc,0xe1,0x1d,0xdc,0xa1,0x0d, + 0xd8,0xa1,0x1c,0xc2,0xc1,0x1c,0x00,0x7a,0x90,0x87,0x7a,0x28,0x07,0x80,0x70,0x87, + 0x77,0x68,0x83,0x74,0x70,0x07,0x73,0x98,0x87,0x36,0x30,0x07,0x78,0x68,0x83,0x76, + 0x08,0x07,0x7a,0x40,0x07,0x80,0x1e,0xe4,0xa1,0x1e,0xca,0x01,0x20,0xdc,0xe1,0x1d, + 0xda,0xc0,0x1d,0xc2,0xc1,0x1d,0xe6,0xa1,0x0d,0xcc,0x01,0x1e,0xda,0xa0,0x1d,0xc2, + 0x81,0x1e,0xd0,0x01,0xa0,0x07,0x79,0xa8,0x87,0x72,0x00,0x08,0x77,0x78,0x87,0x36, + 0x98,0x87,0x74,0x38,0x07,0x77,0x28,0x07,0x72,0x68,0x03,0x7d,0x28,0x07,0x79,0x78, + 0x87,0x79,0x68,0x03,0x73,0x80,0x87,0x36,0x68,0x87,0x70,0xa0,0x07,0x74,0x00,0xe8, + 0x41,0x1e,0xea,0xa1,0x1c,0x00,0xc2,0x1d,0xde,0xa1,0x0d,0xe8,0x41,0x1e,0xc2,0x01, + 0x1e,0xe0,0x21,0x1d,0xdc,0xe1,0x1c,0xda,0xa0,0x1d,0xc2,0x81,0x1e,0xd0,0x01,0xa0, + 0x07,0x79,0xa8,0x87,0x72,0x00,0x88,0x79,0xa0,0x87,0x70,0x18,0x87,0x75,0x68,0x03, + 0x78,0x90,0x87,0x77,0xa0,0x87,0x72,0x18,0x07,0x7a,0x78,0x07,0x79,0x68,0x03,0x71, + 0xa8,0x07,0x73,0x30,0x87,0x72,0x90,0x87,0x36,0x98,0x87,0x74,0xd0,0x87,0x72,0x00, + 0xf0,0x00,0x20,0xea,0xc1,0x1d,0xe6,0x21,0x1c,0xcc,0xa1,0x1c,0xda,0xc0,0x1c,0xe0, + 0xa1,0x0d,0xda,0x21,0x1c,0xe8,0x01,0x1d,0x00,0x7a,0x90,0x87,0x7a,0x28,0x07,0x60, + 0x83,0x21,0x0c,0xc0,0x02,0x54,0x1b,0x8c,0x81,0x00,0x16,0xa0,0xda,0x80,0x10,0xff, + 0xff,0xff,0xff,0x3f,0x00,0x0c,0x20,0x01,0xd5,0x06,0xa3,0x08,0x80,0x05,0xa8,0x36, + 0x18,0x86,0x00,0x2c,0x40,0xb5,0x01,0x39,0xfe,0xff,0xff,0xff,0x7f,0x00,0x18,0x40, + 0x02,0x2a,0x00,0x00,0x00,0x49,0x18,0x00,0x00,0x04,0x00,0x00,0x00,0x13,0x86,0x40, + 0x18,0x26,0x0c,0x44,0x61,0x4c,0x18,0x8e,0xc2,0x00,0x00,0x00,0x00,0x89,0x20,0x00, + 0x00,0x1e,0x00,0x00,0x00,0x32,0x22,0x48,0x09,0x20,0x64,0x85,0x04,0x93,0x22,0xa4, + 0x84,0x04,0x93,0x22,0xe3,0x84,0xa1,0x90,0x14,0x12,0x4c,0x8a,0x8c,0x0b,0x84,0xa4, + 0x4c,0x10,0x4c,0x33,0x00,0xc3,0x08,0x04,0x60,0x83,0x70,0x94,0x34,0x45,0x94,0x30, + 0xf9,0xff,0x44,0x5c,0x13,0x15,0x11,0xbf,0x3d,0xfc,0xd3,0x18,0x01,0x30,0x88,0x30, + 0x04,0x17,0x49,0x53,0x44,0x09,0x93,0xff,0x4b,0x00,0xf3,0x2c,0x44,0xf4,0x4f,0x63, + 0x04,0xc0,0x20,0x42,0x21,0x94,0x42,0x84,0x40,0x0c,0x9d,0x61,0x04,0x01,0x98,0x23, + 0x08,0xe6,0x08,0xc0,0x60,0x18,0x41,0x58,0x0a,0x12,0x88,0x49,0x8a,0x29,0x40,0x6d, + 0x20,0x20,0x05,0xd6,0x30,0x02,0xb1,0x8c,0x00,0x00,0x00,0x00,0x00,0x13,0xa8,0x70, + 0x48,0x07,0x79,0xb0,0x03,0x3a,0x68,0x83,0x70,0x80,0x07,0x78,0x60,0x87,0x72,0x68, + 0x83,0x74,0x78,0x87,0x79,0xc8,0x03,0x37,0x80,0x03,0x37,0x80,0x83,0x0d,0xb7,0x51, + 0x0e,0x6d,0x00,0x0f,0x7a,0x60,0x07,0x74,0xa0,0x07,0x76,0x40,0x07,0x7a,0x60,0x07, + 0x74,0xd0,0x06,0xe9,0x10,0x07,0x7a,0x80,0x07,0x7a,0x80,0x07,0x6d,0x90,0x0e,0x78, + 0xa0,0x07,0x78,0xa0,0x07,0x78,0xd0,0x06,0xe9,0x10,0x07,0x76,0xa0,0x07,0x71,0x60, + 0x07,0x7a,0x10,0x07,0x76,0xd0,0x06,0xe9,0x30,0x07,0x72,0xa0,0x07,0x73,0x20,0x07, + 0x7a,0x30,0x07,0x72,0xd0,0x06,0xe9,0x60,0x07,0x74,0xa0,0x07,0x76,0x40,0x07,0x7a, + 0x60,0x07,0x74,0xd0,0x06,0xe6,0x30,0x07,0x72,0xa0,0x07,0x73,0x20,0x07,0x7a,0x30, + 0x07,0x72,0xd0,0x06,0xe6,0x60,0x07,0x74,0xa0,0x07,0x76,0x40,0x07,0x7a,0x60,0x07, + 0x74,0xd0,0x06,0xf6,0x10,0x07,0x76,0xa0,0x07,0x71,0x60,0x07,0x7a,0x10,0x07,0x76, + 0xd0,0x06,0xf6,0x20,0x07,0x74,0xa0,0x07,0x73,0x20,0x07,0x7a,0x30,0x07,0x72,0xd0, + 0x06,0xf6,0x30,0x07,0x72,0xa0,0x07,0x73,0x20,0x07,0x7a,0x30,0x07,0x72,0xd0,0x06, + 0xf6,0x40,0x07,0x78,0xa0,0x07,0x76,0x40,0x07,0x7a,0x60,0x07,0x74,0xd0,0x06,0xf6, + 0x60,0x07,0x74,0xa0,0x07,0x76,0x40,0x07,0x7a,0x60,0x07,0x74,0xd0,0x06,0xf6,0x90, + 0x07,0x76,0xa0,0x07,0x71,0x20,0x07,0x78,0xa0,0x07,0x71,0x20,0x07,0x78,0xd0,0x06, + 0xf6,0x10,0x07,0x72,0x80,0x07,0x7a,0x10,0x07,0x72,0x80,0x07,0x7a,0x10,0x07,0x72, + 0x80,0x07,0x6d,0x60,0x0f,0x71,0x90,0x07,0x72,0xa0,0x07,0x72,0x50,0x07,0x76,0xa0, + 0x07,0x72,0x50,0x07,0x76,0xd0,0x06,0xf6,0x20,0x07,0x75,0x60,0x07,0x7a,0x20,0x07, + 0x75,0x60,0x07,0x7a,0x20,0x07,0x75,0x60,0x07,0x6d,0x60,0x0f,0x75,0x10,0x07,0x72, + 0xa0,0x07,0x75,0x10,0x07,0x72,0xa0,0x07,0x75,0x10,0x07,0x72,0xd0,0x06,0xf6,0x10, + 0x07,0x70,0x20,0x07,0x74,0xa0,0x07,0x71,0x00,0x07,0x72,0x40,0x07,0x7a,0x10,0x07, + 0x70,0x20,0x07,0x74,0xd0,0x06,0xee,0x80,0x07,0x7a,0x10,0x07,0x76,0xa0,0x07,0x73, + 0x20,0x07,0x43,0x18,0x04,0x00,0x80,0x00,0x00,0x00,0x00,0x00,0x80,0x21,0x8c,0x03, + 0x04,0x80,0x00,0x00,0x00,0x00,0x00,0x40,0x16,0x08,0x00,0x00,0x00,0x08,0x00,0x00, + 0x00,0x32,0x1e,0x98,0x10,0x19,0x11,0x4c,0x90,0x8c,0x09,0x26,0x47,0xc6,0x04,0x43, + 0x5a,0x25,0x30,0x02,0x50,0x04,0x85,0x50,0x10,0x65,0x40,0x70,0x2c,0x41,0x02,0x00, + 0x00,0x79,0x18,0x00,0x00,0xd0,0x00,0x00,0x00,0x1a,0x03,0x4c,0x10,0x97,0x29,0xa2, + 0x25,0x10,0xab,0x32,0xb9,0xb9,0xb4,0x37,0xb7,0x21,0xc6,0x42,0x3c,0x00,0x84,0x50, + 0xb9,0x1b,0x43,0x0b,0x93,0xfb,0x9a,0x4b,0xd3,0x2b,0x1b,0x62,0x2c,0xc2,0x23,0x2c, + 0x05,0xe3,0x20,0x08,0x0e,0x8e,0xad,0x0c,0xa4,0xad,0x8c,0x2e,0x8c,0x0d,0xc4,0xae, + 0x4c,0x6e,0x2e,0xed,0xcd,0x0d,0x64,0x46,0x06,0x46,0x66,0xc6,0x65,0x66,0xa6,0x06, + 0x04,0xa5,0xad,0x8c,0x2e,0x8c,0xcd,0xac,0xac,0x65,0x46,0x06,0x46,0x66,0xc6,0x65, + 0x66,0xa6,0x26,0x65,0x88,0xf0,0x10,0x43,0x8c,0x45,0x58,0x8c,0x65,0x60,0xd1,0x54, + 0x46,0x17,0xc6,0x36,0x04,0x79,0x8e,0x45,0x58,0x84,0x65,0xe0,0x16,0x96,0x26,0xe7, + 0x32,0xf6,0xd6,0x06,0x97,0xc6,0x56,0xe6,0x42,0x56,0xe6,0xf6,0x26,0xd7,0x36,0xf7, + 0x45,0x96,0x36,0x17,0x26,0xc6,0x56,0x36,0x44,0x78,0x12,0x72,0x61,0x69,0x72,0x2e, + 0x63,0x6f,0x6d,0x70,0x69,0x6c,0x65,0x2e,0x66,0x61,0x73,0x74,0x5f,0x6d,0x61,0x74, + 0x68,0x5f,0x65,0x6e,0x61,0x62,0x6c,0x65,0x43,0x84,0x67,0x21,0x19,0x84,0xa5,0xc9, + 0xb9,0x8c,0xbd,0xb5,0xc1,0xa5,0xb1,0x95,0xb9,0x98,0xc9,0x85,0xb5,0x95,0x89,0xd5, + 0x99,0x99,0x95,0xc9,0x7d,0x99,0x95,0xd1,0x8d,0xa1,0x7d,0x95,0xb9,0x85,0x89,0xb1, + 0x95,0x0d,0x11,0x9e,0x86,0x51,0x58,0x9a,0x9c,0x8b,0x5c,0x99,0x1b,0x59,0x99,0xdc, + 0x17,0x5d,0x98,0xdc,0x59,0x19,0x1d,0xa3,0xb0,0x34,0x39,0x97,0x30,0xb9,0xb3,0x2f, + 0xba,0x3c,0xb8,0xb2,0x2f,0xb7,0xb0,0xb6,0x32,0x1a,0x66,0x6c,0x6f,0x61,0x74,0x34, + 0x64,0xc2,0xd2,0xe4,0x5c,0xc2,0xe4,0xce,0xbe,0xdc,0xc2,0xda,0xca,0xa8,0x98,0xc9, + 0x85,0x9d,0x7d,0x8d,0xbd,0xb1,0xbd,0xc9,0x0d,0x61,0x9e,0x67,0x19,0x1e,0xe8,0x89, + 0x1e,0xe9,0x99,0x86,0x08,0x0f,0x45,0x29,0x2c,0x4d,0xce,0xc5,0x4c,0x2e,0xec,0xac, + 0xad,0xcc,0x8d,0xee,0x2b,0xcd,0x0d,0xae,0x8e,0x8e,0x4b,0xdd,0x5c,0x99,0x1c,0x0a, + 0xdb,0xdb,0x98,0x1b,0x4c,0x0a,0x95,0xb0,0x34,0x39,0x97,0xb1,0x32,0x37,0xba,0x32, + 0x39,0x3e,0x61,0x69,0x72,0x2e,0x70,0x65,0x72,0x73,0x70,0x65,0x63,0x74,0x69,0x76, + 0x65,0x14,0xea,0xec,0x86,0x48,0xcb,0xf0,0x58,0xcf,0xf5,0x60,0x4f,0xf6,0x40,0x4f, + 0xf4,0x48,0x8f,0xc6,0xa5,0x6e,0xae,0x4c,0x0e,0x85,0xed,0x6d,0xcc,0x2d,0x26,0x85, + 0xc5,0xd8,0x1b,0xdb,0x9b,0xdc,0x10,0x69,0x11,0x1e,0xeb,0xe1,0x1e,0xec,0xc9,0x1e, + 0xe8,0x89,0x1e,0xe9,0xe9,0xb8,0x84,0xa5,0xc9,0xb9,0xd0,0x95,0xe1,0xd1,0xd5,0xc9, + 0x95,0x51,0x0a,0x4b,0x93,0x73,0x61,0x7b,0x1b,0x0b,0xa3,0x4b,0x7b,0x73,0xfb,0x4a, + 0x73,0x23,0x2b,0xc3,0xa3,0x12,0x96,0x26,0xe7,0x32,0x17,0xd6,0x06,0xc7,0x56,0x46, + 0x8c,0xae,0x0c,0x8f,0xae,0x4e,0xae,0x4c,0x86,0x8c,0xc7,0x8c,0xed,0x2d,0x8c,0x8e, + 0x05,0x64,0x2e,0xac,0x0d,0x8e,0xad,0xcc,0x87,0x03,0x5d,0x19,0xde,0x10,0x6a,0x21, + 0x9e,0xef,0x01,0x83,0x65,0x58,0x84,0x27,0x0c,0x1e,0xe8,0x11,0x83,0x47,0x7a,0xc6, + 0x80,0x4b,0x58,0x9a,0x9c,0xcb,0x5c,0x58,0x1b,0x1c,0x5b,0x99,0x1c,0x8f,0xb9,0xb0, + 0x36,0x38,0xb6,0x32,0x39,0x0e,0x73,0x6d,0x70,0x43,0xa4,0xe5,0x78,0xca,0xe0,0x01, + 0x83,0x65,0x58,0x84,0x07,0x7a,0xcc,0xe0,0x91,0x9e,0x33,0x18,0x82,0x3c,0xdb,0xe3, + 0x3d,0x64,0xf0,0xa0,0xc1,0x10,0x03,0x01,0x9e,0xea,0x49,0x03,0x5e,0x61,0x69,0x72, + 0x2d,0x61,0x6c,0x69,0x61,0x73,0x2d,0x73,0x63,0x6f,0x70,0x65,0x73,0x28,0x6d,0x61, + 0x69,0x6e,0x30,0x29,0x43,0x88,0x87,0x0d,0x9e,0x35,0x20,0x16,0x96,0x26,0xd7,0x12, + 0xc6,0x96,0x16,0x36,0xd7,0x32,0x37,0xf6,0x06,0x57,0xd6,0x42,0x57,0x86,0x47,0x57, + 0x27,0x57,0x36,0x37,0xc4,0x78,0xdc,0xe0,0x61,0x83,0xa7,0x0d,0x88,0x85,0xa5,0xc9, + 0xb5,0x84,0xb1,0xa5,0x85,0xcd,0xb5,0xcc,0x8d,0xbd,0xc1,0x95,0xb5,0xcc,0x85,0xb5, + 0xc1,0xb1,0x95,0xc9,0xcd,0x0d,0x31,0x1e,0x38,0x78,0xd8,0xe0,0x79,0x83,0x21,0xc4, + 0xe3,0x06,0x0f,0x1c,0x8c,0x88,0xd8,0x81,0x1d,0xec,0xa1,0x1d,0xdc,0xa0,0x1d,0xde, + 0x81,0x1c,0xea,0x81,0x1d,0xca,0xc1,0x0d,0xcc,0x81,0x1d,0xc2,0xe1,0x1c,0xe6,0x61, + 0x8a,0x10,0x0c,0x23,0x14,0x76,0x60,0x07,0x7b,0x68,0x07,0x37,0x48,0x07,0x72,0x28, + 0x07,0x77,0xa0,0x87,0x29,0x41,0x31,0x62,0x09,0x87,0x74,0x90,0x07,0x37,0xb0,0x87, + 0x72,0x90,0x87,0x79,0x48,0x87,0x77,0x70,0x87,0x29,0x81,0x31,0x82,0x0a,0x87,0x74, + 0x90,0x07,0x37,0x60,0x87,0x70,0x70,0x87,0x73,0xa8,0x87,0x70,0x38,0x87,0x72,0xf8, + 0x05,0x7b,0x28,0x07,0x79,0x98,0x87,0x74,0x78,0x07,0x77,0x98,0x12,0x20,0x23,0xa6, + 0x70,0x48,0x07,0x79,0x70,0x83,0x71,0x78,0x87,0x76,0x80,0x87,0x74,0x60,0x87,0x72, + 0xf8,0x85,0x77,0x80,0x07,0x7a,0x48,0x87,0x77,0x70,0x87,0x79,0x98,0x32,0x28,0x8c, + 0x33,0x82,0x09,0x87,0x74,0x90,0x07,0x37,0x30,0x07,0x79,0x08,0x87,0x73,0x68,0x87, + 0x72,0x70,0x07,0x7a,0x98,0x12,0xa8,0x01,0x00,0x79,0x18,0x00,0x00,0xa5,0x00,0x00, + 0x00,0x33,0x08,0x80,0x1c,0xc4,0xe1,0x1c,0x66,0x14,0x01,0x3d,0x88,0x43,0x38,0x84, + 0xc3,0x8c,0x42,0x80,0x07,0x79,0x78,0x07,0x73,0x98,0x71,0x0c,0xe6,0x00,0x0f,0xed, + 0x10,0x0e,0xf4,0x80,0x0e,0x33,0x0c,0x42,0x1e,0xc2,0xc1,0x1d,0xce,0xa1,0x1c,0x66, + 0x30,0x05,0x3d,0x88,0x43,0x38,0x84,0x83,0x1b,0xcc,0x03,0x3d,0xc8,0x43,0x3d,0x8c, + 0x03,0x3d,0xcc,0x78,0x8c,0x74,0x70,0x07,0x7b,0x08,0x07,0x79,0x48,0x87,0x70,0x70, + 0x07,0x7a,0x70,0x03,0x76,0x78,0x87,0x70,0x20,0x87,0x19,0xcc,0x11,0x0e,0xec,0x90, + 0x0e,0xe1,0x30,0x0f,0x6e,0x30,0x0f,0xe3,0xf0,0x0e,0xf0,0x50,0x0e,0x33,0x10,0xc4, + 0x1d,0xde,0x21,0x1c,0xd8,0x21,0x1d,0xc2,0x61,0x1e,0x66,0x30,0x89,0x3b,0xbc,0x83, + 0x3b,0xd0,0x43,0x39,0xb4,0x03,0x3c,0xbc,0x83,0x3c,0x84,0x03,0x3b,0xcc,0xf0,0x14, + 0x76,0x60,0x07,0x7b,0x68,0x07,0x37,0x68,0x87,0x72,0x68,0x07,0x37,0x80,0x87,0x70, + 0x90,0x87,0x70,0x60,0x07,0x76,0x28,0x07,0x76,0xf8,0x05,0x76,0x78,0x87,0x77,0x80, + 0x87,0x5f,0x08,0x87,0x71,0x18,0x87,0x72,0x98,0x87,0x79,0x98,0x81,0x2c,0xee,0xf0, + 0x0e,0xee,0xe0,0x0e,0xf5,0xc0,0x0e,0xec,0x30,0x03,0x62,0xc8,0xa1,0x1c,0xe4,0xa1, + 0x1c,0xcc,0xa1,0x1c,0xe4,0xa1,0x1c,0xdc,0x61,0x1c,0xca,0x21,0x1c,0xc4,0x81,0x1d, + 0xca,0x61,0x06,0xd6,0x90,0x43,0x39,0xc8,0x43,0x39,0x98,0x43,0x39,0xc8,0x43,0x39, + 0xb8,0xc3,0x38,0x94,0x43,0x38,0x88,0x03,0x3b,0x94,0xc3,0x2f,0xbc,0x83,0x3c,0xfc, + 0x82,0x3b,0xd4,0x03,0x3b,0xb0,0xc3,0x0c,0xc7,0x69,0x87,0x70,0x58,0x87,0x72,0x70, + 0x83,0x74,0x68,0x07,0x78,0x60,0x87,0x74,0x18,0x87,0x74,0xa0,0x87,0x19,0xce,0x53, + 0x0f,0xee,0x00,0x0f,0xf2,0x50,0x0e,0xe4,0x90,0x0e,0xe3,0x40,0x0f,0xe1,0x20,0x0e, + 0xec,0x50,0x0e,0x33,0x20,0x28,0x1d,0xdc,0xc1,0x1e,0xc2,0x41,0x1e,0xd2,0x21,0x1c, + 0xdc,0x81,0x1e,0xdc,0xe0,0x1c,0xe4,0xe1,0x1d,0xea,0x01,0x1e,0x66,0x18,0x51,0x38, + 0xb0,0x43,0x3a,0x9c,0x83,0x3b,0xcc,0x50,0x24,0x76,0x60,0x07,0x7b,0x68,0x07,0x37, + 0x60,0x87,0x77,0x78,0x07,0x78,0x98,0x51,0x4c,0xf4,0x90,0x0f,0xf0,0x50,0x0e,0x33, + 0x1e,0x6a,0x1e,0xca,0x61,0x1c,0xe8,0x21,0x1d,0xde,0xc1,0x1d,0x7e,0x01,0x1e,0xe4, + 0xa1,0x1c,0xcc,0x21,0x1d,0xf0,0x61,0x06,0x54,0x85,0x83,0x38,0xcc,0xc3,0x3b,0xb0, + 0x43,0x3d,0xd0,0x43,0x39,0xfc,0xc2,0x3c,0xe4,0x43,0x3b,0x88,0xc3,0x3b,0xb0,0xc3, + 0x8c,0xc5,0x0a,0x87,0x79,0x98,0x87,0x77,0x18,0x87,0x74,0x08,0x07,0x7a,0x28,0x07, + 0x72,0x98,0x81,0x5c,0xe3,0x10,0x0e,0xec,0xc0,0x0e,0xe5,0x50,0x0e,0xf3,0x30,0x23, + 0xc1,0xd2,0x41,0x1e,0xe4,0xe1,0x17,0xd8,0xe1,0x1d,0xde,0x01,0x1e,0x66,0x48,0x19, + 0x3b,0xb0,0x83,0x3d,0xb4,0x83,0x1b,0x84,0xc3,0x38,0x8c,0x43,0x39,0xcc,0xc3,0x3c, + 0xb8,0xc1,0x39,0xc8,0xc3,0x3b,0xd4,0x03,0x3c,0xcc,0x48,0xb4,0x71,0x08,0x07,0x76, + 0x60,0x07,0x71,0x08,0x87,0x71,0x58,0x87,0x19,0xdb,0xc6,0x0e,0xec,0x60,0x0f,0xed, + 0xe0,0x06,0xf0,0x20,0x0f,0xe5,0x30,0x0f,0xe5,0x20,0x0f,0xf6,0x50,0x0e,0x6e,0x10, + 0x0e,0xe3,0x30,0x0e,0xe5,0x30,0x0f,0xf3,0xe0,0x06,0xe9,0xe0,0x0e,0xe4,0x50,0x0e, + 0xf8,0x30,0x23,0xe2,0xec,0x61,0x1c,0xc2,0x81,0x1d,0xd8,0xe1,0x17,0xec,0x21,0x1d, + 0xe6,0x21,0x1d,0xc4,0x21,0x1d,0xd8,0x21,0x1d,0xe8,0x21,0x1f,0x66,0x20,0x9d,0x3b, + 0xbc,0x43,0x3d,0xb8,0x03,0x39,0x94,0x83,0x39,0xcc,0x58,0xbc,0x70,0x70,0x07,0x77, + 0x78,0x07,0x7a,0x08,0x07,0x7a,0x48,0x87,0x77,0x70,0x87,0x19,0xce,0x87,0x0e,0xe5, + 0x10,0x0e,0xf0,0x10,0x0e,0xec,0xc0,0x0e,0xef,0x30,0x0e,0xf3,0x90,0x0e,0xf4,0x50, + 0x0e,0x33,0x28,0x30,0x08,0x87,0x74,0x90,0x07,0x37,0x30,0x87,0x7a,0x70,0x87,0x71, + 0xa0,0x87,0x74,0x78,0x07,0x77,0xf8,0x85,0x73,0x90,0x87,0x77,0xa8,0x07,0x78,0x98, + 0x07,0x00,0x00,0x00,0x00,0x71,0x20,0x00,0x00,0x08,0x00,0x00,0x00,0x16,0xb0,0x01, + 0x48,0xe4,0x4b,0x00,0xf3,0x2c,0xc4,0x3f,0x11,0xd7,0x44,0x45,0xc4,0x6f,0x0f,0x7e, + 0x85,0x17,0xb7,0x6d,0x00,0x05,0x03,0x20,0x0d,0x0d,0x00,0x00,0x00,0x61,0x20,0x00, + 0x00,0x1a,0x00,0x00,0x00,0x13,0x04,0x41,0x2c,0x10,0x00,0x00,0x00,0x0b,0x00,0x00, + 0x00,0x14,0xc7,0x22,0x80,0x40,0x20,0x88,0x8d,0x00,0x8c,0x25,0x00,0x01,0xa9,0x11, + 0x80,0x1a,0x20,0x31,0x03,0x40,0x61,0x0e,0xe2,0xba,0xae,0x6a,0x06,0x80,0xc0,0x0c, + 0xc0,0x08,0xc0,0x18,0x01,0x08,0x82,0x20,0xfe,0x01,0x00,0x00,0x00,0x83,0x0c,0x0f, + 0x91,0x8c,0x18,0x28,0x43,0x80,0x39,0x4d,0x80,0x2c,0xc9,0x30,0xc8,0x70,0x04,0x8d, + 0x05,0x91,0x7c,0x66,0x1b,0x94,0x00,0xc8,0x20,0x20,0x06,0x00,0x00,0x02,0x00,0x00, + 0x00,0x5b,0x86,0xe0,0x88,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +}; +/* + #include + #include + + using namespace metal; + + struct vs_params + { + float4x4 mvp; + float4x4 tm; + }; + + struct main0_out + { + float4 uv [[user(locn0)]]; + float4 color [[user(locn1)]]; + float4 gl_Position [[position]]; + float gl_PointSize [[point_size]]; + }; + + struct main0_in + { + float4 position [[attribute(0)]]; + float2 texcoord0 [[attribute(1)]]; + float4 color0 [[attribute(2)]]; + float psize [[attribute(3)]]; + }; + + vertex main0_out main0(main0_in in [[stage_in]], constant vs_params& _19 [[buffer(0)]]) + { + main0_out out = {}; + out.gl_Position = _19.mvp * in.position; + out.gl_PointSize = in.psize; + out.uv = _19.tm * float4(in.texcoord0, 0.0, 1.0); + out.color = in.color0; + return out; + } +*/ +static const uint8_t _sfons_vs_source_metal_sim[756] = { + 0x23,0x69,0x6e,0x63,0x6c,0x75,0x64,0x65,0x20,0x3c,0x6d,0x65,0x74,0x61,0x6c,0x5f, + 0x73,0x74,0x64,0x6c,0x69,0x62,0x3e,0x0a,0x23,0x69,0x6e,0x63,0x6c,0x75,0x64,0x65, + 0x20,0x3c,0x73,0x69,0x6d,0x64,0x2f,0x73,0x69,0x6d,0x64,0x2e,0x68,0x3e,0x0a,0x0a, + 0x75,0x73,0x69,0x6e,0x67,0x20,0x6e,0x61,0x6d,0x65,0x73,0x70,0x61,0x63,0x65,0x20, + 0x6d,0x65,0x74,0x61,0x6c,0x3b,0x0a,0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x76, + 0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66, + 0x6c,0x6f,0x61,0x74,0x34,0x78,0x34,0x20,0x6d,0x76,0x70,0x3b,0x0a,0x20,0x20,0x20, + 0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x78,0x34,0x20,0x74,0x6d,0x3b,0x0a,0x7d,0x3b, + 0x0a,0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x6d,0x61,0x69,0x6e,0x30,0x5f,0x6f, + 0x75,0x74,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20, + 0x75,0x76,0x20,0x5b,0x5b,0x75,0x73,0x65,0x72,0x28,0x6c,0x6f,0x63,0x6e,0x30,0x29, + 0x5d,0x5d,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20,0x63, + 0x6f,0x6c,0x6f,0x72,0x20,0x5b,0x5b,0x75,0x73,0x65,0x72,0x28,0x6c,0x6f,0x63,0x6e, + 0x31,0x29,0x5d,0x5d,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34, + 0x20,0x67,0x6c,0x5f,0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x20,0x5b,0x5b,0x70, + 0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x5d,0x5d,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66, + 0x6c,0x6f,0x61,0x74,0x20,0x67,0x6c,0x5f,0x50,0x6f,0x69,0x6e,0x74,0x53,0x69,0x7a, + 0x65,0x20,0x5b,0x5b,0x70,0x6f,0x69,0x6e,0x74,0x5f,0x73,0x69,0x7a,0x65,0x5d,0x5d, + 0x3b,0x0a,0x7d,0x3b,0x0a,0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x6d,0x61,0x69, + 0x6e,0x30,0x5f,0x69,0x6e,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61, + 0x74,0x34,0x20,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x20,0x5b,0x5b,0x61,0x74, + 0x74,0x72,0x69,0x62,0x75,0x74,0x65,0x28,0x30,0x29,0x5d,0x5d,0x3b,0x0a,0x20,0x20, + 0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x32,0x20,0x74,0x65,0x78,0x63,0x6f,0x6f,0x72, + 0x64,0x30,0x20,0x5b,0x5b,0x61,0x74,0x74,0x72,0x69,0x62,0x75,0x74,0x65,0x28,0x31, + 0x29,0x5d,0x5d,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20, + 0x63,0x6f,0x6c,0x6f,0x72,0x30,0x20,0x5b,0x5b,0x61,0x74,0x74,0x72,0x69,0x62,0x75, + 0x74,0x65,0x28,0x32,0x29,0x5d,0x5d,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f, + 0x61,0x74,0x20,0x70,0x73,0x69,0x7a,0x65,0x20,0x5b,0x5b,0x61,0x74,0x74,0x72,0x69, + 0x62,0x75,0x74,0x65,0x28,0x33,0x29,0x5d,0x5d,0x3b,0x0a,0x7d,0x3b,0x0a,0x0a,0x76, + 0x65,0x72,0x74,0x65,0x78,0x20,0x6d,0x61,0x69,0x6e,0x30,0x5f,0x6f,0x75,0x74,0x20, + 0x6d,0x61,0x69,0x6e,0x30,0x28,0x6d,0x61,0x69,0x6e,0x30,0x5f,0x69,0x6e,0x20,0x69, + 0x6e,0x20,0x5b,0x5b,0x73,0x74,0x61,0x67,0x65,0x5f,0x69,0x6e,0x5d,0x5d,0x2c,0x20, + 0x63,0x6f,0x6e,0x73,0x74,0x61,0x6e,0x74,0x20,0x76,0x73,0x5f,0x70,0x61,0x72,0x61, + 0x6d,0x73,0x26,0x20,0x5f,0x31,0x39,0x20,0x5b,0x5b,0x62,0x75,0x66,0x66,0x65,0x72, + 0x28,0x30,0x29,0x5d,0x5d,0x29,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x6d,0x61,0x69, + 0x6e,0x30,0x5f,0x6f,0x75,0x74,0x20,0x6f,0x75,0x74,0x20,0x3d,0x20,0x7b,0x7d,0x3b, + 0x0a,0x20,0x20,0x20,0x20,0x6f,0x75,0x74,0x2e,0x67,0x6c,0x5f,0x50,0x6f,0x73,0x69, + 0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x5f,0x31,0x39,0x2e,0x6d,0x76,0x70,0x20,0x2a, + 0x20,0x69,0x6e,0x2e,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x3b,0x0a,0x20,0x20, + 0x20,0x20,0x6f,0x75,0x74,0x2e,0x67,0x6c,0x5f,0x50,0x6f,0x69,0x6e,0x74,0x53,0x69, + 0x7a,0x65,0x20,0x3d,0x20,0x69,0x6e,0x2e,0x70,0x73,0x69,0x7a,0x65,0x3b,0x0a,0x20, + 0x20,0x20,0x20,0x6f,0x75,0x74,0x2e,0x75,0x76,0x20,0x3d,0x20,0x5f,0x31,0x39,0x2e, + 0x74,0x6d,0x20,0x2a,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x28,0x69,0x6e,0x2e,0x74, + 0x65,0x78,0x63,0x6f,0x6f,0x72,0x64,0x30,0x2c,0x20,0x30,0x2e,0x30,0x2c,0x20,0x31, + 0x2e,0x30,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x6f,0x75,0x74,0x2e,0x63,0x6f,0x6c, + 0x6f,0x72,0x20,0x3d,0x20,0x69,0x6e,0x2e,0x63,0x6f,0x6c,0x6f,0x72,0x30,0x3b,0x0a, + 0x20,0x20,0x20,0x20,0x72,0x65,0x74,0x75,0x72,0x6e,0x20,0x6f,0x75,0x74,0x3b,0x0a, + 0x7d,0x0a,0x0a,0x00, +}; +/* + #include + #include + + using namespace metal; + + struct main0_out + { + float4 frag_color [[color(0)]]; + }; + + struct main0_in + { + float4 uv [[user(locn0)]]; + float4 color [[user(locn1)]]; + }; + + fragment main0_out main0(main0_in in [[stage_in]], texture2d tex [[texture(0)]], sampler smp [[sampler(0)]]) + { + main0_out out = {}; + out.frag_color = float4(1.0, 1.0, 1.0, tex.sample(smp, in.uv.xy).x) * in.color; + return out; + } +*/ +static const uint8_t _sfons_fs_source_metal_sim[464] = { + 0x23,0x69,0x6e,0x63,0x6c,0x75,0x64,0x65,0x20,0x3c,0x6d,0x65,0x74,0x61,0x6c,0x5f, + 0x73,0x74,0x64,0x6c,0x69,0x62,0x3e,0x0a,0x23,0x69,0x6e,0x63,0x6c,0x75,0x64,0x65, + 0x20,0x3c,0x73,0x69,0x6d,0x64,0x2f,0x73,0x69,0x6d,0x64,0x2e,0x68,0x3e,0x0a,0x0a, + 0x75,0x73,0x69,0x6e,0x67,0x20,0x6e,0x61,0x6d,0x65,0x73,0x70,0x61,0x63,0x65,0x20, + 0x6d,0x65,0x74,0x61,0x6c,0x3b,0x0a,0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x6d, + 0x61,0x69,0x6e,0x30,0x5f,0x6f,0x75,0x74,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66, + 0x6c,0x6f,0x61,0x74,0x34,0x20,0x66,0x72,0x61,0x67,0x5f,0x63,0x6f,0x6c,0x6f,0x72, + 0x20,0x5b,0x5b,0x63,0x6f,0x6c,0x6f,0x72,0x28,0x30,0x29,0x5d,0x5d,0x3b,0x0a,0x7d, + 0x3b,0x0a,0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x6d,0x61,0x69,0x6e,0x30,0x5f, + 0x69,0x6e,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20, + 0x75,0x76,0x20,0x5b,0x5b,0x75,0x73,0x65,0x72,0x28,0x6c,0x6f,0x63,0x6e,0x30,0x29, + 0x5d,0x5d,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20,0x63, + 0x6f,0x6c,0x6f,0x72,0x20,0x5b,0x5b,0x75,0x73,0x65,0x72,0x28,0x6c,0x6f,0x63,0x6e, + 0x31,0x29,0x5d,0x5d,0x3b,0x0a,0x7d,0x3b,0x0a,0x0a,0x66,0x72,0x61,0x67,0x6d,0x65, + 0x6e,0x74,0x20,0x6d,0x61,0x69,0x6e,0x30,0x5f,0x6f,0x75,0x74,0x20,0x6d,0x61,0x69, + 0x6e,0x30,0x28,0x6d,0x61,0x69,0x6e,0x30,0x5f,0x69,0x6e,0x20,0x69,0x6e,0x20,0x5b, + 0x5b,0x73,0x74,0x61,0x67,0x65,0x5f,0x69,0x6e,0x5d,0x5d,0x2c,0x20,0x74,0x65,0x78, + 0x74,0x75,0x72,0x65,0x32,0x64,0x3c,0x66,0x6c,0x6f,0x61,0x74,0x3e,0x20,0x74,0x65, + 0x78,0x20,0x5b,0x5b,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x28,0x30,0x29,0x5d,0x5d, + 0x2c,0x20,0x73,0x61,0x6d,0x70,0x6c,0x65,0x72,0x20,0x73,0x6d,0x70,0x20,0x5b,0x5b, + 0x73,0x61,0x6d,0x70,0x6c,0x65,0x72,0x28,0x30,0x29,0x5d,0x5d,0x29,0x0a,0x7b,0x0a, + 0x20,0x20,0x20,0x20,0x6d,0x61,0x69,0x6e,0x30,0x5f,0x6f,0x75,0x74,0x20,0x6f,0x75, + 0x74,0x20,0x3d,0x20,0x7b,0x7d,0x3b,0x0a,0x20,0x20,0x20,0x20,0x6f,0x75,0x74,0x2e, + 0x66,0x72,0x61,0x67,0x5f,0x63,0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20,0x66,0x6c,0x6f, + 0x61,0x74,0x34,0x28,0x31,0x2e,0x30,0x2c,0x20,0x31,0x2e,0x30,0x2c,0x20,0x31,0x2e, + 0x30,0x2c,0x20,0x74,0x65,0x78,0x2e,0x73,0x61,0x6d,0x70,0x6c,0x65,0x28,0x73,0x6d, + 0x70,0x2c,0x20,0x69,0x6e,0x2e,0x75,0x76,0x2e,0x78,0x79,0x29,0x2e,0x78,0x29,0x20, + 0x2a,0x20,0x69,0x6e,0x2e,0x63,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x20,0x20,0x20,0x20, + 0x72,0x65,0x74,0x75,0x72,0x6e,0x20,0x6f,0x75,0x74,0x3b,0x0a,0x7d,0x0a,0x0a,0x00, +}; +#elif defined(SOKOL_D3D11) +/* + cbuffer vs_params : register(b0) + { + row_major float4x4 _19_mvp : packoffset(c0); + row_major float4x4 _19_tm : packoffset(c4); + }; + + + static float4 gl_Position; + static float gl_PointSize; + static float4 position; + static float psize; + static float4 uv; + static float2 texcoord0; + static float4 color; + static float4 color0; + + struct SPIRV_Cross_Input + { + float4 position : TEXCOORD0; + float2 texcoord0 : TEXCOORD1; + float4 color0 : TEXCOORD2; + float psize : TEXCOORD3; + }; + + struct SPIRV_Cross_Output + { + float4 uv : TEXCOORD0; + float4 color : TEXCOORD1; + float4 gl_Position : SV_Position; + }; + + void vert_main() + { + gl_Position = mul(position, _19_mvp); + gl_PointSize = psize; + uv = mul(float4(texcoord0, 0.0f, 1.0f), _19_tm); + color = color0; + } + + SPIRV_Cross_Output main(SPIRV_Cross_Input stage_input) + { + position = stage_input.position; + psize = stage_input.psize; + texcoord0 = stage_input.texcoord0; + color0 = stage_input.color0; + vert_main(); + SPIRV_Cross_Output stage_output; + stage_output.gl_Position = gl_Position; + stage_output.uv = uv; + stage_output.color = color; + return stage_output; + } +*/ +static const uint8_t _sfons_vs_bytecode_hlsl4[1032] = { + 0x44,0x58,0x42,0x43,0x74,0x7f,0x01,0xd9,0xf4,0xd5,0xed,0x1d,0x74,0xc1,0x30,0x27, + 0xd8,0xe9,0x9d,0x50,0x01,0x00,0x00,0x00,0x08,0x04,0x00,0x00,0x05,0x00,0x00,0x00, + 0x34,0x00,0x00,0x00,0x14,0x01,0x00,0x00,0x90,0x01,0x00,0x00,0x00,0x02,0x00,0x00, + 0x8c,0x03,0x00,0x00,0x52,0x44,0x45,0x46,0xd8,0x00,0x00,0x00,0x01,0x00,0x00,0x00, + 0x48,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x1c,0x00,0x00,0x00,0x00,0x04,0xfe,0xff, + 0x10,0x81,0x00,0x00,0xaf,0x00,0x00,0x00,0x3c,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d, + 0x73,0x00,0xab,0xab,0x3c,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x60,0x00,0x00,0x00, + 0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x90,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x40,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x98,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0xa8,0x00,0x00,0x00,0x40,0x00,0x00,0x00,0x40,0x00,0x00,0x00, + 0x02,0x00,0x00,0x00,0x98,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x5f,0x31,0x39,0x5f, + 0x6d,0x76,0x70,0x00,0x02,0x00,0x03,0x00,0x04,0x00,0x04,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x5f,0x31,0x39,0x5f,0x74,0x6d,0x00,0x4d,0x69,0x63,0x72,0x6f, + 0x73,0x6f,0x66,0x74,0x20,0x28,0x52,0x29,0x20,0x48,0x4c,0x53,0x4c,0x20,0x53,0x68, + 0x61,0x64,0x65,0x72,0x20,0x43,0x6f,0x6d,0x70,0x69,0x6c,0x65,0x72,0x20,0x31,0x30, + 0x2e,0x31,0x00,0xab,0x49,0x53,0x47,0x4e,0x74,0x00,0x00,0x00,0x04,0x00,0x00,0x00, + 0x08,0x00,0x00,0x00,0x68,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0f,0x0f,0x00,0x00,0x68,0x00,0x00,0x00, + 0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x01,0x00,0x00,0x00, + 0x03,0x03,0x00,0x00,0x68,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x03,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x0f,0x0f,0x00,0x00,0x68,0x00,0x00,0x00, + 0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x03,0x00,0x00,0x00, + 0x01,0x00,0x00,0x00,0x54,0x45,0x58,0x43,0x4f,0x4f,0x52,0x44,0x00,0xab,0xab,0xab, + 0x4f,0x53,0x47,0x4e,0x68,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x08,0x00,0x00,0x00, + 0x50,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x0f,0x00,0x00,0x00,0x50,0x00,0x00,0x00,0x01,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x0f,0x00,0x00,0x00, + 0x59,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x03,0x00,0x00,0x00, + 0x02,0x00,0x00,0x00,0x0f,0x00,0x00,0x00,0x54,0x45,0x58,0x43,0x4f,0x4f,0x52,0x44, + 0x00,0x53,0x56,0x5f,0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x00,0xab,0xab,0xab, + 0x53,0x48,0x44,0x52,0x84,0x01,0x00,0x00,0x40,0x00,0x01,0x00,0x61,0x00,0x00,0x00, + 0x59,0x00,0x00,0x04,0x46,0x8e,0x20,0x00,0x00,0x00,0x00,0x00,0x08,0x00,0x00,0x00, + 0x5f,0x00,0x00,0x03,0xf2,0x10,0x10,0x00,0x00,0x00,0x00,0x00,0x5f,0x00,0x00,0x03, + 0x32,0x10,0x10,0x00,0x01,0x00,0x00,0x00,0x5f,0x00,0x00,0x03,0xf2,0x10,0x10,0x00, + 0x02,0x00,0x00,0x00,0x65,0x00,0x00,0x03,0xf2,0x20,0x10,0x00,0x00,0x00,0x00,0x00, + 0x65,0x00,0x00,0x03,0xf2,0x20,0x10,0x00,0x01,0x00,0x00,0x00,0x67,0x00,0x00,0x04, + 0xf2,0x20,0x10,0x00,0x02,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x68,0x00,0x00,0x02, + 0x01,0x00,0x00,0x00,0x38,0x00,0x00,0x08,0xf2,0x00,0x10,0x00,0x00,0x00,0x00,0x00, + 0x56,0x15,0x10,0x00,0x01,0x00,0x00,0x00,0x46,0x8e,0x20,0x00,0x00,0x00,0x00,0x00, + 0x05,0x00,0x00,0x00,0x32,0x00,0x00,0x0a,0xf2,0x00,0x10,0x00,0x00,0x00,0x00,0x00, + 0x06,0x10,0x10,0x00,0x01,0x00,0x00,0x00,0x46,0x8e,0x20,0x00,0x00,0x00,0x00,0x00, + 0x04,0x00,0x00,0x00,0x46,0x0e,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x08, + 0xf2,0x20,0x10,0x00,0x00,0x00,0x00,0x00,0x46,0x0e,0x10,0x00,0x00,0x00,0x00,0x00, + 0x46,0x8e,0x20,0x00,0x00,0x00,0x00,0x00,0x07,0x00,0x00,0x00,0x36,0x00,0x00,0x05, + 0xf2,0x20,0x10,0x00,0x01,0x00,0x00,0x00,0x46,0x1e,0x10,0x00,0x02,0x00,0x00,0x00, + 0x38,0x00,0x00,0x08,0xf2,0x00,0x10,0x00,0x00,0x00,0x00,0x00,0x56,0x15,0x10,0x00, + 0x00,0x00,0x00,0x00,0x46,0x8e,0x20,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00, + 0x32,0x00,0x00,0x0a,0xf2,0x00,0x10,0x00,0x00,0x00,0x00,0x00,0x06,0x10,0x10,0x00, + 0x00,0x00,0x00,0x00,0x46,0x8e,0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x46,0x0e,0x10,0x00,0x00,0x00,0x00,0x00,0x32,0x00,0x00,0x0a,0xf2,0x00,0x10,0x00, + 0x00,0x00,0x00,0x00,0xa6,0x1a,0x10,0x00,0x00,0x00,0x00,0x00,0x46,0x8e,0x20,0x00, + 0x00,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x46,0x0e,0x10,0x00,0x00,0x00,0x00,0x00, + 0x32,0x00,0x00,0x0a,0xf2,0x20,0x10,0x00,0x02,0x00,0x00,0x00,0xf6,0x1f,0x10,0x00, + 0x00,0x00,0x00,0x00,0x46,0x8e,0x20,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00, + 0x46,0x0e,0x10,0x00,0x00,0x00,0x00,0x00,0x3e,0x00,0x00,0x01,0x53,0x54,0x41,0x54, + 0x74,0x00,0x00,0x00,0x09,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x06,0x00,0x00,0x00,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +}; +/* + Texture2D tex : register(t0); + SamplerState smp : register(s0); + + static float4 frag_color; + static float4 uv; + static float4 color; + + struct SPIRV_Cross_Input + { + float4 uv : TEXCOORD0; + float4 color : TEXCOORD1; + }; + + struct SPIRV_Cross_Output + { + float4 frag_color : SV_Target0; + }; + + void frag_main() + { + frag_color = float4(1.0f, 1.0f, 1.0f, tex.Sample(smp, uv.xy).x) * color; + } + + SPIRV_Cross_Output main(SPIRV_Cross_Input stage_input) + { + uv = stage_input.uv; + color = stage_input.color; + frag_main(); + SPIRV_Cross_Output stage_output; + stage_output.frag_color = frag_color; + return stage_output; + } +*/ +static const uint8_t _sfons_fs_bytecode_hlsl4[628] = { + 0x44,0x58,0x42,0x43,0xb6,0x66,0xf0,0xfc,0x09,0x54,0x2a,0x35,0x84,0x1d,0x27,0xd2, + 0xff,0xb3,0x2e,0xdb,0x01,0x00,0x00,0x00,0x74,0x02,0x00,0x00,0x05,0x00,0x00,0x00, + 0x34,0x00,0x00,0x00,0xc8,0x00,0x00,0x00,0x14,0x01,0x00,0x00,0x48,0x01,0x00,0x00, + 0xf8,0x01,0x00,0x00,0x52,0x44,0x45,0x46,0x8c,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x1c,0x00,0x00,0x00,0x00,0x04,0xff,0xff, + 0x10,0x81,0x00,0x00,0x64,0x00,0x00,0x00,0x5c,0x00,0x00,0x00,0x03,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x60,0x00,0x00,0x00,0x02,0x00,0x00,0x00, + 0x05,0x00,0x00,0x00,0x04,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00, + 0x01,0x00,0x00,0x00,0x0d,0x00,0x00,0x00,0x73,0x6d,0x70,0x00,0x74,0x65,0x78,0x00, + 0x4d,0x69,0x63,0x72,0x6f,0x73,0x6f,0x66,0x74,0x20,0x28,0x52,0x29,0x20,0x48,0x4c, + 0x53,0x4c,0x20,0x53,0x68,0x61,0x64,0x65,0x72,0x20,0x43,0x6f,0x6d,0x70,0x69,0x6c, + 0x65,0x72,0x20,0x31,0x30,0x2e,0x31,0x00,0x49,0x53,0x47,0x4e,0x44,0x00,0x00,0x00, + 0x02,0x00,0x00,0x00,0x08,0x00,0x00,0x00,0x38,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0f,0x03,0x00,0x00, + 0x38,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00, + 0x01,0x00,0x00,0x00,0x0f,0x0f,0x00,0x00,0x54,0x45,0x58,0x43,0x4f,0x4f,0x52,0x44, + 0x00,0xab,0xab,0xab,0x4f,0x53,0x47,0x4e,0x2c,0x00,0x00,0x00,0x01,0x00,0x00,0x00, + 0x08,0x00,0x00,0x00,0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0f,0x00,0x00,0x00,0x53,0x56,0x5f,0x54, + 0x61,0x72,0x67,0x65,0x74,0x00,0xab,0xab,0x53,0x48,0x44,0x52,0xa8,0x00,0x00,0x00, + 0x40,0x00,0x00,0x00,0x2a,0x00,0x00,0x00,0x5a,0x00,0x00,0x03,0x00,0x60,0x10,0x00, + 0x00,0x00,0x00,0x00,0x58,0x18,0x00,0x04,0x00,0x70,0x10,0x00,0x00,0x00,0x00,0x00, + 0x55,0x55,0x00,0x00,0x62,0x10,0x00,0x03,0x32,0x10,0x10,0x00,0x00,0x00,0x00,0x00, + 0x62,0x10,0x00,0x03,0xf2,0x10,0x10,0x00,0x01,0x00,0x00,0x00,0x65,0x00,0x00,0x03, + 0xf2,0x20,0x10,0x00,0x00,0x00,0x00,0x00,0x68,0x00,0x00,0x02,0x01,0x00,0x00,0x00, + 0x45,0x00,0x00,0x09,0xf2,0x00,0x10,0x00,0x00,0x00,0x00,0x00,0x46,0x10,0x10,0x00, + 0x00,0x00,0x00,0x00,0x96,0x73,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x60,0x10,0x00, + 0x00,0x00,0x00,0x00,0x36,0x00,0x00,0x05,0x12,0x00,0x10,0x00,0x00,0x00,0x00,0x00, + 0x01,0x40,0x00,0x00,0x00,0x00,0x80,0x3f,0x38,0x00,0x00,0x07,0xf2,0x20,0x10,0x00, + 0x00,0x00,0x00,0x00,0x06,0x0c,0x10,0x00,0x00,0x00,0x00,0x00,0x46,0x1e,0x10,0x00, + 0x01,0x00,0x00,0x00,0x3e,0x00,0x00,0x01,0x53,0x54,0x41,0x54,0x74,0x00,0x00,0x00, + 0x04,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00, + 0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00, +}; +#elif defined(SOKOL_WGPU) +/* + diagnostic(off, derivative_uniformity); + + struct vs_params { + /_ @offset(0) _/ + mvp : mat4x4f, + /_ @offset(64) _/ + tm : mat4x4f, + } + + @group(0) @binding(0) var x_19 : vs_params; + + var position_1 : vec4f; + + var uv : vec4f; + + var texcoord0 : vec2f; + + var color : vec4f; + + var color0 : vec4f; + + var psize : f32; + + var gl_Position : vec4f; + + fn main_1() { + let x_22 : mat4x4f = x_19.mvp; + let x_25 : vec4f = position_1; + gl_Position = (x_22 * x_25); + let x_32 : mat4x4f = x_19.tm; + let x_36 : vec2f = texcoord0; + uv = (x_32 * vec4f(x_36.x, x_36.y, 0.0f, 1.0f)); + let x_45 : vec4f = color0; + color = x_45; + return; + } + + struct main_out { + @builtin(position) + gl_Position : vec4f, + @location(0) + uv_1 : vec4f, + @location(1) + color_1 : vec4f, + } + + @vertex + fn main(@location(0) position_1_param : vec4f, @location(1) texcoord0_param : vec2f, @location(2) color0_param : vec4f, @location(3) psize_param : f32) -> main_out { + position_1 = position_1_param; + texcoord0 = texcoord0_param; + color0 = color0_param; + psize = psize_param; + main_1(); + return main_out(gl_Position, uv, color); + } +*/ +static const uint8_t _sfons_vs_source_wgsl[1162] = { + 0x64,0x69,0x61,0x67,0x6e,0x6f,0x73,0x74,0x69,0x63,0x28,0x6f,0x66,0x66,0x2c,0x20, + 0x64,0x65,0x72,0x69,0x76,0x61,0x74,0x69,0x76,0x65,0x5f,0x75,0x6e,0x69,0x66,0x6f, + 0x72,0x6d,0x69,0x74,0x79,0x29,0x3b,0x0a,0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20, + 0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x20,0x7b,0x0a,0x20,0x20,0x2f,0x2a, + 0x20,0x40,0x6f,0x66,0x66,0x73,0x65,0x74,0x28,0x30,0x29,0x20,0x2a,0x2f,0x0a,0x20, + 0x20,0x6d,0x76,0x70,0x20,0x3a,0x20,0x6d,0x61,0x74,0x34,0x78,0x34,0x66,0x2c,0x0a, + 0x20,0x20,0x2f,0x2a,0x20,0x40,0x6f,0x66,0x66,0x73,0x65,0x74,0x28,0x36,0x34,0x29, + 0x20,0x2a,0x2f,0x0a,0x20,0x20,0x74,0x6d,0x20,0x3a,0x20,0x6d,0x61,0x74,0x34,0x78, + 0x34,0x66,0x2c,0x0a,0x7d,0x0a,0x0a,0x40,0x67,0x72,0x6f,0x75,0x70,0x28,0x30,0x29, + 0x20,0x40,0x62,0x69,0x6e,0x64,0x69,0x6e,0x67,0x28,0x30,0x29,0x20,0x76,0x61,0x72, + 0x3c,0x75,0x6e,0x69,0x66,0x6f,0x72,0x6d,0x3e,0x20,0x78,0x5f,0x31,0x39,0x20,0x3a, + 0x20,0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x3b,0x0a,0x0a,0x76,0x61,0x72, + 0x3c,0x70,0x72,0x69,0x76,0x61,0x74,0x65,0x3e,0x20,0x70,0x6f,0x73,0x69,0x74,0x69, + 0x6f,0x6e,0x5f,0x31,0x20,0x3a,0x20,0x76,0x65,0x63,0x34,0x66,0x3b,0x0a,0x0a,0x76, + 0x61,0x72,0x3c,0x70,0x72,0x69,0x76,0x61,0x74,0x65,0x3e,0x20,0x75,0x76,0x20,0x3a, + 0x20,0x76,0x65,0x63,0x34,0x66,0x3b,0x0a,0x0a,0x76,0x61,0x72,0x3c,0x70,0x72,0x69, + 0x76,0x61,0x74,0x65,0x3e,0x20,0x74,0x65,0x78,0x63,0x6f,0x6f,0x72,0x64,0x30,0x20, + 0x3a,0x20,0x76,0x65,0x63,0x32,0x66,0x3b,0x0a,0x0a,0x76,0x61,0x72,0x3c,0x70,0x72, + 0x69,0x76,0x61,0x74,0x65,0x3e,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x20,0x3a,0x20,0x76, + 0x65,0x63,0x34,0x66,0x3b,0x0a,0x0a,0x76,0x61,0x72,0x3c,0x70,0x72,0x69,0x76,0x61, + 0x74,0x65,0x3e,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x30,0x20,0x3a,0x20,0x76,0x65,0x63, + 0x34,0x66,0x3b,0x0a,0x0a,0x76,0x61,0x72,0x3c,0x70,0x72,0x69,0x76,0x61,0x74,0x65, + 0x3e,0x20,0x70,0x73,0x69,0x7a,0x65,0x20,0x3a,0x20,0x66,0x33,0x32,0x3b,0x0a,0x0a, + 0x76,0x61,0x72,0x3c,0x70,0x72,0x69,0x76,0x61,0x74,0x65,0x3e,0x20,0x67,0x6c,0x5f, + 0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x20,0x3a,0x20,0x76,0x65,0x63,0x34,0x66, + 0x3b,0x0a,0x0a,0x66,0x6e,0x20,0x6d,0x61,0x69,0x6e,0x5f,0x31,0x28,0x29,0x20,0x7b, + 0x0a,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x32,0x32,0x20,0x3a,0x20,0x6d,0x61, + 0x74,0x34,0x78,0x34,0x66,0x20,0x3d,0x20,0x78,0x5f,0x31,0x39,0x2e,0x6d,0x76,0x70, + 0x3b,0x0a,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x32,0x35,0x20,0x3a,0x20,0x76, + 0x65,0x63,0x34,0x66,0x20,0x3d,0x20,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x5f, + 0x31,0x3b,0x0a,0x20,0x20,0x67,0x6c,0x5f,0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e, + 0x20,0x3d,0x20,0x28,0x78,0x5f,0x32,0x32,0x20,0x2a,0x20,0x78,0x5f,0x32,0x35,0x29, + 0x3b,0x0a,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x33,0x32,0x20,0x3a,0x20,0x6d, + 0x61,0x74,0x34,0x78,0x34,0x66,0x20,0x3d,0x20,0x78,0x5f,0x31,0x39,0x2e,0x74,0x6d, + 0x3b,0x0a,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x33,0x36,0x20,0x3a,0x20,0x76, + 0x65,0x63,0x32,0x66,0x20,0x3d,0x20,0x74,0x65,0x78,0x63,0x6f,0x6f,0x72,0x64,0x30, + 0x3b,0x0a,0x20,0x20,0x75,0x76,0x20,0x3d,0x20,0x28,0x78,0x5f,0x33,0x32,0x20,0x2a, + 0x20,0x76,0x65,0x63,0x34,0x66,0x28,0x78,0x5f,0x33,0x36,0x2e,0x78,0x2c,0x20,0x78, + 0x5f,0x33,0x36,0x2e,0x79,0x2c,0x20,0x30,0x2e,0x30,0x66,0x2c,0x20,0x31,0x2e,0x30, + 0x66,0x29,0x29,0x3b,0x0a,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x34,0x35,0x20, + 0x3a,0x20,0x76,0x65,0x63,0x34,0x66,0x20,0x3d,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x30, + 0x3b,0x0a,0x20,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20,0x78,0x5f,0x34,0x35, + 0x3b,0x0a,0x20,0x20,0x72,0x65,0x74,0x75,0x72,0x6e,0x3b,0x0a,0x7d,0x0a,0x0a,0x73, + 0x74,0x72,0x75,0x63,0x74,0x20,0x6d,0x61,0x69,0x6e,0x5f,0x6f,0x75,0x74,0x20,0x7b, + 0x0a,0x20,0x20,0x40,0x62,0x75,0x69,0x6c,0x74,0x69,0x6e,0x28,0x70,0x6f,0x73,0x69, + 0x74,0x69,0x6f,0x6e,0x29,0x0a,0x20,0x20,0x67,0x6c,0x5f,0x50,0x6f,0x73,0x69,0x74, + 0x69,0x6f,0x6e,0x20,0x3a,0x20,0x76,0x65,0x63,0x34,0x66,0x2c,0x0a,0x20,0x20,0x40, + 0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x28,0x30,0x29,0x0a,0x20,0x20,0x75,0x76, + 0x5f,0x31,0x20,0x3a,0x20,0x76,0x65,0x63,0x34,0x66,0x2c,0x0a,0x20,0x20,0x40,0x6c, + 0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x28,0x31,0x29,0x0a,0x20,0x20,0x63,0x6f,0x6c, + 0x6f,0x72,0x5f,0x31,0x20,0x3a,0x20,0x76,0x65,0x63,0x34,0x66,0x2c,0x0a,0x7d,0x0a, + 0x0a,0x40,0x76,0x65,0x72,0x74,0x65,0x78,0x0a,0x66,0x6e,0x20,0x6d,0x61,0x69,0x6e, + 0x28,0x40,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x28,0x30,0x29,0x20,0x70,0x6f, + 0x73,0x69,0x74,0x69,0x6f,0x6e,0x5f,0x31,0x5f,0x70,0x61,0x72,0x61,0x6d,0x20,0x3a, + 0x20,0x76,0x65,0x63,0x34,0x66,0x2c,0x20,0x40,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f, + 0x6e,0x28,0x31,0x29,0x20,0x74,0x65,0x78,0x63,0x6f,0x6f,0x72,0x64,0x30,0x5f,0x70, + 0x61,0x72,0x61,0x6d,0x20,0x3a,0x20,0x76,0x65,0x63,0x32,0x66,0x2c,0x20,0x40,0x6c, + 0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x28,0x32,0x29,0x20,0x63,0x6f,0x6c,0x6f,0x72, + 0x30,0x5f,0x70,0x61,0x72,0x61,0x6d,0x20,0x3a,0x20,0x76,0x65,0x63,0x34,0x66,0x2c, + 0x20,0x40,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x28,0x33,0x29,0x20,0x70,0x73, + 0x69,0x7a,0x65,0x5f,0x70,0x61,0x72,0x61,0x6d,0x20,0x3a,0x20,0x66,0x33,0x32,0x29, + 0x20,0x2d,0x3e,0x20,0x6d,0x61,0x69,0x6e,0x5f,0x6f,0x75,0x74,0x20,0x7b,0x0a,0x20, + 0x20,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x5f,0x31,0x20,0x3d,0x20,0x70,0x6f, + 0x73,0x69,0x74,0x69,0x6f,0x6e,0x5f,0x31,0x5f,0x70,0x61,0x72,0x61,0x6d,0x3b,0x0a, + 0x20,0x20,0x74,0x65,0x78,0x63,0x6f,0x6f,0x72,0x64,0x30,0x20,0x3d,0x20,0x74,0x65, + 0x78,0x63,0x6f,0x6f,0x72,0x64,0x30,0x5f,0x70,0x61,0x72,0x61,0x6d,0x3b,0x0a,0x20, + 0x20,0x63,0x6f,0x6c,0x6f,0x72,0x30,0x20,0x3d,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x30, + 0x5f,0x70,0x61,0x72,0x61,0x6d,0x3b,0x0a,0x20,0x20,0x70,0x73,0x69,0x7a,0x65,0x20, + 0x3d,0x20,0x70,0x73,0x69,0x7a,0x65,0x5f,0x70,0x61,0x72,0x61,0x6d,0x3b,0x0a,0x20, + 0x20,0x6d,0x61,0x69,0x6e,0x5f,0x31,0x28,0x29,0x3b,0x0a,0x20,0x20,0x72,0x65,0x74, + 0x75,0x72,0x6e,0x20,0x6d,0x61,0x69,0x6e,0x5f,0x6f,0x75,0x74,0x28,0x67,0x6c,0x5f, + 0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x2c,0x20,0x75,0x76,0x2c,0x20,0x63,0x6f, + 0x6c,0x6f,0x72,0x29,0x3b,0x0a,0x7d,0x0a,0x0a,0x00, +}; +/* + diagnostic(off, derivative_uniformity); + + var frag_color : vec4f; + + @group(1) @binding(64) var tex : texture_2d; + + @group(1) @binding(80) var smp : sampler; + + var uv : vec4f; + + var color : vec4f; + + fn main_1() { + let x_24 : vec4f = uv; + let x_26 : vec4f = textureSample(tex, smp, vec2f(x_24.x, x_24.y)); + let x_32 : vec4f = color; + frag_color = (vec4f(1.0f, 1.0f, 1.0f, x_26.x) * x_32); + return; + } + + struct main_out { + @location(0) + frag_color_1 : vec4f, + } + + @fragment + fn main(@location(0) uv_param : vec4f, @location(1) color_param : vec4f) -> main_out { + uv = uv_param; + color = color_param; + main_1(); + return main_out(frag_color); + } +*/ +static const uint8_t _sfons_fs_source_wgsl[674] = { + 0x64,0x69,0x61,0x67,0x6e,0x6f,0x73,0x74,0x69,0x63,0x28,0x6f,0x66,0x66,0x2c,0x20, + 0x64,0x65,0x72,0x69,0x76,0x61,0x74,0x69,0x76,0x65,0x5f,0x75,0x6e,0x69,0x66,0x6f, + 0x72,0x6d,0x69,0x74,0x79,0x29,0x3b,0x0a,0x0a,0x76,0x61,0x72,0x3c,0x70,0x72,0x69, + 0x76,0x61,0x74,0x65,0x3e,0x20,0x66,0x72,0x61,0x67,0x5f,0x63,0x6f,0x6c,0x6f,0x72, + 0x20,0x3a,0x20,0x76,0x65,0x63,0x34,0x66,0x3b,0x0a,0x0a,0x40,0x67,0x72,0x6f,0x75, + 0x70,0x28,0x31,0x29,0x20,0x40,0x62,0x69,0x6e,0x64,0x69,0x6e,0x67,0x28,0x36,0x34, + 0x29,0x20,0x76,0x61,0x72,0x20,0x74,0x65,0x78,0x20,0x3a,0x20,0x74,0x65,0x78,0x74, + 0x75,0x72,0x65,0x5f,0x32,0x64,0x3c,0x66,0x33,0x32,0x3e,0x3b,0x0a,0x0a,0x40,0x67, + 0x72,0x6f,0x75,0x70,0x28,0x31,0x29,0x20,0x40,0x62,0x69,0x6e,0x64,0x69,0x6e,0x67, + 0x28,0x38,0x30,0x29,0x20,0x76,0x61,0x72,0x20,0x73,0x6d,0x70,0x20,0x3a,0x20,0x73, + 0x61,0x6d,0x70,0x6c,0x65,0x72,0x3b,0x0a,0x0a,0x76,0x61,0x72,0x3c,0x70,0x72,0x69, + 0x76,0x61,0x74,0x65,0x3e,0x20,0x75,0x76,0x20,0x3a,0x20,0x76,0x65,0x63,0x34,0x66, + 0x3b,0x0a,0x0a,0x76,0x61,0x72,0x3c,0x70,0x72,0x69,0x76,0x61,0x74,0x65,0x3e,0x20, + 0x63,0x6f,0x6c,0x6f,0x72,0x20,0x3a,0x20,0x76,0x65,0x63,0x34,0x66,0x3b,0x0a,0x0a, + 0x66,0x6e,0x20,0x6d,0x61,0x69,0x6e,0x5f,0x31,0x28,0x29,0x20,0x7b,0x0a,0x20,0x20, + 0x6c,0x65,0x74,0x20,0x78,0x5f,0x32,0x34,0x20,0x3a,0x20,0x76,0x65,0x63,0x34,0x66, + 0x20,0x3d,0x20,0x75,0x76,0x3b,0x0a,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x32, + 0x36,0x20,0x3a,0x20,0x76,0x65,0x63,0x34,0x66,0x20,0x3d,0x20,0x74,0x65,0x78,0x74, + 0x75,0x72,0x65,0x53,0x61,0x6d,0x70,0x6c,0x65,0x28,0x74,0x65,0x78,0x2c,0x20,0x73, + 0x6d,0x70,0x2c,0x20,0x76,0x65,0x63,0x32,0x66,0x28,0x78,0x5f,0x32,0x34,0x2e,0x78, + 0x2c,0x20,0x78,0x5f,0x32,0x34,0x2e,0x79,0x29,0x29,0x3b,0x0a,0x20,0x20,0x6c,0x65, + 0x74,0x20,0x78,0x5f,0x33,0x32,0x20,0x3a,0x20,0x76,0x65,0x63,0x34,0x66,0x20,0x3d, + 0x20,0x63,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x20,0x20,0x66,0x72,0x61,0x67,0x5f,0x63, + 0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20,0x28,0x76,0x65,0x63,0x34,0x66,0x28,0x31,0x2e, + 0x30,0x66,0x2c,0x20,0x31,0x2e,0x30,0x66,0x2c,0x20,0x31,0x2e,0x30,0x66,0x2c,0x20, + 0x78,0x5f,0x32,0x36,0x2e,0x78,0x29,0x20,0x2a,0x20,0x78,0x5f,0x33,0x32,0x29,0x3b, + 0x0a,0x20,0x20,0x72,0x65,0x74,0x75,0x72,0x6e,0x3b,0x0a,0x7d,0x0a,0x0a,0x73,0x74, + 0x72,0x75,0x63,0x74,0x20,0x6d,0x61,0x69,0x6e,0x5f,0x6f,0x75,0x74,0x20,0x7b,0x0a, + 0x20,0x20,0x40,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x28,0x30,0x29,0x0a,0x20, + 0x20,0x66,0x72,0x61,0x67,0x5f,0x63,0x6f,0x6c,0x6f,0x72,0x5f,0x31,0x20,0x3a,0x20, + 0x76,0x65,0x63,0x34,0x66,0x2c,0x0a,0x7d,0x0a,0x0a,0x40,0x66,0x72,0x61,0x67,0x6d, + 0x65,0x6e,0x74,0x0a,0x66,0x6e,0x20,0x6d,0x61,0x69,0x6e,0x28,0x40,0x6c,0x6f,0x63, + 0x61,0x74,0x69,0x6f,0x6e,0x28,0x30,0x29,0x20,0x75,0x76,0x5f,0x70,0x61,0x72,0x61, + 0x6d,0x20,0x3a,0x20,0x76,0x65,0x63,0x34,0x66,0x2c,0x20,0x40,0x6c,0x6f,0x63,0x61, + 0x74,0x69,0x6f,0x6e,0x28,0x31,0x29,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x5f,0x70,0x61, + 0x72,0x61,0x6d,0x20,0x3a,0x20,0x76,0x65,0x63,0x34,0x66,0x29,0x20,0x2d,0x3e,0x20, + 0x6d,0x61,0x69,0x6e,0x5f,0x6f,0x75,0x74,0x20,0x7b,0x0a,0x20,0x20,0x75,0x76,0x20, + 0x3d,0x20,0x75,0x76,0x5f,0x70,0x61,0x72,0x61,0x6d,0x3b,0x0a,0x20,0x20,0x63,0x6f, + 0x6c,0x6f,0x72,0x20,0x3d,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x5f,0x70,0x61,0x72,0x61, + 0x6d,0x3b,0x0a,0x20,0x20,0x6d,0x61,0x69,0x6e,0x5f,0x31,0x28,0x29,0x3b,0x0a,0x20, + 0x20,0x72,0x65,0x74,0x75,0x72,0x6e,0x20,0x6d,0x61,0x69,0x6e,0x5f,0x6f,0x75,0x74, + 0x28,0x66,0x72,0x61,0x67,0x5f,0x63,0x6f,0x6c,0x6f,0x72,0x29,0x3b,0x0a,0x7d,0x0a, + 0x0a,0x00, +}; +#elif defined(SOKOL_DUMMY_BACKEND) +static const char* _sfons_vs_source_dummy = ""; +static const char* _sfons_fs_source_dummy = ""; +#else +#error "Please define one of SOKOL_GLCORE, SOKOL_GLES3, SOKOL_D3D11, SOKOL_METAL, SOKOL_WGPU or SOKOL_DUMMY_BACKEND!" +#endif + +typedef struct _sfons_t { + sfons_desc_t desc; + sg_shader shd; + sgl_pipeline pip; + sg_image img; + sg_view tex_view; + sg_sampler smp; + int cur_width, cur_height; + bool img_dirty; +} _sfons_t; + +static void _sfons_clear(void* ptr, size_t size) { + SOKOL_ASSERT(ptr && (size > 0)); + memset(ptr, 0, size); +} + +static void* _sfons_malloc(const sfons_allocator_t* allocator, size_t size) { + SOKOL_ASSERT(allocator && (size > 0)); + void* ptr; + if (allocator->alloc_fn) { + ptr = allocator->alloc_fn(size, allocator->user_data); + } else { + ptr = malloc(size); + } + SOKOL_ASSERT(ptr); + return ptr; +} + +static void* _sfons_malloc_clear(const sfons_allocator_t* allocator, size_t size) { + void* ptr = _sfons_malloc(allocator, size); + _sfons_clear(ptr, size); + return ptr; +} + +static void _sfons_free(const sfons_allocator_t* allocator, void* ptr) { + SOKOL_ASSERT(allocator); + if (allocator->free_fn) { + allocator->free_fn(ptr, allocator->user_data); + } else { + free(ptr); + } +} + +static int _sfons_render_create(void* user_ptr, int width, int height) { + SOKOL_ASSERT(user_ptr && (width > 8) && (height > 8)); + _sfons_t* sfons = (_sfons_t*) user_ptr; + + // sokol-gl compatible shader which treats RED channel as alpha + if (sfons->shd.id == SG_INVALID_ID) { + sg_shader_desc shd_desc; + _sfons_clear(&shd_desc, sizeof(shd_desc)); + shd_desc.attrs[0].glsl_name = "position"; + shd_desc.attrs[1].glsl_name = "texcoord0"; + shd_desc.attrs[2].glsl_name = "color0"; + shd_desc.attrs[3].glsl_name = "psize"; + shd_desc.attrs[0].hlsl_sem_name = "TEXCOORD"; + shd_desc.attrs[0].hlsl_sem_index = 0; + shd_desc.attrs[1].hlsl_sem_name = "TEXCOORD"; + shd_desc.attrs[1].hlsl_sem_index = 1; + shd_desc.attrs[2].hlsl_sem_name = "TEXCOORD"; + shd_desc.attrs[2].hlsl_sem_index = 2; + shd_desc.attrs[3].hlsl_sem_name = "TEXCOORD"; + shd_desc.attrs[3].hlsl_sem_index = 3; + shd_desc.uniform_blocks[0].stage = SG_SHADERSTAGE_VERTEX; + shd_desc.uniform_blocks[0].size = 128; + shd_desc.uniform_blocks[0].hlsl_register_b_n = 0; + shd_desc.uniform_blocks[0].msl_buffer_n = 0; + shd_desc.uniform_blocks[0].wgsl_group0_binding_n = 0; + shd_desc.uniform_blocks[0].glsl_uniforms[0].glsl_name = "vs_params"; + shd_desc.uniform_blocks[0].glsl_uniforms[0].type = SG_UNIFORMTYPE_FLOAT4; + shd_desc.uniform_blocks[0].glsl_uniforms[0].array_count = 8; + shd_desc.views[0].texture.stage = SG_SHADERSTAGE_FRAGMENT; + shd_desc.views[0].texture.image_type = SG_IMAGETYPE_2D; + shd_desc.views[0].texture.sample_type = SG_IMAGESAMPLETYPE_FLOAT; + shd_desc.views[0].texture.hlsl_register_t_n = 0; + shd_desc.views[0].texture.msl_texture_n = 0; + shd_desc.views[0].texture.wgsl_group1_binding_n = 64; + shd_desc.samplers[0].stage = SG_SHADERSTAGE_FRAGMENT; + shd_desc.samplers[0].sampler_type = SG_SAMPLERTYPE_FILTERING; + shd_desc.samplers[0].hlsl_register_s_n = 0; + shd_desc.samplers[0].msl_sampler_n = 0; + shd_desc.samplers[0].wgsl_group1_binding_n = 80; + shd_desc.texture_sampler_pairs[0].stage = SG_SHADERSTAGE_FRAGMENT; + shd_desc.texture_sampler_pairs[0].glsl_name = "tex_smp"; + shd_desc.texture_sampler_pairs[0].view_slot = 0; + shd_desc.texture_sampler_pairs[0].sampler_slot = 0; + shd_desc.label = "sokol-fontstash-shader"; + #if defined(SOKOL_GLCORE) + shd_desc.vertex_func.source = (const char*)_sfons_vs_source_glsl410; + shd_desc.fragment_func.source = (const char*)_sfons_fs_source_glsl410; + #elif defined(SOKOL_GLES3) + shd_desc.vertex_func.source = (const char*)_sfons_vs_source_glsl300es; + shd_desc.fragment_func.source = (const char*)_sfons_fs_source_glsl300es; + #elif defined(SOKOL_METAL) + shd_desc.vertex_func.entry = "main0"; + shd_desc.fragment_func.entry = "main0"; + switch (sg_query_backend()) { + case SG_BACKEND_METAL_MACOS: + shd_desc.vertex_func.bytecode = SG_RANGE(_sfons_vs_bytecode_metal_macos); + shd_desc.fragment_func.bytecode = SG_RANGE(_sfons_fs_bytecode_metal_macos); + break; + case SG_BACKEND_METAL_IOS: + shd_desc.vertex_func.bytecode = SG_RANGE(_sfons_vs_bytecode_metal_ios); + shd_desc.fragment_func.bytecode = SG_RANGE(_sfons_fs_bytecode_metal_ios); + break; + default: + shd_desc.vertex_func.source = (const char*)_sfons_vs_source_metal_sim; + shd_desc.fragment_func.source = (const char*)_sfons_fs_source_metal_sim; + break; + } + #elif defined(SOKOL_D3D11) + shd_desc.vertex_func.bytecode = SG_RANGE(_sfons_vs_bytecode_hlsl4); + shd_desc.fragment_func.bytecode = SG_RANGE(_sfons_fs_bytecode_hlsl4); + #elif defined(SOKOL_WGPU) + shd_desc.vertex_func.source = (const char*)_sfons_vs_source_wgsl; + shd_desc.fragment_func.source = (const char*)_sfons_fs_source_wgsl; + #else + shd_desc.vertex_func.source = _sfons_vs_source_dummy; + shd_desc.fragment_func.source = _sfons_fs_source_dummy; + #endif + shd_desc.label = "sfons-shader"; + sfons->shd = sg_make_shader(&shd_desc); + } + + // sokol-gl pipeline object + if (sfons->pip.id == SG_INVALID_ID) { + sg_pipeline_desc pip_desc; + _sfons_clear(&pip_desc, sizeof(pip_desc)); + pip_desc.shader = sfons->shd; + pip_desc.colors[0].blend.enabled = true; + pip_desc.colors[0].blend.src_factor_rgb = SG_BLENDFACTOR_SRC_ALPHA; + pip_desc.colors[0].blend.dst_factor_rgb = SG_BLENDFACTOR_ONE_MINUS_SRC_ALPHA; + pip_desc.label = "fontstash-pipeline"; + sfons->pip = sgl_make_pipeline(&pip_desc); + } + + // a sampler object + if (sfons->smp.id == SG_INVALID_ID) { + sg_sampler_desc smp_desc; + _sfons_clear(&smp_desc, sizeof(smp_desc)); + smp_desc.min_filter = SG_FILTER_LINEAR; + smp_desc.mag_filter = SG_FILTER_LINEAR; + smp_desc.label = "fontstash-sampler"; + sfons->smp = sg_make_sampler(&smp_desc); + } + + // create or re-create font atlas image and texture view + if (sfons->img.id != SG_INVALID_ID) { + sg_destroy_image(sfons->img); + sfons->img.id = SG_INVALID_ID; + } + if (sfons->tex_view.id != SG_INVALID_ID) { + sg_destroy_view(sfons->tex_view); + sfons->tex_view.id = SG_INVALID_ID; + } + SOKOL_ASSERT(sfons->img.id == SG_INVALID_ID); + SOKOL_ASSERT(sfons->tex_view.id == SG_INVALID_ID); + sfons->cur_width = width; + sfons->cur_height = height; + sg_image_desc img_desc; + _sfons_clear(&img_desc, sizeof(img_desc)); + img_desc.width = sfons->cur_width; + img_desc.height = sfons->cur_height; + img_desc.usage.dynamic_update = true; + img_desc.pixel_format = SG_PIXELFORMAT_R8; + img_desc.label = "fontstash-image"; + sfons->img = sg_make_image(&img_desc); + sg_view_desc view_desc; + _sfons_clear(&view_desc, sizeof(view_desc)); + view_desc.texture.image = sfons->img; + view_desc.label = "fontstash-texview"; + sfons->tex_view = sg_make_view(&view_desc); + return 1; +} + +static int _sfons_render_resize(void* user_ptr, int width, int height) { + return _sfons_render_create(user_ptr, width, height); +} + +static void _sfons_render_update(void* user_ptr, int* rect, const unsigned char* data) { + SOKOL_ASSERT(user_ptr && rect && data); + _SOKOL_UNUSED(rect); + _SOKOL_UNUSED(data); + _sfons_t* sfons = (_sfons_t*) user_ptr; + sfons->img_dirty = true; +} + +static void _sfons_render_draw(void* user_ptr, const float* verts, const float* tcoords, const unsigned int* colors, int nverts) { + SOKOL_ASSERT(user_ptr && verts && tcoords && colors && (nverts > 0)); + _sfons_t* sfons = (_sfons_t*) user_ptr; + sgl_enable_texture(); + sgl_texture(sfons->tex_view, sfons->smp); + sgl_push_pipeline(); + sgl_load_pipeline(sfons->pip); + sgl_begin_triangles(); + for (int i = 0; i < nverts; i++) { + sgl_v2f_t2f_c1i(verts[2*i+0], verts[2*i+1], tcoords[2*i+0], tcoords[2*i+1], colors[i]); + } + sgl_end(); + sgl_pop_pipeline(); + sgl_disable_texture(); +} + +static void _sfons_render_delete(void* user_ptr) { + SOKOL_ASSERT(user_ptr); + _sfons_t* sfons = (_sfons_t*) user_ptr; + if (sfons->img.id != SG_INVALID_ID) { + sg_destroy_image(sfons->img); + sfons->img.id = SG_INVALID_ID; + } + if (sfons->tex_view.id != SG_INVALID_ID) { + sg_destroy_view(sfons->tex_view); + sfons->tex_view.id = SG_INVALID_ID; + } + if (sfons->smp.id != SG_INVALID_ID) { + sg_destroy_sampler(sfons->smp); + sfons->smp.id = SG_INVALID_ID; + } + if (sfons->pip.id != SG_INVALID_ID) { + sgl_destroy_pipeline(sfons->pip); + sfons->pip.id = SG_INVALID_ID; + } + if (sfons->shd.id != SG_INVALID_ID) { + sg_destroy_shader(sfons->shd); + sfons->shd.id = SG_INVALID_ID; + } +} + +#define _sfons_def(val, def) (((val) == 0) ? (def) : (val)) + +static sfons_desc_t _sfons_desc_defaults(const sfons_desc_t* desc) { + SOKOL_ASSERT(desc); + sfons_desc_t res = *desc; + res.width = _sfons_def(res.width, 512); + res.height = _sfons_def(res.height, 512); + return res; +} + +SOKOL_API_IMPL FONScontext* sfons_create(const sfons_desc_t* desc) { + SOKOL_ASSERT(desc); + SOKOL_ASSERT((desc->allocator.alloc_fn && desc->allocator.free_fn) || (!desc->allocator.alloc_fn && !desc->allocator.free_fn)); + _sfons_t* sfons = (_sfons_t*) _sfons_malloc_clear(&desc->allocator, sizeof(_sfons_t)); + sfons->desc = _sfons_desc_defaults(desc); + FONSparams params; + _sfons_clear(¶ms, sizeof(params)); + params.width = sfons->desc.width; + params.height = sfons->desc.height; + params.flags = FONS_ZERO_TOPLEFT; + params.renderCreate = _sfons_render_create; + params.renderResize = _sfons_render_resize; + params.renderUpdate = _sfons_render_update; + params.renderDraw = _sfons_render_draw; + params.renderDelete = _sfons_render_delete; + params.userPtr = sfons; + return fonsCreateInternal(¶ms); +} + +SOKOL_API_IMPL void sfons_destroy(FONScontext* ctx) { + SOKOL_ASSERT(ctx); + _sfons_t* sfons = (_sfons_t*) ctx->params.userPtr; + fonsDeleteInternal(ctx); + const sfons_allocator_t allocator = sfons->desc.allocator; + _sfons_free(&allocator, sfons); +} + +SOKOL_API_IMPL void sfons_flush(FONScontext* ctx) { + SOKOL_ASSERT(ctx && ctx->params.userPtr); + _sfons_t* sfons = (_sfons_t*) ctx->params.userPtr; + if (sfons->img_dirty) { + sfons->img_dirty = false; + sg_image_data data; + _sfons_clear(&data, sizeof(data)); + data.mip_levels[0].ptr = ctx->texData; + data.mip_levels[0].size = (size_t) (sfons->cur_width * sfons->cur_height); + sg_update_image(sfons->img, &data); + } +} + +SOKOL_API_IMPL uint32_t sfons_rgba(uint8_t r, uint8_t g, uint8_t b, uint8_t a) { + return ((uint32_t)r) | ((uint32_t)g<<8) | ((uint32_t)b<<16) | ((uint32_t)a<<24); +} + +#endif // SOKOL_FONTSTASH_IMPL diff --git a/third-party/sokol/sokol_gfx.h b/third-party/sokol/sokol_gfx.h new file mode 100644 index 0000000..a6fa985 --- /dev/null +++ b/third-party/sokol/sokol_gfx.h @@ -0,0 +1,22948 @@ +#if defined(SOKOL_IMPL) && !defined(SOKOL_GFX_IMPL) +#define SOKOL_GFX_IMPL +#endif +#ifndef SOKOL_GFX_INCLUDED +/* + sokol_gfx.h -- simple 3D API wrapper + + Project URL: https://github.com/floooh/sokol + + Example code: https://github.com/floooh/sokol-samples + + Do this: + #define SOKOL_IMPL or + #define SOKOL_GFX_IMPL + before you include this file in *one* C or C++ file to create the + implementation. + + In the same place define one of the following to select the rendering + backend: + #define SOKOL_GLCORE + #define SOKOL_GLES3 + #define SOKOL_D3D11 + #define SOKOL_METAL + #define SOKOL_WGPU + #define SOKOL_DUMMY_BACKEND + + I.e. for the desktop GL it should look like this: + + #include ... + #include ... + #define SOKOL_IMPL + #define SOKOL_GLCORE + #include "sokol_gfx.h" + + The dummy backend replaces the platform-specific backend code with empty + stub functions. This is useful for writing tests that need to run on the + command line. + + Optionally provide the following defines with your own implementations: + + SOKOL_ASSERT(c) - your own assert macro (default: assert(c)) + SOKOL_UNREACHABLE() - a guard macro for unreachable code (default: assert(false)) + SOKOL_GFX_API_DECL - public function declaration prefix (default: extern) + SOKOL_API_DECL - same as SOKOL_GFX_API_DECL + SOKOL_API_IMPL - public function implementation prefix (default: -) + SOKOL_TRACE_HOOKS - enable trace hook callbacks (search below for TRACE HOOKS) + SOKOL_EXTERNAL_GL_LOADER - indicates that you're using your own GL loader, in this case + sokol_gfx.h will not include any platform GL headers and disable + the integrated Win32 GL loader + + If sokol_gfx.h is compiled as a DLL, define the following before + including the declaration or implementation: + + SOKOL_DLL + + On Windows, SOKOL_DLL will define SOKOL_GFX_API_DECL as __declspec(dllexport) + or __declspec(dllimport) as needed. + + Optionally define the following to force debug checks and validations + even in release mode: + + SOKOL_DEBUG - by default this is defined if NDEBUG is not defined + + Link with the following system libraries (note that sokol_app.h has + additional linker requirements): + + - on macOS/iOS with Metal: Metal + - on macOS with GL: OpenGL + - on iOS with GL: OpenGLES + - on Linux with EGL: GL or GLESv2 + - on Linux with GLX: GL + - on Android: GLESv3, log, android + - on Windows with the MSVC or Clang toolchains: no action needed, libs are defined in-source via pragma-comment-lib + - on Windows with MINGW/MSYS2 gcc: compile with '-mwin32' so that _WIN32 is defined + - with the D3D11 backend: -ld3d11 + + On macOS and iOS, the implementation must be compiled as Objective-C. + + On Emscripten: + - for WebGL2: add the linker option `-s USE_WEBGL2=1` + - for WebGPU: compile and link with `--use-port=emdawnwebgpu` + (for more exotic situations, read: https://dawn.googlesource.com/dawn/+/refs/heads/main/src/emdawnwebgpu/pkg/README.md) + + sokol_gfx DOES NOT: + =================== + - create a window, swapchain or the 3D-API context/device, you must do this + before sokol_gfx is initialized, and pass any required information + (like 3D device pointers) to the sokol_gfx initialization call + + - present the rendered frame, how this is done exactly usually depends + on how the window and 3D-API context/device was created + + - provide a unified shader language, instead 3D-API-specific shader + source-code or shader-bytecode must be provided (for the "official" + offline shader cross-compiler / code-generator, see here: + https://github.com/floooh/sokol-tools/blob/master/docs/sokol-shdc.md) + + + STEP BY STEP + ============ + --- to initialize sokol_gfx, after creating a window and a 3D-API + context/device, call: + + sg_setup(const sg_desc*) + + Depending on the selected 3D backend, sokol-gfx requires some + information about its runtime environment, like a GPU device pointer, + default swapchain pixel formats and so on. If you are using sokol_app.h + for the window system glue, you can use a helper function provided in + the sokol_glue.h header: + + #include "sokol_gfx.h" + #include "sokol_app.h" + #include "sokol_glue.h" + //... + sg_setup(&(sg_desc){ + .environment = sglue_environment(), + }); + + To get any logging output for errors and from the validation layer, you + need to provide a logging callback. Easiest way is through sokol_log.h: + + #include "sokol_log.h" + //... + sg_setup(&(sg_desc){ + //... + .logger.func = slog_func, + }); + + --- create resource objects (buffers, images, views, samplers, shaders + and pipeline objects) + + sg_buffer sg_make_buffer(const sg_buffer_desc*) + sg_image sg_make_image(const sg_image_desc*) + sg_view sg_make_view(const sg_view_desc*) + sg_sampler sg_make_sampler(const sg_sampler_desc*) + sg_shader sg_make_shader(const sg_shader_desc*) + sg_pipeline sg_make_pipeline(const sg_pipeline_desc*) + + --- start a render- or compute-pass: + + sg_begin_pass(const sg_pass* pass); + + Typically, render passes render into an externally provided swapchain which + presents the rendering result on the display. Such a 'swapchain pass' + is started like this: + + sg_begin_pass(&(sg_pass){ .action = { ... }, .swapchain = sglue_swapchain() }) + + ...where .action is an sg_pass_action struct containing actions to be performed + at the start and end of a render pass (such as clearing the render surfaces to + a specific color), and .swapchain is an sg_swapchain struct with all the required + information to render into the swapchain's surfaces. + + To start an 'offscreen render pass' into sokol-gfx image objects, populate + the sg_pass.attachments nested struct with attachment view objects + (1..4 color-attachment-views for to render into, a depth-stencil-attachment-view + to provide the depth-stencil-buffer, and optionally 1..4 resolve-attachment-views + for an MSAA-resolve operation: + + sg_begin_pass(&(sg_pass){ + .action = { ... }, + .attachments = { + .colors[0] = color_attachment_view, + .resolves[0] = optional_resolve_attachment_view, + .depth_stencil = depth_stencil_attachment_view, + }, + }); + + To start a compute-pass, just set the .compute item to true: + + sg_begin_pass(&(sg_pass){ .compute = true }); + + --- set the pipeline state for the next draw call with: + + sg_apply_pipeline(sg_pipeline pip) + + --- fill an sg_bindings struct with the resource bindings for the next + draw- or dispatch-call (0..N vertex buffers, 0 or 1 index buffer, 0..N views, + 0..N samplers), and call + + sg_apply_bindings(const sg_bindings* bindings) + + ...to update the resource bindings. Note that in a compute pass, no vertex- + or index-buffer bindings can be used, and in render passes, no storage-image bindings + are allowed. Those restrictions will be checked by the sokol-gfx validation layer. + + --- optionally update shader uniform data with: + + sg_apply_uniforms(int ub_slot, const sg_range* data) + + Read the section 'UNIFORM DATA LAYOUT' to learn about the expected memory layout + of the uniform data passed into sg_apply_uniforms(). + + --- kick off a draw call with: + + sg_draw(int base_element, int num_elements, int num_instances) + + The sg_draw() function unifies all the different ways to render primitives + in a single call (indexed vs non-indexed rendering, and instanced vs non-instanced + rendering). In case of indexed rendering, base_element and num_element specify + indices in the currently bound index buffer. In case of non-indexed rendering + base_element and num_elements specify vertices in the currently bound + vertex-buffer(s). To perform instanced rendering, the rendering pipeline + must be setup for instancing (see sg_pipeline_desc below), a separate vertex buffer + containing per-instance data must be bound, and the num_instances parameter + must be > 1. + + Alternatively, call: + + sg_draw_ex(...) + + to provide a base-vertex and/or base-instance which allows to render + from different sections of a vertex buffer without rebinding the + vertex buffer with a different offset. Note that the `sg_draw_ex()` + only has limited portability on OpenGL, check the sg_limits struct + members .draw_base_vertex and .draw_base_instance for runtime support, + those are generally true on non-GL-backends, and on GL the feature + flags are set according to the GL version: + + - on GL base_instance != 0 is only supported since GL 4.2 + - on GLES3.x, base_instance != 0 is not supported + - on GLES3.x, base_vertex is only supported since GLES3.2 + (e.g. not supported on WebGL2) + + --- ...or kick of a dispatch call to invoke a compute shader workload: + + sg_dispatch(int num_groups_x, int num_groups_y, int num_groups_z) + + The dispatch args define the number of 'compute workgroups' processed + by the currently applied compute shader. + + --- finish the current pass with: + + sg_end_pass() + + --- when done with the current frame, call + + sg_commit() + + --- at the end of your program, shutdown sokol_gfx with: + + sg_shutdown() + + --- if you need to destroy resources before sg_shutdown(), call: + + sg_destroy_buffer(sg_buffer buf) + sg_destroy_image(sg_image img) + sg_destroy_sampler(sg_sampler smp) + sg_destroy_shader(sg_shader shd) + sg_destroy_pipeline(sg_pipeline pip) + sg_destroy_view(sg_view view) + + --- to set a new viewport rectangle, call: + + sg_apply_viewport(int x, int y, int width, int height, bool origin_top_left) + + ...or if you want to specify the viewport rectangle with float values: + + sg_apply_viewportf(float x, float y, float width, float height, bool origin_top_left) + + --- to set a new scissor rect, call: + + sg_apply_scissor_rect(int x, int y, int width, int height, bool origin_top_left) + + ...or with float values: + + sg_apply_scissor_rectf(float x, float y, float width, float height, bool origin_top_left) + + Both sg_apply_viewport() and sg_apply_scissor_rect() must be called + inside a rendering pass (e.g. not in a compute pass, or outside a pass) + + Note that sg_begin_pass() will reset both the viewport and scissor + rectangles to cover the entire framebuffer. + + --- to update (overwrite) the content of buffer and image resources, call: + + sg_update_buffer(sg_buffer buf, const sg_range* data) + sg_update_image(sg_image img, const sg_image_data* data) + + Buffers and images to be updated must have been created with + sg_buffer_desc.usage.dynamic_update or .stream_update. + + Only one update per frame is allowed for buffer and image resources when + using the sg_update_*() functions. The rationale is to have a simple + protection from the CPU scribbling over data the GPU is currently + using, or the CPU having to wait for the GPU + + Buffer and image updates can be partial, as long as a rendering + operation only references the valid (updated) data in the + buffer or image. + + --- to append a chunk of data to a buffer resource, call: + + int sg_append_buffer(sg_buffer buf, const sg_range* data) + + The difference to sg_update_buffer() is that sg_append_buffer() + can be called multiple times per frame to append new data to the + buffer piece by piece, optionally interleaved with draw calls referencing + the previously written data. + + sg_append_buffer() returns a byte offset to the start of the + written data, this offset can be assigned to + sg_bindings.vertex_buffer_offsets[n] or + sg_bindings.index_buffer_offset + + Code example: + + for (...) { + const void* data = ...; + const int num_bytes = ...; + int offset = sg_append_buffer(buf, &(sg_range) { .ptr=data, .size=num_bytes }); + bindings.vertex_buffer_offsets[0] = offset; + sg_apply_pipeline(pip); + sg_apply_bindings(&bindings); + sg_apply_uniforms(...); + sg_draw(...); + } + + A buffer to be used with sg_append_buffer() must have been created + with sg_buffer_desc.usage.dynamic_update or .stream_update. + + If the application appends more data to the buffer then fits into + the buffer, the buffer will go into the "overflow" state for the + rest of the frame. + + Any draw calls attempting to render an overflown buffer will be + silently dropped (in debug mode this will also result in a + validation error). + + You can also check manually if a buffer is in overflow-state by calling + + bool sg_query_buffer_overflow(sg_buffer buf) + + You can manually check to see if an overflow would occur before adding + any data to a buffer by calling + + bool sg_query_buffer_will_overflow(sg_buffer buf, size_t size) + + NOTE: Due to restrictions in underlying 3D-APIs, appended chunks of + data will be 4-byte aligned in the destination buffer. This means + that there will be gaps in index buffers containing 16-bit indices + when the number of indices in a call to sg_append_buffer() is + odd. This isn't a problem when each call to sg_append_buffer() + is associated with one draw call, but will be problematic when + a single indexed draw call spans several appended chunks of indices. + + --- to check at runtime for optional features, limits and pixelformat support, + call: + + sg_features sg_query_features() + sg_limits sg_query_limits() + sg_pixelformat_info sg_query_pixelformat(sg_pixel_format fmt) + + --- if you need to call into the underlying 3D-API directly, you must call: + + sg_reset_state_cache() + + ...before calling sokol_gfx functions again + + --- you can inspect the original sg_desc structure handed to sg_setup() + by calling sg_query_desc(). This will return an sg_desc struct with + the default values patched in instead of any zero-initialized values + + --- you can get a desc struct matching the creation attributes of a + specific resource object via: + + sg_buffer_desc sg_query_buffer_desc(sg_buffer buf) + sg_image_desc sg_query_image_desc(sg_image img) + sg_sampler_desc sg_query_sampler_desc(sg_sampler smp) + sg_shader_desc sq_query_shader_desc(sg_shader shd) + sg_pipeline_desc sg_query_pipeline_desc(sg_pipeline pip) + sg_view_desc sg_query_view_desc(sg_view view) + + ...but NOTE that the returned desc structs may be incomplete, only + creation attributes that are kept around internally after resource + creation will be filled in, and in some cases (like shaders) that's + very little. Any missing attributes will be set to zero. The returned + desc structs might still be useful as partial blueprint for creating + similar resources if filled up with the missing attributes. + + Calling the query-desc functions on an invalid resource will return + completely zeroed structs (it makes sense to check the resource state + with sg_query_*_state() first) + + --- you can query the default resource creation parameters through the functions + + sg_buffer_desc sg_query_buffer_defaults(const sg_buffer_desc* desc) + sg_image_desc sg_query_image_defaults(const sg_image_desc* desc) + sg_sampler_desc sg_query_sampler_defaults(const sg_sampler_desc* desc) + sg_shader_desc sg_query_shader_defaults(const sg_shader_desc* desc) + sg_pipeline_desc sg_query_pipeline_defaults(const sg_pipeline_desc* desc) + sg_view_desc sg_query_view_defaults(const sg_view_desc* desc) + + These functions take a pointer to a desc structure which may contain + zero-initialized items for default values. These zero-init values + will be replaced with their concrete values in the returned desc + struct. + + --- you can inspect various internal resource runtime values via: + + sg_buffer_info sg_query_buffer_info(sg_buffer buf) + sg_image_info sg_query_image_info(sg_image img) + sg_sampler_info sg_query_sampler_info(sg_sampler smp) + sg_shader_info sg_query_shader_info(sg_shader shd) + sg_pipeline_info sg_query_pipeline_info(sg_pipeline pip) + sg_view_info sg_query_view_info(sg_view view) + + ...please note that the returned info-structs are tied quite closely + to sokol_gfx.h internals, and may change more often than other + public API functions and structs. + + -- you can query the type/flavour and parent resource of a view: + + sg_view_type sg_query_view_type(sg_view view) + sg_image sg_query_view_image(sg_view view) + sg_buffer sg_query_view_buffer(sg_view view) + + --- you can query frame stats and control stats collection via: + + sg_query_frame_stats() + sg_enable_frame_stats() + sg_disable_frame_stats() + sg_frame_stats_enabled() + + --- you can ask at runtime what backend sokol_gfx.h has been compiled for: + + sg_backend sg_query_backend(void) + + --- call the following helper functions to compute the number of + bytes in a texture row or surface for a specific pixel format. + These functions might be helpful when preparing image data for consumption + by sg_make_image() or sg_update_image(): + + int sg_query_row_pitch(sg_pixel_format fmt, int width, int int row_align_bytes); + int sg_query_surface_pitch(sg_pixel_format fmt, int width, int height, int row_align_bytes); + + Width and height are generally in number pixels, but note that 'row' has different meaning + for uncompressed vs compressed pixel formats: for uncompressed formats, a row is identical + with a single line if pixels, while in compressed formats, one row is a line of *compression blocks*. + + This is why calling sg_query_surface_pitch() for a compressed pixel format and height + N, N+1, N+2, ... may return the same result. + + The row_align_bytes parameter is for added flexibility. For image data that goes into + the sg_make_image() or sg_update_image() this should generally be 1, because these + functions take tightly packed image data as input no matter what alignment restrictions + exist in the backend 3D APIs. + + ON INITIALIZATION: + ================== + When calling sg_setup(), a pointer to an sg_desc struct must be provided + which contains initialization options. These options provide two types + of information to sokol-gfx: + + (1) upper bounds and limits needed to allocate various internal + data structures: + - the max number of resources of each type that can + be alive at the same time, this is used for allocating + internal pools + - the max overall size of uniform data that can be + updated per frame, including a worst-case alignment + per uniform update (this worst-case alignment is 256 bytes) + - the max size of all dynamic resource updates (sg_update_buffer, + sg_append_buffer and sg_update_image) per frame + - the max number of compute-dispatch calls in a compute pass + Not all of those limit values are used by all backends, but it is + good practice to provide them none-the-less. + + (2) 3D backend "environment information" in a nested sg_environment struct: + - pointers to backend-specific context- or device-objects (for instance + the D3D11, WebGPU or Metal device objects) + - defaults for external swapchain pixel formats and sample counts, + these will be used as default values in image and pipeline objects, + and the sg_swapchain struct passed into sg_begin_pass() + Usually you provide a complete sg_environment struct through + a helper function, as an example look at the sglue_environment() + function in the sokol_glue.h header. + + See the documentation block of the sg_desc struct below for more information. + + + ON RENDER PASSES + ================ + Relevant samples: + - https://floooh.github.io/sokol-html5/offscreen-sapp.html + - https://floooh.github.io/sokol-html5/offscreen-msaa-sapp.html + - https://floooh.github.io/sokol-html5/mrt-sapp.html + - https://floooh.github.io/sokol-html5/mrt-pixelformats-sapp.html + + A render pass groups rendering commands into a set of render target images + (called 'render pass attachments'). Render target images can be used in subsequent + passes as textures (it is invalid to use the same image both as render target + and as texture in the same pass). + + The following sokol-gfx functions must only be called inside a render-pass: + + sg_apply_viewport[f] + sg_apply_scissor_rect[f] + sg_draw + + The following function may be called inside a render- or compute-pass, but + not outside a pass: + + sg_apply_pipeline + sg_apply_bindings + sg_apply_uniforms + + A frame must have at least one 'swapchain render pass' which renders into an + externally provided swapchain provided as an sg_swapchain struct to the + sg_begin_pass() function. If you use sokol_gfx.h together with sokol_app.h, + just call the sglue_swapchain() helper function in sokol_glue.h to + provide the swapchain information. Otherwise the following information + must be provided: + + - the color pixel-format of the swapchain's render surface + - an optional depth/stencil pixel format if the swapchain + has a depth/stencil buffer + - an optional sample-count for MSAA rendering + - NOTE: the above three values can be zero-initialized, in that + case the defaults from the sg_environment struct will be used that + had been passed to the sg_setup() function. + - a number of backend specific objects: + - GL/GLES3: just a GL framebuffer handle + - D3D11: + - an ID3D11RenderTargetView for the rendering surface + - if MSAA is used, an ID3D11RenderTargetView as + MSAA resolve-target + - an optional ID3D11DepthStencilView for the + depth/stencil buffer + - WebGPU + - a WGPUTextureView object for the rendering surface + - if MSAA is used, a WGPUTextureView object as MSAA resolve target + - an optional WGPUTextureView for the + - Metal (NOTE that the roles of provided surfaces is slightly + different in Metal than in D3D11 or WebGPU, notably, the + CAMetalDrawable is either rendered to directly, or serves + as MSAA resolve target): + - a CAMetalDrawable object which is either rendered + into directly, or in case of MSAA rendering, serves + as MSAA-resolve-target + - if MSAA is used, an multisampled MTLTexture where + rendering goes into + - an optional MTLTexture for the depth/stencil buffer + + It's recommended that you create a helper function which returns an + initialized sg_swapchain struct by value. This can then be directly plugged + into the sg_begin_pass function like this: + + sg_begin_pass(&(sg_pass){ .swapchain = sglue_swapchain() }); + + As an example for such a helper function check out the function sglue_swapchain() + in the sokol_glue.h header. + + For offscreen render passes, the render target images used in a render pass + must be provided as sg_view objects specialized for the specific pass-attachment + types: + + - color-attachment-views for color-rendering + - depth-stencil-attachment-views for the depth-stencil-buffer surface + - resolve-attachment-views for MSAA-resolve operations + + For a simple offscreen scenario with one color-, one depth-stencil-render + target and without multisampling, setting up the required image- + and view-objects looks like this: + + First create two render target images, one with a color pixel format, + and one with the depth- or depth-stencil pixel format. Both images + must have the same dimensions. Also not the usage flags: + + const sg_image color_img = sg_make_image(&(sg_image_desc){ + .usage.color_attachment = true, + .width = 256, + .height = 256, + .pixel_format = SG_PIXELFORMAT_RGBA8, + .sample_count = 1, + }); + const sg_image depth_img = sg_make_image(&(sg_image_desc){ + .usage.depth_stencil_attachment = true, + .width = 256, + .height = 256, + .pixel_format = SG_PIXELFORMAT_DEPTH, + .sample_count = 1, + }); + + NOTE: when creating render target images, have in mind that some default values + are aligned with the default environment attributes in the sg_environment struct + that was passed into the sg_setup() call: + + - the default value for sg_image_desc.pixel_format is taken from + sg_environment.defaults.color_format + - the default value for sg_image_desc.sample_count is taken from + sg_environment.defaults.sample_count + - the default value for sg_image_desc.num_mipmaps is always 1 + + Next, create two view objects, one color-attachment-view and one + depth-stencil-attachment view: + + const sg_view color_att_view = sg_make_view(&(sg_view_desc){ + .color_attachment.image = color_img, + }); + const sg_view depth_att_view = sg_make_view(&(sg_view_desc){ + .depth_stencil_attachment.image = depth_img, + }); + + You'll typically also want to create a texture-view on the color image + to sample the color attachment image as texture in a later pass: + + const sg_view tex_view = sg_make_view(&(sg_view_desc){ + .texture.image = color_img, + }); + + The attachment-view objects are then passed into the sg_begin_pass function in + place of the nested swapchain struct: + + sg_begin_pass(&(sg_pass){ + .attachments = { + .colors[0] = color_att_view, + .depth_stencil = depth_att_view, + }, + }); + + ...in a later pass when you want to sample the color attachment image as + texture, use the texture view in the sg_apply_bindings() call: + + sg_apply_bindings(&(sg_bindings){ + .vertex_buffers[0] = ..., + .index_buffer = ..., + .views[VIEW_tex] = tex_view, + .samplers[SMP_smp] = smp, + }); + + Swapchain and offscreen passes form dependency trees with a swapchain + pass at the root, offscreen passes as nodes, and attachment images as + dependencies between passes. + + sg_pass_action structs are used to define actions that should happen at the + start and end of render passes (such as clearing pass attachments to a + specific color or depth-value, or performing an MSAA resolve operation at + the end of a pass). + + A typical sg_pass_action object which clears the color attachment to black + might look like this: + + const sg_pass_action = { + .colors[0] = { + .load_action = SG_LOADACTION_CLEAR, + .clear_value = { 0.0f, 0.0f, 0.0f, 1.0f } + } + }; + + This omits the defaults for the color attachment store action, and + the depth-stencil-attachments actions. The same pass action with the + defaults explicitly filled in would look like this: + + const sg_pass_action pass_action = { + .colors[0] = { + .load_action = SG_LOADACTION_CLEAR, + .store_action = SG_STOREACTION_STORE, + .clear_value = { 0.0f, 0.0f, 0.0f, 1.0f } + }, + .depth = = { + .load_action = SG_LOADACTION_CLEAR, + .store_action = SG_STOREACTION_DONTCARE, + .clear_value = 1.0f, + }, + .stencil = { + .load_action = SG_LOADACTION_CLEAR, + .store_action = SG_STOREACTION_DONTCARE, + .clear_value = 0 + } + }; + + With the sg_pass object and sg_pass_action struct in place everything + is ready now for the actual render pass: + + Using such this prepared sg_pass_action in a swapchain pass looks like + this: + + sg_begin_pass(&(sg_pass){ + .action = pass_action, + .swapchain = sglue_swapchain() + }); + ... + sg_end_pass(); + + ...of alternatively in one offscreen pass: + + sg_begin_pass(&(sg_pass){ + .action = pass_action, + .attachments = { + .colors[0] = color_att_view, + .depth_stencil = ds_att_view, + }, + }); + ... + sg_end_pass(); + + Offscreen rendering can also go into a mipmap, or a slice/face of + a cube-, array- or 3d-image (which some restrictions, for instance + it's not possible to create a 3D image with a depth/stencil pixel format, + these exceptions are generally caught by the sokol-gfx validation layer). + + The mipmap/slice selection is baked into the attachment-view objects, for + instance to create a color-attachment-view for rendering into mip-level + 2 and slice 3 of an array texture: + + const sg_view color_att_view = sg_make_view(&(sg_view_desc){ + .color_attachment = { + .image = color_img, + .mip_level = 2, + .slice = 3, + }, + }); + + If MSAA offscreen rendering is desired, the multi-sample rendering result + must be 'resolved' into a separate 'resolve image', before that image can + be used as texture. + + Setting up MSAA offscreen 3D rendering requires three image objects + (one color-attachment image with a sample count > 1), a resolve-attachment + image with a sample count of 1, and a depth-stencil-attachment image + with the same sample count as the color-attachment image: + + const sg_image color_img = sg_make_image(&(sg_image_desc){ + .usage.color_attachment = true, + .width = 256, + .height = 256, + .pixel_format = SG_PIXELFORMAT_RGBA8, + .sample_count = 4, + }); + const sg_image resolve_img = sg_make_image(&(sg_image_desc){ + .usage.resolve_attachment = true, + .width = 256, + .height = 256, + .pixel_format = SG_PIXELFORMAT_RGBA8, + .sample_count = 1, + }); + const sg_image depth_img = sg_make_image(&(sg_image_desc){ + .usage.depth_stencil_attachment = true, + .width = 256, + .height = 256, + .pixel_format = SG_PIXELFORMAT_DEPTH, + .sample_count = 4, + }); + + Next you'll need the corresponding attachment-view objects: + + const sg_view color_att_view = sg_make_view(&(sg_view_desc){ + .color_attachment.image = color_img, + }); + const sg_view resolve_att_view = sg_make_view(&(sg_view_desc){ + .resolve_attachment.image = resolve_img, + }); + const sg_view depth_att_view = sg_make_view(&(sg_view_desc){ + .depth_stencil_attachment.image = depth_img, + }); + + To sample the rendered image as a texture in a later pass you'll also + need a texture-view on the resolve-attachment-image (not the color-attachment-image!): + + const sg_view tex_view = sg_make_view(&(sg_view_desc){ + .texture.image = resolve_img, + }); + + Next start the render pass with all attachment-views, as soon as a + resolve-attachment-view is provided, an MSAA resolve operation will happen + at the end of the pass. Also note that the content of the MSAA color-attachment-image + doesn't need to be preserved, since it's only needed until the MSAA-resolve + at the end of the pass, so the .store_action should be set to "don't care": + + sg_begin_pass(&(sg_pass){ + .attachments = { + .colors[0] = color_att_view, + .resolves[0] = resolve_att_view, + .depth_stencil = depth_att_view, + }, + .action = { + .colors[0] = { + .load_action = SG_LOADACTION_CLEAR, + .store_action = SG_STOREACTION_DONTCARE, + .clear_value = { 0.0f, 0.0f, 0.0f, 1.0f }, + } + }, + }); + + ...in a later pass, use the texture-view that had been created on the + resolve-image to use the rendering result as texture: + + sg_apply_bindings(&(sg_bindings){ + .vertex_buffers[0] = ..., + .index_buffer = ..., + .views[VIEW_tex] = tex_view, + .samplers[SMP_smp] = smp, + }); + + ON COMPUTE PASSES + ================= + Compute passes are used to update the content of storage buffers and + storage images by running compute shader code on + the GPU. Updating storage resources with a compute shader will almost always + be more efficient than computing the same data on the CPU and then uploading + it via `sg_update_buffer()` or `sg_update_image()`. + + NOTE: compute passes are only supported on the following platforms and + backends: + + - macOS and iOS with Metal + - Windows with D3D11 and OpenGL + - Linux with OpenGL or GLES3.1+ + - Web with WebGPU + - Android with GLES3.1+ + + ...this means compute shaders can't be used on the following platform/backend + combos (the same restrictions apply to using storage buffers without compute + shaders): + + - macOS with GL + - iOS with GLES3 + - Web with WebGL2 + + A compute pass is started with: + + sg_begin_pass(&(sg_pass){ .compute = true }); + + ...and finished with a regular: + + sg_end_pass(); + + Typically the following functions will be called inside a compute pass: + + sg_apply_pipeline() + sg_apply_bindings() + sg_apply_uniforms() + sg_dispatch() + + The following functions are disallowed inside a compute pass + and will cause validation layer errors: + + sg_apply_viewport[f]() + sg_apply_scissor_rect[f]() + sg_draw() + + Only special 'compute shaders' and 'compute pipelines' can be used in + compute passes. A compute shader only has a compute-function instead + of a vertex- and fragment-function pair, and it doesn't accept vertex- + and index-buffers as bindings, only storage-buffer-views (readable + and writable), storage-image-views (read/write or writeonly) and + texture-views (read-only). + + A compute pipeline is created by providing a compute shader object, + setting the .compute creation parameter to true and not defining any + 'render state': + + sg_pipeline pip = sg_make_pipeline(&(sg_pipeline_desc){ + .compute = true, + .shader = compute_shader, + }); + + The sg_apply_bindings and sg_apply_uniforms calls are the same as in + render passes, with the exception that no vertex- and index-buffers + can be bound in the sg_apply_bindings call. + + Finally to kick off a compute workload, call sg_dispatch with the + number of workgroups in the x, y and z-dimension: + + sg_dispatch(int num_groups_x, int num_groups_y, int num_groups_z) + + Also see the following compute-shader samples: + + - https://floooh.github.io/sokol-webgpu/instancing-compute-sapp.html + - https://floooh.github.io/sokol-webgpu/computeboids-sapp.html + - https://floooh.github.io/sokol-webgpu/imageblur-sapp.html + + + ON SHADER CREATION + ================== + sokol-gfx doesn't come with an integrated shader cross-compiler, instead + backend-specific shader sources or binary blobs need to be provided when + creating a shader object, along with reflection information about the + shader resource binding interface needed to bind sokol-gfx resources to the + proper shader inputs. + + The easiest way to provide all this shader creation data is to use the + sokol-shdc shader compiler tool to compile shaders from a common + GLSL syntax into backend-specific sources or binary blobs, along with + shader interface information and uniform blocks and storage buffer array items + mapped to C structs. + + To create a shader using a C header which has been code-generated by sokol-shdc: + + // include the C header code-generated by sokol-shdc: + #include "myshader.glsl.h" + ... + + // create shader using a code-generated helper function from the C header: + sg_shader shd = sg_make_shader(myshader_shader_desc(sg_query_backend())); + + The samples in the 'sapp' subdirectory of the sokol-samples project + also use the sokol-shdc approach: + + https://github.com/floooh/sokol-samples/tree/master/sapp + + If you're planning to use sokol-shdc, you can stop reading here, instead + continue with the sokol-shdc documentation: + + https://github.com/floooh/sokol-tools/blob/master/docs/sokol-shdc.md + + To create shaders with backend-specific shader code or binary blobs, + the sg_make_shader() function requires the following information: + + - Shader code or shader binary blobs for the vertex- and fragment-, or the + compute-shader-stage: + - for the desktop GL backend, source code can be provided in '#version 410' or + '#version 430', version 430 is required when using storage buffers and + compute shaders, but note that this is not available on macOS + - for the GLES3 backend, source code must be provided in '#version 300 es' or + '#version 310 es' syntax (version 310 is required for storage buffer and + compute shader support, but note that this is not supported on WebGL2) + - for the D3D11 backend, shaders can be provided as source or binary + blobs, the source code should be in HLSL4.0 (for compatibility with old + low-end GPUs) or preferably in HLSL5.0 syntax, note that when + shader source code is provided for the D3D11 backend, sokol-gfx will + dynamically load 'd3dcompiler_47.dll' + - for the Metal backends, shaders can be provided as source or binary blobs, the + MSL version should be in 'metal-1.1' (other versions may work but are not tested) + - for the WebGPU backend, shaders must be provided as WGSL source code + - optionally the following shader-code related attributes can be provided: + - an entry function name (only on D3D11 or Metal, but not OpenGL) + - on D3D11 only, a compilation target (default is "vs_4_0" and "ps_4_0") + + - Information about the input vertex attributes used by the vertex shader, + most of that backend-specific: + - An optional 'base type' (float, signed-/unsigned-int) for each vertex + attribute. When provided, this is used by the validation layer to check + that the CPU-side input vertex format is compatible with the input + vertex declaration of the vertex shader. + - Metal: no location information needed since vertex attributes are always bound + by their attribute location defined in the shader via '[[attribute(N)]]' + - WebGPU: no location information needed since vertex attributes are always + bound by their attribute location defined in the shader via `@location(N)` + - GLSL: vertex attribute names can be optionally provided, in that case their + location will be looked up by name, otherwise, the vertex attribute location + can be defined with 'layout(location = N)' + - D3D11: a 'semantic name' and 'semantic index' must be provided for each vertex + attribute, e.g. if the vertex attribute is defined as 'TEXCOORD1' in the shader, + the semantic name would be 'TEXCOORD', and the semantic index would be '1' + + NOTE that vertex attributes currently must not have gaps. This requirement + may be relaxed in the future. + + - Specifically for Metal compute shaders, the 'number of threads per threadgroup' + must be provided. Normally this is extracted by sokol-shdc from the GLSL + shader source code. For instance the following statement in the input + GLSL: + + layout(local_size_x=64, local_size_y=1, local_size_z=1) in; + + ...will be communicated to the sokol-gfx Metal backend in the + code-generated sg_shader_desc struct: + + (sg_shader_desc){ + .mtl_threads_per_threadgroup = { .x = 64, .y = 1, .z = 1 }, + } + + - Information about each uniform block binding used in the shader: + - the shader stage of the uniform block (vertex, fragment or compute) + - the size of the uniform block in number of bytes + - a memory layout hint (currently 'native' or 'std140') where 'native' defines a + backend-specific memory layout which shouldn't be used for cross-platform code. + Only std140 guarantees a backend-agnostic memory layout. + - a backend-specific bind slot: + - D3D11/HLSL: the buffer register N (`register(bN)`) where N is 0..7 + - Metal/MSL: the buffer bind slot N (`[[buffer(N)]]`) where N is 0..7 + - WebGPU: the binding N in `@group(0) @binding(N)` where N is 0..15 + - For GLSL only: a description of the internal uniform block layout, which maps + member types and their offsets on the CPU side to uniform variable names + in the GLSL shader + - please also NOTE the documentation sections about UNIFORM DATA LAYOUT + and CROSS-BACKEND COMMON UNIFORM DATA LAYOUT below! + + - A description of each resource binding (texture-, storage-buffer- + and storage-image-bindings) which directly map to the sg_bindings.view[] + array slots. + + Each resource binding slot comes in three flavours: + + 1. Texture bindings with the following properties: + - the shader stage of the texture (vertex, fragment or compute) + - the expected image type: + - SG_IMAGETYPE_2D + - SG_IMAGETYPE_CUBE + - SG_IMAGETYPE_3D + - SG_IMAGETYPE_ARRAY + - the expected 'image sample type': + - SG_IMAGESAMPLETYPE_FLOAT + - SG_IMAGESAMPLETYPE_DEPTH + - SG_IMAGESAMPLETYPE_SINT + - SG_IMAGESAMPLETYPE_UINT + - SG_IMAGESAMPLETYPE_UNFILTERABLE_FLOAT + - a flag whether the texture is expected to be multisampled + - a backend-specific bind slot: + - D3D11/HLSL: the texture register N (`register(tN)`) where N is 0..31 + (in HLSL, readonly storage buffers and texture share the same bind space) + - Metal/MSL: the texture bind slot N (`[[texture(N)]]`) where N is 0..31 + (the bind slot must not collide with storage image bindings on the same stage) + - WebGPU/WGSL: the binding N in `@group(0) @binding(N)` where N is 0..127 + + 2. Storage buffer bindings with the following properties: + - the shader stage of the storage buffer + - a boolean 'readonly' flag, this is used for validation and hazard + tracking in some 3D backends. Note that in render passes, only + readonly storage buffer bindings are allowed. In compute passes, any + read/write storage buffer binding is assumed to be written to by the + compute shader. + - a backend-specific bind slot: + - D3D11/HLSL: + - for readonly storage buffer bindings: the texture register N + (`register(tN)`) where N is 0..31 (in HLSL, readonly storage + buffers and textures share the same bind space for + 'shader resource views') + - for read/write storage buffer buffer bindings: the UAV register N + (`register(uN)`) where N is 0..31 (in HLSL, readwrite storage + buffers use their own bind space for 'unordered access views') + - Metal/MSL: the buffer bind slot N (`[[buffer(N)]]`) where N is 8..23 + - WebGPU/WGSL: the binding N in `@group(0) @binding(N)` where N is 0..127 + - GL/GLSL: the buffer binding N in `layout(binding=N)` + where N is 0..sg_limits.max_storage_buffer_bindings_per_stage + - note that storage buffer bindings are not supported on all backends + and platforms + + 3. Storage image bindings with the following properties: + - the shader stage (*must* be compute) + - the expected image type: + - SG_IMAGETYPE_2D + - SG_IMAGETYPE_CUBE + - SG_IMAGETYPE_3D + - SG_IMAGETYPE_ARRAY + - the 'access pixel format', this is currently limited to: + - SG_PIXELFORMAT_RGBA8 + - SG_PIXELFORMAT_RGBA8SN/UI/SI + - SG_PIXELFORMAT_RGBA16UI/SI/F + - SG_PIXELFORMAT_R32UIUI/SI/F + - SG_PIXELFORMAT_RG32UI/SI/F + - SG_PIXELFORMAT_RGBA32UI/SI/F + - the access type (readwrite or writeonly) + - a backend-specific bind slot: + - D3D11/HLSL: the UAV register N (`register(uN)` where N is 0..31, the + bind slot must not collide with UAV storage buffer bindings + - Metal/MSL: the texture bind slot N (`[[texture(N)]])` where N is 0..31, + the bind slot must not collide with other texture bindings on the same + stage + - WebGPU/WGSL: the binding N in `@group(1) @binding(N)` where N is 0..127 + - GL/GLSL: the buffer binding N in `layout(binding=N)` + where N is 0.._sg.max_storage_image_bindings_per_stage + - note that storage image bindings are not supported on all backends and platforms + + - A description of each sampler used in the shader: + - the shader stage of the sampler (vertex, fragment or compute) + - the expected sampler type: + - SG_SAMPLERTYPE_FILTERING, + - SG_SAMPLERTYPE_NONFILTERING, + - SG_SAMPLERTYPE_COMPARISON, + - a backend-specific bind slot: + - D3D11/HLSL: the sampler register N (`register(sN)`) where N is 0..SG_MAX_SAMPLER_BINDINGS + - Metal/MSL: the sampler bind slot N (`[[sampler(N)]]`) where N is 0..SG_MAX_SAMPLER_BINDINGS + - WebGPU/WGSL: the binding N in `@group(0) @binding(N)` where N is 0..127 + + - An array of 'texture-sampler-pairs' used by the shader to sample textures, + for D3D11, Metal and WebGPU this is used for validation purposes to check + whether the texture and sampler are compatible with each other (especially + WebGPU is very picky about combining the correct + texture-sample-type with the correct sampler-type). For GLSL an + additional 'combined-image-sampler name' must be provided because 'OpenGL + style GLSL' cannot handle separate texture and sampler objects, but still + groups them into a traditional GLSL 'sampler object'. + + Compatibility rules for image-sample-type vs sampler-type are as follows: + + - SG_IMAGESAMPLETYPE_FLOAT => (SG_SAMPLERTYPE_FILTERING or SG_SAMPLERTYPE_NONFILTERING) + - SG_IMAGESAMPLETYPE_UNFILTERABLE_FLOAT => SG_SAMPLERTYPE_NONFILTERING + - SG_IMAGESAMPLETYPE_SINT => SG_SAMPLERTYPE_NONFILTERING + - SG_IMAGESAMPLETYPE_UINT => SG_SAMPLERTYPE_NONFILTERING + - SG_IMAGESAMPLETYPE_DEPTH => SG_SAMPLERTYPE_COMPARISON + + Backend-specific bindslot ranges (not relevant when using sokol-shdc): + + - D3D11/HLSL: + - separate bindslot space per shader stage + - uniform block bindings (as cbuffer): `register(b0..b7)` + - texture- and readonly storage buffer bindings: `register(t0..t31)` + - read/write storage buffer and storage image bindings: `register(u0..u31)` + - samplers: `register(s0..s11)` + - Metal/MSL: + - separate bindslot space per shader stage + - uniform blocks: `[[buffer(0..7)]]` + - storage buffers: `[[buffer(8..23)]]` + - textures and storage image bindings: `[[texture(0..31)]]` + - samplers: `[[sampler(0..11)]]` + - WebGPU/WGSL: + - common bindslot space across shader stages + - uniform blocks: `@group(0) @binding(0..15)` + - textures, storage-images, storage-buffers and sampler: `@group(1) @binding(0..127)` + - GL/GLSL: + - uniforms and image-samplers are bound by name + - storage buffer bindings: `layout(std430, binding=0..sg_limits.max_storage_buffer_bindings_per_stage` (common + bindslot space across shader stages) + - storage image bindings: `layout(binding=0..sg_limits.max_storage_image_bindings_per_stage, [access_format])` + + For example code of how to create backend-specific shader objects, + please refer to the following samples: + + - for D3D11: https://github.com/floooh/sokol-samples/tree/master/d3d11 + - for Metal: https://github.com/floooh/sokol-samples/tree/master/metal + - for OpenGL: https://github.com/floooh/sokol-samples/tree/master/glfw + - for GLES3: https://github.com/floooh/sokol-samples/tree/master/html5 + - for WebGPU: https://github.com/floooh/sokol-samples/tree/master/wgpu + + + ON SG_IMAGESAMPLETYPE_UNFILTERABLE_FLOAT AND SG_SAMPLERTYPE_NONFILTERING + ======================================================================== + The WebGPU backend introduces the concept of 'unfilterable-float' textures, + which can only be combined with 'nonfiltering' samplers (this is a restriction + specific to WebGPU, but since the same sokol-gfx code should work across + all backend, the sokol-gfx validation layer also enforces this restriction + - the alternative would be undefined behaviour in some backend APIs on + some devices). + + The background is that some mobile devices (most notably iOS devices) can + not perform linear filtering when sampling textures with certain pixel + formats, most notable the 32F formats: + + - SG_PIXELFORMAT_R32F + - SG_PIXELFORMAT_RG32F + - SG_PIXELFORMAT_RGBA32F + + The information of whether a shader is going to be used with such an + unfilterable-float texture must already be provided in the sg_shader_desc + struct when creating the shader (see the above section "ON SHADER CREATION"). + + If you are using the sokol-shdc shader compiler, the information whether a + texture/sampler binding expects an 'unfilterable-float/nonfiltering' + texture/sampler combination cannot be inferred from the shader source + alone, you'll need to provide this hint via annotation-tags. For instance + here is an example from the ozz-skin-sapp.c sample shader which samples an + RGBA32F texture with skinning matrices in the vertex shader: + + ```glsl + @image_sample_type joint_tex unfilterable_float + uniform texture2D joint_tex; + @sampler_type smp nonfiltering + uniform sampler smp; + ``` + + This will result in SG_IMAGESAMPLETYPE_UNFILTERABLE_FLOAT and + SG_SAMPLERTYPE_NONFILTERING being written to the code-generated + sg_shader_desc struct. + + + ON VERTEX FORMATS + ================= + Sokol-gfx implements the same strict mapping rules from CPU-side + vertex component formats to GPU-side vertex input data types: + + - float and packed normalized CPU-side formats must be used as + floating point base type in the vertex shader + - packed signed-integer CPU-side formats must be used as signed + integer base type in the vertex shader + - packed unsigned-integer CPU-side formats must be used as unsigned + integer base type in the vertex shader + + These mapping rules are enforced by the sokol-gfx validation layer, + but only when sufficient reflection information is provided in + `sg_shader_desc.attrs[].base_type`. This is the case when sokol-shdc + is used, otherwise the default base_type will be SG_SHADERATTRBASETYPE_UNDEFINED + which causes the sokol-gfx validation check to be skipped (of course you + can also provide the per-attribute base type information manually when + not using sokol-shdc). + + The detailed mapping rules from SG_VERTEXFORMAT_* to GLSL data types + are as follows: + + - FLOAT[*] => float, vec* + - BYTE4N => vec* (scaled to -1.0 .. +1.0) + - UBYTE4N => vec* (scaled to 0.0 .. +1.0) + - SHORT[*]N => vec* (scaled to -1.0 .. +1.0) + - USHORT[*]N => vec* (scaled to 0.0 .. +1.0) + - INT[*] => int, ivec* + - UINT[*] => uint, uvec* + - BYTE4 => int* + - UBYTE4 => uint* + - SHORT[*] => int* + - USHORT[*] => uint* + + NOTE that sokol-gfx only provides vertex formats with sizes of a multiple + of 4 (e.g. BYTE4N but not BYTE2N). This is because vertex components must + be 4-byte aligned anyway. + + + UNIFORM DATA LAYOUT: + ==================== + NOTE: if you use the sokol-shdc shader compiler tool, you don't need to worry + about the following details. + + The data that's passed into the sg_apply_uniforms() function must adhere to + specific layout rules so that the GPU shader finds the uniform block + items at the right offset. + + For the D3D11 and Metal backends, sokol-gfx only cares about the size of uniform + blocks, but not about the internal layout. The data will just be copied into + a uniform/constant buffer in a single operation and it's up you to arrange the + CPU-side layout so that it matches the GPU side layout. This also means that with + the D3D11 and Metal backends you are not limited to a 'cross-platform' subset + of uniform variable types. + + If you ever only use one of the D3D11, Metal *or* WebGPU backend, you can stop reading here. + + For the GL backends, the internal layout of uniform blocks matters though, + and you are limited to a small number of uniform variable types. This is + because sokol-gfx must be able to locate the uniform block members in order + to upload them to the GPU with glUniformXXX() calls. + + To describe the uniform block layout to sokol-gfx, the following information + must be passed to the sg_make_shader() call in the sg_shader_desc struct: + + - a hint about the used packing rule (either SG_UNIFORMLAYOUT_NATIVE or + SG_UNIFORMLAYOUT_STD140) + - a list of the uniform block members types in the correct order they + appear on the CPU side + + For example if the GLSL shader has the following uniform declarations: + + uniform mat4 mvp; + uniform vec2 offset0; + uniform vec2 offset1; + uniform vec2 offset2; + + ...and on the CPU side, there's a similar C struct: + + typedef struct { + float mvp[16]; + float offset0[2]; + float offset1[2]; + float offset2[2]; + } params_t; + + ...the uniform block description in the sg_shader_desc must look like this: + + sg_shader_desc desc = { + .vs.uniform_blocks[0] = { + .size = sizeof(params_t), + .layout = SG_UNIFORMLAYOUT_NATIVE, // this is the default and can be omitted + .uniforms = { + // order must be the same as in 'params_t': + [0] = { .name = "mvp", .type = SG_UNIFORMTYPE_MAT4 }, + [1] = { .name = "offset0", .type = SG_UNIFORMTYPE_VEC2 }, + [2] = { .name = "offset1", .type = SG_UNIFORMTYPE_VEC2 }, + [3] = { .name = "offset2", .type = SG_UNIFORMTYPE_VEC2 }, + } + } + }; + + With this information sokol-gfx can now compute the correct offsets of the data items + within the uniform block struct. + + The SG_UNIFORMLAYOUT_NATIVE packing rule works fine if only the GL backends are used, + but for proper D3D11/Metal/GL a subset of the std140 layout must be used which is + described in the next section: + + + CROSS-BACKEND COMMON UNIFORM DATA LAYOUT + ======================================== + For cross-platform / cross-3D-backend code it is important that the same uniform block + layout on the CPU side can be used for all sokol-gfx backends. To achieve this, + a common subset of the std140 layout must be used: + + - The uniform block layout hint in sg_shader_desc must be explicitly set to + SG_UNIFORMLAYOUT_STD140. + - Only the following GLSL uniform types can be used (with their associated sokol-gfx enums): + - float => SG_UNIFORMTYPE_FLOAT + - vec2 => SG_UNIFORMTYPE_FLOAT2 + - vec3 => SG_UNIFORMTYPE_FLOAT3 + - vec4 => SG_UNIFORMTYPE_FLOAT4 + - int => SG_UNIFORMTYPE_INT + - ivec2 => SG_UNIFORMTYPE_INT2 + - ivec3 => SG_UNIFORMTYPE_INT3 + - ivec4 => SG_UNIFORMTYPE_INT4 + - mat4 => SG_UNIFORMTYPE_MAT4 + - Alignment for those types must be as follows (in bytes): + - float => 4 + - vec2 => 8 + - vec3 => 16 + - vec4 => 16 + - int => 4 + - ivec2 => 8 + - ivec3 => 16 + - ivec4 => 16 + - mat4 => 16 + - Arrays are only allowed for the following types: vec4, int4, mat4. + + Note that the HLSL cbuffer layout rules are slightly different from the + std140 layout rules, this means that the cbuffer declarations in HLSL code + must be tweaked so that the layout is compatible with std140. + + The by far easiest way to tackle the common uniform block layout problem is + to use the sokol-shdc shader cross-compiler tool! + + + ON STORAGE BUFFERS + ================== + The two main purpose of storage buffers are: + + - to be populated by compute shaders with dynamically generated data + - for providing random-access data to all shader stages + + Storage buffers can be used to pass large amounts of random access structured + data from the CPU side to the shaders. They are similar to data textures, but are + more convenient to use both on the CPU and shader side since they can be accessed + in shaders as as a 1-dimensional array of struct items. + + Storage buffers are *NOT* supported on the following platform/backend combos: + + - macOS+GL (because storage buffers require GL 4.3, while macOS only goes up to GL 4.1) + - platforms which only support a GLES3.0 context (WebGL2 and iOS) + + To use storage buffers, the following steps are required: + + - write a shader which uses storage buffers (vertex- and fragment-shaders + can only read from storage buffers, while compute-shaders can both read + and write storage buffers) + - create one or more storage buffers via sg_make_buffer() with the + `.usage.storage_buffer = true` + - when creating a shader via sg_make_shader(), populate the sg_shader_desc + struct with binding info (when using sokol-shdc, this step will be taken care + of automatically) + - which storage buffer bind slots on the vertex-, fragment- or compute-stage + are occupied + - whether the storage buffer on that bind slot is readonly (readonly + bindings are required for vertex- and fragment-shaders, and in compute + shaders the readonly flag is used to control hazard tracking in some + 3D backends) + + - when calling sg_apply_bindings(), apply the matching bind slots with the previously + created storage buffers + - ...and that's it. + + For more details, see the following backend-agnostic sokol samples: + + - simple vertex pulling from a storage buffer: + - C code: https://github.com/floooh/sokol-samples/blob/master/sapp/vertexpull-sapp.c + - shader: https://github.com/floooh/sokol-samples/blob/master/sapp/vertexpull-sapp.glsl + - instanced rendering via storage buffers (vertex- and instance-pulling): + - C code: https://github.com/floooh/sokol-samples/blob/master/sapp/instancing-pull-sapp.c + - shader: https://github.com/floooh/sokol-samples/blob/master/sapp/instancing-pull-sapp.glsl + - storage buffers both on the vertex- and fragment-stage: + - C code: https://github.com/floooh/sokol-samples/blob/master/sapp/sbuftex-sapp.c + - shader: https://github.com/floooh/sokol-samples/blob/master/sapp/sbuftex-sapp.glsl + - the Ozz animation sample rewritten to pull all rendering data from storage buffers: + - C code: https://github.com/floooh/sokol-samples/blob/master/sapp/ozz-storagebuffer-sapp.cc + - shader: https://github.com/floooh/sokol-samples/blob/master/sapp/ozz-storagebuffer-sapp.glsl + - the instancing sample modified to use compute shaders: + - C code: https://github.com/floooh/sokol-samples/blob/master/sapp/instancing-compute-sapp.c + - shader: https://github.com/floooh/sokol-samples/blob/master/sapp/instancing-compute-sapp.glsl + - the Compute Boids sample ported to sokol-gfx: + - C code: https://github.com/floooh/sokol-samples/blob/master/sapp/computeboids-sapp.c + - shader: https://github.com/floooh/sokol-samples/blob/master/sapp/computeboids-sapp.glsl + + ...also see the following backend-specific vertex pulling samples (those also don't use sokol-shdc): + + - D3D11: https://github.com/floooh/sokol-samples/blob/master/d3d11/vertexpulling-d3d11.c + - desktop GL: https://github.com/floooh/sokol-samples/blob/master/glfw/vertexpulling-glfw.c + - Metal: https://github.com/floooh/sokol-samples/blob/master/metal/vertexpulling-metal.c + - WebGPU: https://github.com/floooh/sokol-samples/blob/master/wgpu/vertexpulling-wgpu.c + + ...and the backend specific compute shader samples: + + - D3D11: https://github.com/floooh/sokol-samples/blob/master/d3d11/instancing-compute-d3d11.c + - desktop GL: https://github.com/floooh/sokol-samples/blob/master/glfw/instancing-compute-glfw.c + - Metal: https://github.com/floooh/sokol-samples/blob/master/metal/instancing-compute-metal.c + - WebGPU: https://github.com/floooh/sokol-samples/blob/master/wgpu/instancing-compute-wgpu.c + + Storage buffer shader authoring caveats when using sokol-shdc: + + - declare a read-only storage buffer interface block with `layout(binding=N) readonly buffer [name] { ... }` + (where 'N' is the index in `sg_bindings.storage_buffers[N]`) + - ...or a read/write storage buffer interface block with `layout(binding=N) buffer [name] { ... }` + - declare a struct which describes a single array item in the storage buffer interface block + - only put a single flexible array member into the storage buffer interface block + + E.g. a complete example in 'sokol-shdc GLSL': + + ```glsl + @vs + // declare a struct: + struct sb_vertex { + vec3 pos; + vec4 color; + } + // declare a buffer interface block with a single flexible struct array: + layout(binding=0) readonly buffer vertices { + sb_vertex vtx[]; + } + // in the shader function, access the storage buffer like this: + void main() { + vec3 pos = vtx[gl_VertexIndex].pos; + ... + } + @end + ``` + + In a compute shader you can read and write the same item in the same + storage buffer (but you'll have to be careful for random access since + many threads of the same compute function run in parallel): + + @cs + struct sb_item { + vec3 pos; + vec3 vel; + } + layout(binding=0) buffer items_ssbo { + sb_item items[]; + } + layout(local_size_x=64, local_size_y=1, local_size_z=1) in; + void main() { + uint idx = gl_GlobalInvocationID.x; + vec3 pos = items[idx].pos; + ... + items[idx].pos = pos; + } + @end + + Backend-specific storage-buffer caveats (not relevant when using sokol-shdc): + + D3D11: + - storage buffers are created as 'raw' Byte Address Buffers + (https://learn.microsoft.com/en-us/windows/win32/direct3d11/overviews-direct3d-11-resources-intro#raw-views-of-buffers) + - in HLSL, use a ByteAddressBuffer for readonly access of the buffer content: + (https://learn.microsoft.com/en-us/windows/win32/direct3dhlsl/sm5-object-byteaddressbuffer) + - ...or RWByteAddressBuffer for read/write access: + (https://learn.microsoft.com/en-us/windows/win32/direct3dhlsl/sm5-object-rwbyteaddressbuffer) + - readonly-storage buffers and textures are both bound as 'shader-resource-view' and + share the same bind slots (declared as `register(tN)` in HLSL), where N must be in the range 0..23) + - read/write storage buffers and storage images are bound as 'unordered-access-view' + (declared as `register(uN)` in HLSL where N is in the range 0..11) + + Metal: + - in Metal there is no internal difference between vertex-, uniform- and + storage-buffers, all are bound to the same 'buffer bind slots' with the + following reserved ranges: + - vertex shader stage: + - uniform buffers: slots 0..7 + - storage buffers: slots 8..15 + - vertex buffers: slots 15..23 + - fragment shader stage: + - uniform buffers: slots 0..7 + - storage buffers: slots 8..15 + - this means in MSL, storage buffer bindings start at [[buffer(8)]] both in + the vertex and fragment stage + + GL: + - the GL backend doesn't use name-lookup to find storage buffer bindings, this + means you must annotate buffers with `layout(std430, binding=N)` in GLSL + - ...where N is 0..sg_limits.max_storage_buffer_bindings_per_stage. + + WebGPU: + - in WGSL, textures, samplers and storage buffers all use a shared + bindspace across all shader stages on bindgroup 1: + + `@group(1) @binding(0..127) + + ON STORAGE IMAGES: + ================== + To write pixel data to texture objects in compute shaders, first an image + object must be created with `storage_image usage`: + + sg_image storage_image = sg_make_image(&(sg_image_desc){ + .usage.storage_image = true, + }, + .width = ..., + .height = ..., + .pixel_format = ..., + }); + + Next a storage-image-view object is required which also allows to pick + a specific mip-level or slice for the compute-shader to access: + + sg_view simg_view = sg_make_view(&(sg_view_desc){ + .storage_image = { + .image = storage_image, + .mip_level = ..., + .slice = ... + }, + }); + + Finally 'bind' the storage-image-view via a regular sg_apply_bindings() call + inside a compute pass: + + sg_begin_pass(&(sg_pass){ .compute = true }); + sg_apply_pipeline(...); + sg_apply_bindings(&(sg_bindings){ + .views[VIEW_simg] = simg_view, + }); + sg_dispatch(...); + sg_end_pass(); + + Currently, storage images can only be used with `readwrite` or `writeonly` access in + shaders. For readonly access use a regular texture binding instead. + + For an example of using storage images in compute shaders see imageblur-sapp: + + - C code: https://github.com/floooh/sokol-samples/blob/master/sapp/imageblur-sapp.c + - shader: https://github.com/floooh/sokol-samples/blob/master/sapp/imageblur-sapp.glsl + + TRACE HOOKS: + ============ + sokol_gfx.h optionally allows to install "trace hook" callbacks for + each public API functions. When a public API function is called, and + a trace hook callback has been installed for this function, the + callback will be invoked with the parameters and result of the function. + This is useful for things like debugging- and profiling-tools, or + keeping track of resource creation and destruction. + + To use the trace hook feature: + + --- Define SOKOL_TRACE_HOOKS before including the implementation. + + --- Setup an sg_trace_hooks structure with your callback function + pointers (keep all function pointers you're not interested + in zero-initialized), optionally set the user_data member + in the sg_trace_hooks struct. + + --- Install the trace hooks by calling sg_install_trace_hooks(), + the return value of this function is another sg_trace_hooks + struct which contains the previously set of trace hooks. + You should keep this struct around, and call those previous + functions pointers from your own trace callbacks for proper + chaining. + + As an example of how trace hooks are used, have a look at the + imgui/sokol_gfx_imgui.h header which implements a realtime + debugging UI for sokol_gfx.h on top of Dear ImGui. + + + MEMORY ALLOCATION OVERRIDE + ========================== + You can override the memory allocation functions at initialization time + like this: + + void* my_alloc(size_t size, void* user_data) { + return malloc(size); + } + + void my_free(void* ptr, void* user_data) { + free(ptr); + } + + ... + sg_setup(&(sg_desc){ + // ... + .allocator = { + .alloc_fn = my_alloc, + .free_fn = my_free, + .user_data = ..., + } + }); + ... + + If no overrides are provided, malloc and free will be used. + + This only affects memory allocation calls done by sokol_gfx.h + itself though, not any allocations in OS libraries. + + + ERROR REPORTING AND LOGGING + =========================== + To get any logging information at all you need to provide a logging callback in the setup call + the easiest way is to use sokol_log.h: + + #include "sokol_log.h" + + sg_setup(&(sg_desc){ .logger.func = slog_func }); + + To override logging with your own callback, first write a logging function like this: + + void my_log(const char* tag, // e.g. 'sg' + uint32_t log_level, // 0=panic, 1=error, 2=warn, 3=info + uint32_t log_item_id, // SG_LOGITEM_* + const char* message_or_null, // a message string, may be nullptr in release mode + uint32_t line_nr, // line number in sokol_gfx.h + const char* filename_or_null, // source filename, may be nullptr in release mode + void* user_data) + { + ... + } + + ...and then setup sokol-gfx like this: + + sg_setup(&(sg_desc){ + .logger = { + .func = my_log, + .user_data = my_user_data, + } + }); + + The provided logging function must be reentrant (e.g. be callable from + different threads). + + If you don't want to provide your own custom logger it is highly recommended to use + the standard logger in sokol_log.h instead, otherwise you won't see any warnings or + errors. + + + COMMIT LISTENERS + ================ + It's possible to hook callback functions into sokol-gfx which are called from + inside sg_commit() in unspecified order. This is mainly useful for libraries + that build on top of sokol_gfx.h to be notified about the end/start of a frame. + + To add a commit listener, call: + + static void my_commit_listener(void* user_data) { + ... + } + + bool success = sg_add_commit_listener((sg_commit_listener){ + .func = my_commit_listener, + .user_data = ..., + }); + + The function returns false if the internal array of commit listeners is full, + or the same commit listener had already been added. + + If the function returns true, my_commit_listener() will be called each frame + from inside sg_commit(). + + By default, 1024 distinct commit listeners can be added, but this number + can be tweaked in the sg_setup() call: + + sg_setup(&(sg_desc){ + .max_commit_listeners = 2048, + }); + + An sg_commit_listener item is equal to another if both the function + pointer and user_data field are equal. + + To remove a commit listener: + + bool success = sg_remove_commit_listener((sg_commit_listener){ + .func = my_commit_listener, + .user_data = ..., + }); + + ...where the .func and .user_data field are equal to a previous + sg_add_commit_listener() call. The function returns true if the commit + listener item was found and removed, and false otherwise. + + + RESOURCE CREATION AND DESTRUCTION IN DETAIL + =========================================== + The 'vanilla' way to create resource objects is with the 'make functions': + + sg_buffer sg_make_buffer(const sg_buffer_desc* desc) + sg_image sg_make_image(const sg_image_desc* desc) + sg_sampler sg_make_sampler(const sg_sampler_desc* desc) + sg_shader sg_make_shader(const sg_shader_desc* desc) + sg_pipeline sg_make_pipeline(const sg_pipeline_desc* desc) + sg_view sg_make_view(const sg_view_desc* desc) + + This will result in one of three cases: + + 1. The returned handle is invalid. This happens when there are no more + free slots in the resource pool for this resource type. An invalid + handle is associated with the INVALID resource state, for instance: + + sg_buffer buf = sg_make_buffer(...) + if (sg_query_buffer_state(buf) == SG_RESOURCESTATE_INVALID) { + // buffer pool is exhausted + } + + 2. The returned handle is valid, but creating the underlying resource + has failed for some reason. This results in a resource object in the + FAILED state. The reason *why* resource creation has failed differ + by resource type. Look for log messages with more details. A failed + resource state can be checked with: + + sg_buffer buf = sg_make_buffer(...) + if (sg_query_buffer_state(buf) == SG_RESOURCESTATE_FAILED) { + // creating the resource has failed + } + + 3. And finally, if everything goes right, the returned resource is + in resource state VALID and ready to use. This can be checked + with: + + sg_buffer buf = sg_make_buffer(...) + if (sg_query_buffer_state(buf) == SG_RESOURCESTATE_VALID) { + // creating the resource has failed + } + + When calling the 'make functions', the created resource goes through a number + of states: + + - INITIAL: the resource slot associated with the new resource is currently + free (technically, there is no resource yet, just an empty pool slot) + - ALLOC: a handle for the new resource has been allocated, this just means + a pool slot has been reserved. + - VALID or FAILED: in VALID state any 3D API backend resource objects have + been successfully created, otherwise if anything went wrong, the resource + will be in FAILED state. + + Sometimes it makes sense to first grab a handle, but initialize the + underlying resource at a later time. For instance when loading data + asynchronously from a slow data source, you may know what buffers and + textures are needed at an early stage of the loading process, but actually + loading the buffer or texture content can only be completed at a later time. + + For such situations, sokol-gfx resource objects can be created in two steps. + You can allocate a handle upfront with one of the 'alloc functions': + + sg_buffer sg_alloc_buffer(void) + sg_image sg_alloc_image(void) + sg_sampler sg_alloc_sampler(void) + sg_shader sg_alloc_shader(void) + sg_pipeline sg_alloc_pipeline(void) + sg_view sg_alloc_view(void) + + This will return a handle with the underlying resource object in the + ALLOC state: + + sg_image img = sg_alloc_image(); + if (sg_query_image_state(img) == SG_RESOURCESTATE_ALLOC) { + // allocating an image handle has succeeded, otherwise + // the image pool is full + } + + Such an 'incomplete' handle can be used in most sokol-gfx rendering functions + without doing any harm, sokol-gfx will simply skip any rendering operation + that involve resources which are not in VALID state. + + At a later time (for instance once the texture has completed loading + asynchronously), the resource creation can be completed by calling one of + the 'init functions', those functions take an existing resource handle and + 'desc struct': + + void sg_init_buffer(sg_buffer buf, const sg_buffer_desc* desc) + void sg_init_image(sg_image img, const sg_image_desc* desc) + void sg_init_sampler(sg_sampler smp, const sg_sampler_desc* desc) + void sg_init_shader(sg_shader shd, const sg_shader_desc* desc) + void sg_init_pipeline(sg_pipeline pip, const sg_pipeline_desc* desc) + void sg_init_view(sg_view view, const sg_view_desc* desc) + + The init functions expect a resource in ALLOC state, and after the function + returns, the resource will be either in VALID or FAILED state. Calling + an 'alloc function' followed by the matching 'init function' is fully + equivalent with calling the 'make function' alone. + + Destruction can also happen as a two-step process. The 'uninit functions' + will put a resource object from the VALID or FAILED state back into the + ALLOC state: + + void sg_uninit_buffer(sg_buffer buf) + void sg_uninit_image(sg_image img) + void sg_uninit_sampler(sg_sampler smp) + void sg_uninit_shader(sg_shader shd) + void sg_uninit_pipeline(sg_pipeline pip) + void sg_uninit_view(sg_view view) + + Calling the 'uninit functions' with a resource that is not in the VALID or + FAILED state is a no-op. + + To finally free the pool slot for recycling call the 'dealloc functions': + + void sg_dealloc_buffer(sg_buffer buf) + void sg_dealloc_image(sg_image img) + void sg_dealloc_sampler(sg_sampler smp) + void sg_dealloc_shader(sg_shader shd) + void sg_dealloc_pipeline(sg_pipeline pip) + void sg_dealloc_view(sg_view view) + + Calling the 'dealloc functions' on a resource that's not in ALLOC state is + a no-op, but will generate a warning log message. + + Calling an 'uninit function' and 'dealloc function' in sequence is equivalent + with calling the associated 'destroy function': + + void sg_destroy_buffer(sg_buffer buf) + void sg_destroy_image(sg_image img) + void sg_destroy_sampler(sg_sampler smp) + void sg_destroy_shader(sg_shader shd) + void sg_destroy_pipeline(sg_pipeline pip) + void sg_destroy_view(sg_view view) + + The 'destroy functions' can be called on resources in any state and generally + do the right thing (for instance if the resource is in ALLOC state, the destroy + function will be equivalent to the 'dealloc function' and skip the 'uninit part'). + + And finally to close the circle, the 'fail functions' can be called to manually + put a resource in ALLOC state into the FAILED state: + + sg_fail_buffer(sg_buffer buf) + sg_fail_image(sg_image img) + sg_fail_sampler(sg_sampler smp) + sg_fail_shader(sg_shader shd) + sg_fail_pipeline(sg_pipeline pip) + sg_fail_view(sg_view view) + + This is recommended if anything went wrong outside of sokol-gfx during asynchronous + resource setup (for instance a file loading operation failed). In this case, + the 'fail function' should be called instead of the 'init function'. + + Calling a 'fail function' on a resource that's not in ALLOC state is a no-op, + but will generate a warning log message. + + NOTE: that two-step resource creation usually only makes sense for buffers, + images and views, but not for samplers, shaders or pipelines. Most notably, trying + to create a pipeline object with a shader that's not in VALID state will + trigger a validation layer error, or if the validation layer is disabled, + result in a pipeline object in FAILED state. + + + WEBGPU CAVEATS + ============== + For a general overview and design notes of the WebGPU backend see: + + https://floooh.github.io/2023/10/16/sokol-webgpu.html + + In general, don't expect an automatic speedup when switching from the WebGL2 + backend to the WebGPU backend. Some WebGPU functions currently actually + have a higher CPU overhead than similar WebGL2 functions, leading to the + paradoxical situation that some WebGPU code may be slower than similar WebGL2 + code. + + - when writing WGSL shader code by hand, a specific bind-slot convention + must be used: + + All uniform block structs must use `@group(0)` and bindings in the + range 0..15 + + @group(0) @binding(0..15) + + All textures, samplers, storage-buffers and storage-images must use `@group(1)` + and bindings must be in the range 0..127: + + @group(1) @binding(0..127) + + Note that the number of texture, sampler, storage-buffer storage-image bindings + is still limited despite the large bind range: + + - up to 16 textures and sampler across all shader stages + - up to 8 storage buffers across all shader stages + - up to 4 storage images on the compute shader stage + + If you use sokol-shdc to generate WGSL shader code, you don't need to worry + about the above binding conventions since sokol-shdc will allocate + the WGSL bindslots). + + - The sokol-gfx WebGPU backend uses the sg_desc.uniform_buffer_size item + to allocate a single per-frame uniform buffer which must be big enough + to hold all data written by sg_apply_uniforms() during a single frame, + including a worst-case 256-byte alignment (e.g. each sg_apply_uniform + call will cost at least 256 bytes of uniform buffer size). The default size + is 4 MB, which is enough for 16384 sg_apply_uniform() calls per + frame (assuming the uniform data 'payload' is less than 256 bytes + per call). These rules are the same as for the Metal backend, so if + you are already using the Metal backend you'll be fine. + + - sg_apply_bindings(): the sokol-gfx WebGPU backend implements a bindgroup + cache to prevent excessive creation and destruction of BindGroup objects + when calling sg_apply_bindings(). The number of slots in the bindgroups + cache is defined in sg_desc.wgpu_bindgroups_cache_size when calling + sg_setup. The cache size must be a power-of-2 number, with the default being + 1024. The bindgroups cache behaviour can be observed by calling the new + function sg_query_frame_stats(), where the following struct items are + of interest: + + .wgpu.num_bindgroup_cache_hits + .wgpu.num_bindgroup_cache_misses + .wgpu.num_bindgroup_cache_collisions + .wgpu_num_bindgroup_cache_invalidates + .wgpu.num_bindgroup_cache_vs_hash_key_mismatch + + The value to pay attention to is `.wgpu.num_bindgroup_cache_collisions`, + if this number is consistently higher than a few percent of the + .wgpu.num_set_bindgroup value, it might be a good idea to bump the + bindgroups cache size to the next power-of-2. + + - sg_apply_viewport(): WebGPU currently has a unique restriction that viewport + rectangles must be contained entirely within the framebuffer. As a shitty + workaround sokol_gfx.h will clip incoming viewport rectangles against + the framebuffer, but this will distort the clipspace-to-screenspace mapping. + There's no proper way to handle this inside sokol_gfx.h, this must be fixed + in a future WebGPU update (see: https://github.com/gpuweb/gpuweb/issues/373 + and https://github.com/gpuweb/gpuweb/pull/5025) + + - The sokol shader compiler generally adds `diagnostic(off, derivative_uniformity);` + into the WGSL output. Currently only the Chrome WebGPU implementation seems + to recognize this. + + - Likewise, the following sokol-gfx pixel formats are not supported in WebGPU: + R16, R16SN, RG16, RG16SN, RGBA16, RGBA16SN. + Unlike unsupported vertex formats, unsupported pixel formats can be queried + in cross-backend code via sg_query_pixelformat() though. + + - The Emscripten WebGPU shim currently doesn't support the Closure minification + post-link-step (e.g. currently the emcc argument '--closure 1' or '--closure 2' + will generate broken Javascript code. + + - sokol-gfx requires the WebGPU device feature `depth32float-stencil8` to be enabled + (this should be widely supported) + + - sokol-gfx expects that the WebGPU device feature `float32-filterable` to *not* be + enabled (since this would exclude all iOS devices) + + + LICENSE + ======= + zlib/libpng license + + Copyright (c) 2018 Andre Weissflog + + This software is provided 'as-is', without any express or implied warranty. + In no event will the authors be held liable for any damages arising from the + use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software in a + product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not + be misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ +#define SOKOL_GFX_INCLUDED (1) +#include // size_t +#include +#include + +#if defined(SOKOL_API_DECL) && !defined(SOKOL_GFX_API_DECL) +#define SOKOL_GFX_API_DECL SOKOL_API_DECL +#endif +#ifndef SOKOL_GFX_API_DECL +#if defined(_WIN32) && defined(SOKOL_DLL) && defined(SOKOL_GFX_IMPL) +#define SOKOL_GFX_API_DECL __declspec(dllexport) +#elif defined(_WIN32) && defined(SOKOL_DLL) +#define SOKOL_GFX_API_DECL __declspec(dllimport) +#else +#define SOKOL_GFX_API_DECL extern +#endif +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/* + Resource id typedefs: + + sg_buffer: vertex- and index-buffers + sg_image: images used as textures and render-pass attachments + sg_sampler sampler objects describing how a texture is sampled in a shader + sg_shader: vertex- and fragment-shaders and shader interface information + sg_pipeline: associated shader and vertex-layouts, and render states + sg_view: a resource view object used for bindings and render-pass attachments + + Instead of pointers, resource creation functions return a 32-bit + handle which uniquely identifies the resource object. + + The 32-bit resource id is split into a 16-bit pool index in the lower bits, + and a 16-bit 'generation counter' in the upper bits. The index allows fast + pool lookups, and combined with the generation-counter it allows to detect + 'dangling accesses' (trying to use an object which no longer exists, and + its pool slot has been reused for a new object) + + The resource ids are wrapped into a strongly-typed struct so that + trying to pass an incompatible resource id is a compile error. +*/ +typedef struct sg_buffer { uint32_t id; } sg_buffer; +typedef struct sg_image { uint32_t id; } sg_image; +typedef struct sg_sampler { uint32_t id; } sg_sampler; +typedef struct sg_shader { uint32_t id; } sg_shader; +typedef struct sg_pipeline { uint32_t id; } sg_pipeline; +typedef struct sg_view { uint32_t id; } sg_view; + +/* + sg_range is a pointer-size-pair struct used to pass memory blobs into + sokol-gfx. When initialized from a value type (array or struct), you can + use the SG_RANGE() macro to build an sg_range struct. For functions which + take either a sg_range pointer, or a (C++) sg_range reference, use the + SG_RANGE_REF macro as a solution which compiles both in C and C++. +*/ +typedef struct sg_range { + const void* ptr; + size_t size; +} sg_range; + +// disabling this for every includer isn't great, but the warnings are also quite pointless +#if defined(_MSC_VER) +#pragma warning(disable:4221) // /W4 only: nonstandard extension used: 'x': cannot be initialized using address of automatic variable 'y' +#pragma warning(disable:4204) // VS2015: nonstandard extension used: non-constant aggregate initializer +#endif +#if defined(__cplusplus) +#define SG_RANGE(x) sg_range{ &x, sizeof(x) } +#define SG_RANGE_REF(x) sg_range{ &x, sizeof(x) } +#else +#define SG_RANGE(x) (sg_range){ &x, sizeof(x) } +#define SG_RANGE_REF(x) &(sg_range){ &x, sizeof(x) } +#endif + +// various compile-time constants in the public API +enum { + SG_INVALID_ID = 0, + SG_NUM_INFLIGHT_FRAMES = 2, + SG_MAX_COLOR_ATTACHMENTS = 8, + SG_MAX_UNIFORMBLOCK_MEMBERS = 16, + SG_MAX_VERTEX_ATTRIBUTES = 16, + SG_MAX_MIPMAPS = 16, + SG_MAX_VERTEXBUFFER_BINDSLOTS = 8, + SG_MAX_UNIFORMBLOCK_BINDSLOTS = 8, + SG_MAX_VIEW_BINDSLOTS = 32, + SG_MAX_SAMPLER_BINDSLOTS = 12, + SG_MAX_TEXTURE_SAMPLER_PAIRS = 32, // same as SG_MAX_VIEW_BINDSLOTS + SG_MAX_PORTABLE_COLOR_ATTACHMENTS = 4, + SG_MAX_PORTABLE_TEXTURE_BINDINGS_PER_STAGE = 16, + SG_MAX_PORTABLE_STORAGEBUFFER_BINDINGS_PER_STAGE = 8, // assuming sg_features.compute = true + SG_MAX_PORTABLE_STORAGEIMAGE_BINDINGS_PER_STAGE = 4, // assuming sg_features.compute = true +}; + +/* + sg_color + + An RGBA color value. +*/ +typedef struct sg_color { float r, g, b, a; } sg_color; + +/* + sg_backend + + The active 3D-API backend, use the function sg_query_backend() + to get the currently active backend. +*/ +typedef enum sg_backend { + SG_BACKEND_GLCORE, + SG_BACKEND_GLES3, + SG_BACKEND_D3D11, + SG_BACKEND_METAL_IOS, + SG_BACKEND_METAL_MACOS, + SG_BACKEND_METAL_SIMULATOR, + SG_BACKEND_WGPU, + SG_BACKEND_DUMMY, +} sg_backend; + +/* + sg_pixel_format + + sokol_gfx.h basically uses the same pixel formats as WebGPU, since these + are supported on most newer GPUs. + + A pixelformat name consist of three parts: + + - components (R, RG, RGB or RGBA) + - bit width per component (8, 16 or 32) + - component data type: + - unsigned normalized (no postfix) + - signed normalized (SN postfix) + - unsigned integer (UI postfix) + - signed integer (SI postfix) + - float (F postfix) + + Not all pixel formats can be used for everything, call sg_query_pixelformat() + to inspect the capabilities of a given pixelformat. The function returns + an sg_pixelformat_info struct with the following members: + + - sample: the pixelformat can be sampled as texture at least with + nearest filtering + - filter: the pixelformat can be sampled as texture with linear + filtering + - render: the pixelformat can be used as render-pass attachment + - blend: blending is supported when used as render-pass attachment + - msaa: multisample-antialiasing is supported when used + as render-pass attachment + - depth: the pixelformat can be used for depth-stencil attachments + - compressed: this is a block-compressed format + - bytes_per_pixel: the numbers of bytes in a pixel (0 for compressed formats) + + The default pixel format for texture images is SG_PIXELFORMAT_RGBA8. + + The default pixel format for render target images is platform-dependent + and taken from the sg_environment struct passed into sg_setup(). Typically + the default formats are: + + - for the Metal, D3D11 and WebGPU backends: SG_PIXELFORMAT_BGRA8 + - for GL backends: SG_PIXELFORMAT_RGBA8 +*/ +typedef enum sg_pixel_format { + _SG_PIXELFORMAT_DEFAULT, // value 0 reserved for default-init + SG_PIXELFORMAT_NONE, + + SG_PIXELFORMAT_R8, + SG_PIXELFORMAT_R8SN, + SG_PIXELFORMAT_R8UI, + SG_PIXELFORMAT_R8SI, + + SG_PIXELFORMAT_R16, + SG_PIXELFORMAT_R16SN, + SG_PIXELFORMAT_R16UI, + SG_PIXELFORMAT_R16SI, + SG_PIXELFORMAT_R16F, + SG_PIXELFORMAT_RG8, + SG_PIXELFORMAT_RG8SN, + SG_PIXELFORMAT_RG8UI, + SG_PIXELFORMAT_RG8SI, + + SG_PIXELFORMAT_R32UI, + SG_PIXELFORMAT_R32SI, + SG_PIXELFORMAT_R32F, + SG_PIXELFORMAT_RG16, + SG_PIXELFORMAT_RG16SN, + SG_PIXELFORMAT_RG16UI, + SG_PIXELFORMAT_RG16SI, + SG_PIXELFORMAT_RG16F, + SG_PIXELFORMAT_RGBA8, + SG_PIXELFORMAT_SRGB8A8, + SG_PIXELFORMAT_RGBA8SN, + SG_PIXELFORMAT_RGBA8UI, + SG_PIXELFORMAT_RGBA8SI, + SG_PIXELFORMAT_BGRA8, + SG_PIXELFORMAT_RGB10A2, + SG_PIXELFORMAT_RG11B10F, + SG_PIXELFORMAT_RGB9E5, + + SG_PIXELFORMAT_RG32UI, + SG_PIXELFORMAT_RG32SI, + SG_PIXELFORMAT_RG32F, + SG_PIXELFORMAT_RGBA16, + SG_PIXELFORMAT_RGBA16SN, + SG_PIXELFORMAT_RGBA16UI, + SG_PIXELFORMAT_RGBA16SI, + SG_PIXELFORMAT_RGBA16F, + + SG_PIXELFORMAT_RGBA32UI, + SG_PIXELFORMAT_RGBA32SI, + SG_PIXELFORMAT_RGBA32F, + + // NOTE: when adding/removing pixel formats before DEPTH, also update sokol_app.h/_SAPP_PIXELFORMAT_* + SG_PIXELFORMAT_DEPTH, + SG_PIXELFORMAT_DEPTH_STENCIL, + + // NOTE: don't put any new compressed format in front of here + SG_PIXELFORMAT_BC1_RGBA, + SG_PIXELFORMAT_BC2_RGBA, + SG_PIXELFORMAT_BC3_RGBA, + SG_PIXELFORMAT_BC3_SRGBA, + SG_PIXELFORMAT_BC4_R, + SG_PIXELFORMAT_BC4_RSN, + SG_PIXELFORMAT_BC5_RG, + SG_PIXELFORMAT_BC5_RGSN, + SG_PIXELFORMAT_BC6H_RGBF, + SG_PIXELFORMAT_BC6H_RGBUF, + SG_PIXELFORMAT_BC7_RGBA, + SG_PIXELFORMAT_BC7_SRGBA, + SG_PIXELFORMAT_ETC2_RGB8, + SG_PIXELFORMAT_ETC2_SRGB8, + SG_PIXELFORMAT_ETC2_RGB8A1, + SG_PIXELFORMAT_ETC2_RGBA8, + SG_PIXELFORMAT_ETC2_SRGB8A8, + SG_PIXELFORMAT_EAC_R11, + SG_PIXELFORMAT_EAC_R11SN, + SG_PIXELFORMAT_EAC_RG11, + SG_PIXELFORMAT_EAC_RG11SN, + + SG_PIXELFORMAT_ASTC_4x4_RGBA, + SG_PIXELFORMAT_ASTC_4x4_SRGBA, + + _SG_PIXELFORMAT_NUM, + _SG_PIXELFORMAT_FORCE_U32 = 0x7FFFFFFF +} sg_pixel_format; + +/* + Runtime information about a pixel format, returned by sg_query_pixelformat(). +*/ +typedef struct sg_pixelformat_info { + bool sample; // pixel format can be sampled in shaders at least with nearest filtering + bool filter; // pixel format can be sampled with linear filtering + bool render; // pixel format can be used as render-pass attachment + bool blend; // pixel format supports alpha-blending when used as render-pass attachment + bool msaa; // pixel format supports MSAA when used as render-pass attachment + bool depth; // pixel format is a depth format + bool compressed; // true if this is a hardware-compressed format + bool read; // true if format supports compute shader read access + bool write; // true if format supports compute shader write access + int bytes_per_pixel; // NOTE: this is 0 for compressed formats, use sg_query_row_pitch() / sg_query_surface_pitch() as alternative +} sg_pixelformat_info; + +/* + Runtime information about available optional features, returned by sg_query_features() +*/ +typedef struct sg_features { + bool origin_top_left; // framebuffer- and texture-origin is in top left corner + bool image_clamp_to_border; // border color and clamp-to-border uv-wrap mode is supported + bool mrt_independent_blend_state; // multiple-render-target rendering can use per-render-target blend state + bool mrt_independent_write_mask; // multiple-render-target rendering can use per-render-target color write masks + bool compute; // storage buffers and compute shaders are supported + bool msaa_texture_bindings; // if true, multisampled images can be bound as textures + bool separate_buffer_types; // cannot use the same buffer for vertex and indices (only WebGL2) + bool draw_base_vertex; // draw with (base vertex > 0) && (base_instance == 0) supported + bool draw_base_instance; // draw with (base instance > 0) supported + bool gl_texture_views; // supports 'proper' texture views (GL 4.3+) +} sg_features; + +/* + Runtime information about resource limits, returned by sg_query_limit() +*/ +typedef struct sg_limits { + int max_image_size_2d; // max width/height of SG_IMAGETYPE_2D images + int max_image_size_cube; // max width/height of SG_IMAGETYPE_CUBE images + int max_image_size_3d; // max width/height/depth of SG_IMAGETYPE_3D images + int max_image_size_array; // max width/height of SG_IMAGETYPE_ARRAY images + int max_image_array_layers; // max number of layers in SG_IMAGETYPE_ARRAY images + int max_vertex_attrs; // max number of vertex attributes, clamped to SG_MAX_VERTEX_ATTRIBUTES + int max_color_attachments; // max number of render pass color attachments, clamped to SG_MAX_COLOR_ATTACHMENTS + int max_texture_bindings_per_stage; // max number of texture bindings per shader stage, clamped to SG_MAX_VIEW_BINDSLOTS + int max_storage_buffer_bindings_per_stage; // max number of storage buffer bindings per shader stage, clamped to SG_MAX_VIEW_BINDSLOTS + int max_storage_image_bindings_per_stage; // max number of storage image bindings per shader stage, clamped to SG_MAX_VIEW_BINDSLOTS + int gl_max_vertex_uniform_components; // GL_MAX_VERTEX_UNIFORM_COMPONENTS (only on GL backends) + int gl_max_combined_texture_image_units; // GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS (only on GL backends) + int d3d11_max_unordered_access_views; // 8 on feature level 11.0, otherwise 32 (clamped to SG_MAX_VIEW_BINDSLOTS) +} sg_limits; + +/* + sg_resource_state + + The current state of a resource in its resource pool. + Resources start in the INITIAL state, which means the + pool slot is unoccupied and can be allocated. When a resource is + created, first an id is allocated, and the resource pool slot + is set to state ALLOC. After allocation, the resource is + initialized, which may result in the VALID or FAILED state. The + reason why allocation and initialization are separate is because + some resource types (e.g. buffers and images) might be asynchronously + initialized by the user application. If a resource which is not + in the VALID state is attempted to be used for rendering, rendering + operations will silently be dropped. + + The special INVALID state is returned in sg_query_xxx_state() if no + resource object exists for the provided resource id. +*/ +typedef enum sg_resource_state { + SG_RESOURCESTATE_INITIAL, + SG_RESOURCESTATE_ALLOC, + SG_RESOURCESTATE_VALID, + SG_RESOURCESTATE_FAILED, + SG_RESOURCESTATE_INVALID, + _SG_RESOURCESTATE_FORCE_U32 = 0x7FFFFFFF +} sg_resource_state; + +/* + sg_index_type + + Indicates whether indexed rendering (fetching vertex-indices from an + index buffer) is used, and if yes, the index data type (16- or 32-bits). + + This is used in the sg_pipeline_desc.index_type member when creating a + pipeline object. + + The default index type is SG_INDEXTYPE_NONE. +*/ +typedef enum sg_index_type { + _SG_INDEXTYPE_DEFAULT, // value 0 reserved for default-init + SG_INDEXTYPE_NONE, + SG_INDEXTYPE_UINT16, + SG_INDEXTYPE_UINT32, + _SG_INDEXTYPE_NUM, + _SG_INDEXTYPE_FORCE_U32 = 0x7FFFFFFF +} sg_index_type; + +/* + sg_image_type + + Indicates the basic type of an image object (2D-texture, cubemap, + 3D-texture or 2D-array-texture). Used in the sg_image_desc.type member when + creating an image, and in sg_shader_image_desc to describe a sampled texture + in the shader (both must match and will be checked in the validation layer + when calling sg_apply_bindings). + + The default image type when creating an image is SG_IMAGETYPE_2D. +*/ +typedef enum sg_image_type { + _SG_IMAGETYPE_DEFAULT, // value 0 reserved for default-init + SG_IMAGETYPE_2D, + SG_IMAGETYPE_CUBE, + SG_IMAGETYPE_3D, + SG_IMAGETYPE_ARRAY, + _SG_IMAGETYPE_NUM, + _SG_IMAGETYPE_FORCE_U32 = 0x7FFFFFFF +} sg_image_type; + +/* + sg_image_sample_type + + The basic data type of a texture sample as expected by a shader. + Must be provided in sg_shader_image and used by the validation + layer in sg_apply_bindings() to check if the provided image object + is compatible with what the shader expects. Apart from the sokol-gfx + validation layer, WebGPU is the only backend API which actually requires + matching texture and sampler type to be provided upfront for validation + (other 3D APIs treat texture/sampler type mismatches as undefined behaviour). + + NOTE that the following texture pixel formats require the use + of SG_IMAGESAMPLETYPE_UNFILTERABLE_FLOAT, combined with a sampler + of type SG_SAMPLERTYPE_NONFILTERING: + + - SG_PIXELFORMAT_R32F + - SG_PIXELFORMAT_RG32F + - SG_PIXELFORMAT_RGBA32F + + (when using sokol-shdc, also check out the meta tags `@image_sample_type` + and `@sampler_type`) +*/ +typedef enum sg_image_sample_type { + _SG_IMAGESAMPLETYPE_DEFAULT, // value 0 reserved for default-init + SG_IMAGESAMPLETYPE_FLOAT, + SG_IMAGESAMPLETYPE_DEPTH, + SG_IMAGESAMPLETYPE_SINT, + SG_IMAGESAMPLETYPE_UINT, + SG_IMAGESAMPLETYPE_UNFILTERABLE_FLOAT, + _SG_IMAGESAMPLETYPE_NUM, + _SG_IMAGESAMPLETYPE_FORCE_U32 = 0x7FFFFFFF +} sg_image_sample_type; + +/* + sg_sampler_type + + The basic type of a texture sampler (sampling vs comparison) as + defined in a shader. Must be provided in sg_shader_sampler_desc. + + sg_image_sample_type and sg_sampler_type for a texture/sampler + pair must be compatible with each other, specifically only + the following pairs are allowed: + + - SG_IMAGESAMPLETYPE_FLOAT => (SG_SAMPLERTYPE_FILTERING or SG_SAMPLERTYPE_NONFILTERING) + - SG_IMAGESAMPLETYPE_UNFILTERABLE_FLOAT => SG_SAMPLERTYPE_NONFILTERING + - SG_IMAGESAMPLETYPE_SINT => SG_SAMPLERTYPE_NONFILTERING + - SG_IMAGESAMPLETYPE_UINT => SG_SAMPLERTYPE_NONFILTERING + - SG_IMAGESAMPLETYPE_DEPTH => SG_SAMPLERTYPE_COMPARISON +*/ +typedef enum sg_sampler_type { + _SG_SAMPLERTYPE_DEFAULT, + SG_SAMPLERTYPE_FILTERING, + SG_SAMPLERTYPE_NONFILTERING, + SG_SAMPLERTYPE_COMPARISON, + _SG_SAMPLERTYPE_NUM, + _SG_SAMPLERTYPE_FORCE_U32, +} sg_sampler_type; + +/* + sg_primitive_type + + This is the common subset of 3D primitive types supported across all 3D + APIs. This is used in the sg_pipeline_desc.primitive_type member when + creating a pipeline object. + + The default primitive type is SG_PRIMITIVETYPE_TRIANGLES. +*/ +typedef enum sg_primitive_type { + _SG_PRIMITIVETYPE_DEFAULT, // value 0 reserved for default-init + SG_PRIMITIVETYPE_POINTS, + SG_PRIMITIVETYPE_LINES, + SG_PRIMITIVETYPE_LINE_STRIP, + SG_PRIMITIVETYPE_TRIANGLES, + SG_PRIMITIVETYPE_TRIANGLE_STRIP, + _SG_PRIMITIVETYPE_NUM, + _SG_PRIMITIVETYPE_FORCE_U32 = 0x7FFFFFFF +} sg_primitive_type; + +/* + sg_filter + + The filtering mode when sampling a texture image. This is + used in the sg_sampler_desc.min_filter, sg_sampler_desc.mag_filter + and sg_sampler_desc.mipmap_filter members when creating a sampler object. + + For the default is SG_FILTER_NEAREST. +*/ +typedef enum sg_filter { + _SG_FILTER_DEFAULT, // value 0 reserved for default-init + SG_FILTER_NEAREST, + SG_FILTER_LINEAR, + _SG_FILTER_NUM, + _SG_FILTER_FORCE_U32 = 0x7FFFFFFF +} sg_filter; + +/* + sg_wrap + + The texture coordinates wrapping mode when sampling a texture + image. This is used in the sg_image_desc.wrap_u, .wrap_v + and .wrap_w members when creating an image. + + The default wrap mode is SG_WRAP_REPEAT. + + NOTE: SG_WRAP_CLAMP_TO_BORDER is not supported on all backends + and platforms. To check for support, call sg_query_features() + and check the "clamp_to_border" boolean in the returned + sg_features struct. + + Platforms which don't support SG_WRAP_CLAMP_TO_BORDER will silently fall back + to SG_WRAP_CLAMP_TO_EDGE without a validation error. +*/ +typedef enum sg_wrap { + _SG_WRAP_DEFAULT, // value 0 reserved for default-init + SG_WRAP_REPEAT, + SG_WRAP_CLAMP_TO_EDGE, + SG_WRAP_CLAMP_TO_BORDER, + SG_WRAP_MIRRORED_REPEAT, + _SG_WRAP_NUM, + _SG_WRAP_FORCE_U32 = 0x7FFFFFFF +} sg_wrap; + +/* + sg_border_color + + The border color to use when sampling a texture, and the UV wrap + mode is SG_WRAP_CLAMP_TO_BORDER. + + The default border color is SG_BORDERCOLOR_OPAQUE_BLACK +*/ +typedef enum sg_border_color { + _SG_BORDERCOLOR_DEFAULT, // value 0 reserved for default-init + SG_BORDERCOLOR_TRANSPARENT_BLACK, + SG_BORDERCOLOR_OPAQUE_BLACK, + SG_BORDERCOLOR_OPAQUE_WHITE, + _SG_BORDERCOLOR_NUM, + _SG_BORDERCOLOR_FORCE_U32 = 0x7FFFFFFF +} sg_border_color; + +/* + sg_vertex_format + + The data type of a vertex component. This is used to describe + the layout of input vertex data when creating a pipeline object. + + NOTE that specific mapping rules exist from the CPU-side vertex + formats to the vertex attribute base type in the vertex shader code + (see doc header section 'ON VERTEX FORMATS'). +*/ +typedef enum sg_vertex_format { + SG_VERTEXFORMAT_INVALID, + SG_VERTEXFORMAT_FLOAT, + SG_VERTEXFORMAT_FLOAT2, + SG_VERTEXFORMAT_FLOAT3, + SG_VERTEXFORMAT_FLOAT4, + SG_VERTEXFORMAT_INT, + SG_VERTEXFORMAT_INT2, + SG_VERTEXFORMAT_INT3, + SG_VERTEXFORMAT_INT4, + SG_VERTEXFORMAT_UINT, + SG_VERTEXFORMAT_UINT2, + SG_VERTEXFORMAT_UINT3, + SG_VERTEXFORMAT_UINT4, + SG_VERTEXFORMAT_BYTE4, + SG_VERTEXFORMAT_BYTE4N, + SG_VERTEXFORMAT_UBYTE4, + SG_VERTEXFORMAT_UBYTE4N, + SG_VERTEXFORMAT_SHORT2, + SG_VERTEXFORMAT_SHORT2N, + SG_VERTEXFORMAT_USHORT2, + SG_VERTEXFORMAT_USHORT2N, + SG_VERTEXFORMAT_SHORT4, + SG_VERTEXFORMAT_SHORT4N, + SG_VERTEXFORMAT_USHORT4, + SG_VERTEXFORMAT_USHORT4N, + SG_VERTEXFORMAT_UINT10_N2, + SG_VERTEXFORMAT_HALF2, + SG_VERTEXFORMAT_HALF4, + _SG_VERTEXFORMAT_NUM, + _SG_VERTEXFORMAT_FORCE_U32 = 0x7FFFFFFF +} sg_vertex_format; + +/* + sg_vertex_step + + Defines whether the input pointer of a vertex input stream is advanced + 'per vertex' or 'per instance'. The default step-func is + SG_VERTEXSTEP_PER_VERTEX. SG_VERTEXSTEP_PER_INSTANCE is used with + instanced-rendering. + + The vertex-step is part of the vertex-layout definition + when creating pipeline objects. +*/ +typedef enum sg_vertex_step { + _SG_VERTEXSTEP_DEFAULT, // value 0 reserved for default-init + SG_VERTEXSTEP_PER_VERTEX, + SG_VERTEXSTEP_PER_INSTANCE, + _SG_VERTEXSTEP_NUM, + _SG_VERTEXSTEP_FORCE_U32 = 0x7FFFFFFF +} sg_vertex_step; + +/* + sg_uniform_type + + The data type of a uniform block member. This is used to + describe the internal layout of uniform blocks when creating + a shader object. This is only required for the GL backend, all + other backends will ignore the interior layout of uniform blocks. +*/ +typedef enum sg_uniform_type { + SG_UNIFORMTYPE_INVALID, + SG_UNIFORMTYPE_FLOAT, + SG_UNIFORMTYPE_FLOAT2, + SG_UNIFORMTYPE_FLOAT3, + SG_UNIFORMTYPE_FLOAT4, + SG_UNIFORMTYPE_INT, + SG_UNIFORMTYPE_INT2, + SG_UNIFORMTYPE_INT3, + SG_UNIFORMTYPE_INT4, + SG_UNIFORMTYPE_MAT4, + _SG_UNIFORMTYPE_NUM, + _SG_UNIFORMTYPE_FORCE_U32 = 0x7FFFFFFF +} sg_uniform_type; + +/* + sg_uniform_layout + + A hint for the interior memory layout of uniform blocks. This is + only relevant for the GL backend where the internal layout + of uniform blocks must be known to sokol-gfx. For all other backends the + internal memory layout of uniform blocks doesn't matter, sokol-gfx + will just pass uniform data as an opaque memory blob to the + 3D backend. + + SG_UNIFORMLAYOUT_NATIVE (default) + Native layout means that a 'backend-native' memory layout + is used. For the GL backend this means that uniforms + are packed tightly in memory (e.g. there are no padding + bytes). + + SG_UNIFORMLAYOUT_STD140 + The memory layout is a subset of std140. Arrays are only + allowed for the FLOAT4, INT4 and MAT4. Alignment is as + is as follows: + + FLOAT, INT: 4 byte alignment + FLOAT2, INT2: 8 byte alignment + FLOAT3, INT3: 16 byte alignment(!) + FLOAT4, INT4: 16 byte alignment + MAT4: 16 byte alignment + FLOAT4[], INT4[]: 16 byte alignment + + The overall size of the uniform block must be a multiple + of 16. + + For more information search for 'UNIFORM DATA LAYOUT' in the documentation block + at the start of the header. +*/ +typedef enum sg_uniform_layout { + _SG_UNIFORMLAYOUT_DEFAULT, // value 0 reserved for default-init + SG_UNIFORMLAYOUT_NATIVE, // default: layout depends on currently active backend + SG_UNIFORMLAYOUT_STD140, // std140: memory layout according to std140 + _SG_UNIFORMLAYOUT_NUM, + _SG_UNIFORMLAYOUT_FORCE_U32 = 0x7FFFFFFF +} sg_uniform_layout; + +/* + sg_cull_mode + + The face-culling mode, this is used in the + sg_pipeline_desc.cull_mode member when creating a + pipeline object. + + The default cull mode is SG_CULLMODE_NONE +*/ +typedef enum sg_cull_mode { + _SG_CULLMODE_DEFAULT, // value 0 reserved for default-init + SG_CULLMODE_NONE, + SG_CULLMODE_FRONT, + SG_CULLMODE_BACK, + _SG_CULLMODE_NUM, + _SG_CULLMODE_FORCE_U32 = 0x7FFFFFFF +} sg_cull_mode; + +/* + sg_face_winding + + The vertex-winding rule that determines a front-facing primitive. This + is used in the member sg_pipeline_desc.face_winding + when creating a pipeline object. + + The default winding is SG_FACEWINDING_CW (clockwise) +*/ +typedef enum sg_face_winding { + _SG_FACEWINDING_DEFAULT, // value 0 reserved for default-init + SG_FACEWINDING_CCW, + SG_FACEWINDING_CW, + _SG_FACEWINDING_NUM, + _SG_FACEWINDING_FORCE_U32 = 0x7FFFFFFF +} sg_face_winding; + +/* + sg_compare_func + + The compare-function for configuring depth- and stencil-ref tests + in pipeline objects, and for texture samplers which perform a comparison + instead of regular sampling operation. + + Used in the following structs: + + sg_pipeline_desc + .depth + .compare + .stencil + .front.compare + .back.compare + + sg_sampler_desc + .compare + + The default compare func for depth- and stencil-tests is + SG_COMPAREFUNC_ALWAYS. + + The default compare func for samplers is SG_COMPAREFUNC_NEVER. +*/ +typedef enum sg_compare_func { + _SG_COMPAREFUNC_DEFAULT, // value 0 reserved for default-init + SG_COMPAREFUNC_NEVER, + SG_COMPAREFUNC_LESS, + SG_COMPAREFUNC_EQUAL, + SG_COMPAREFUNC_LESS_EQUAL, + SG_COMPAREFUNC_GREATER, + SG_COMPAREFUNC_NOT_EQUAL, + SG_COMPAREFUNC_GREATER_EQUAL, + SG_COMPAREFUNC_ALWAYS, + _SG_COMPAREFUNC_NUM, + _SG_COMPAREFUNC_FORCE_U32 = 0x7FFFFFFF +} sg_compare_func; + +/* + sg_stencil_op + + The operation performed on a currently stored stencil-value when a + comparison test passes or fails. This is used when creating a pipeline + object in the following sg_pipeline_desc struct items: + + sg_pipeline_desc + .stencil + .front + .fail_op + .depth_fail_op + .pass_op + .back + .fail_op + .depth_fail_op + .pass_op + + The default value is SG_STENCILOP_KEEP. +*/ +typedef enum sg_stencil_op { + _SG_STENCILOP_DEFAULT, // value 0 reserved for default-init + SG_STENCILOP_KEEP, + SG_STENCILOP_ZERO, + SG_STENCILOP_REPLACE, + SG_STENCILOP_INCR_CLAMP, + SG_STENCILOP_DECR_CLAMP, + SG_STENCILOP_INVERT, + SG_STENCILOP_INCR_WRAP, + SG_STENCILOP_DECR_WRAP, + _SG_STENCILOP_NUM, + _SG_STENCILOP_FORCE_U32 = 0x7FFFFFFF +} sg_stencil_op; + +/* + sg_blend_factor + + The source and destination factors in blending operations. + This is used in the following members when creating a pipeline object: + + sg_pipeline_desc + .colors[i] + .blend + .src_factor_rgb + .dst_factor_rgb + .src_factor_alpha + .dst_factor_alpha + + The default value is SG_BLENDFACTOR_ONE for source + factors, and for the destination SG_BLENDFACTOR_ZERO if the associated + blend-op is ADD, SUBTRACT or REVERSE_SUBTRACT or SG_BLENDFACTOR_ONE + if the associated blend-op is MIN or MAX. +*/ +typedef enum sg_blend_factor { + _SG_BLENDFACTOR_DEFAULT, // value 0 reserved for default-init + SG_BLENDFACTOR_ZERO, + SG_BLENDFACTOR_ONE, + SG_BLENDFACTOR_SRC_COLOR, + SG_BLENDFACTOR_ONE_MINUS_SRC_COLOR, + SG_BLENDFACTOR_SRC_ALPHA, + SG_BLENDFACTOR_ONE_MINUS_SRC_ALPHA, + SG_BLENDFACTOR_DST_COLOR, + SG_BLENDFACTOR_ONE_MINUS_DST_COLOR, + SG_BLENDFACTOR_DST_ALPHA, + SG_BLENDFACTOR_ONE_MINUS_DST_ALPHA, + SG_BLENDFACTOR_SRC_ALPHA_SATURATED, + SG_BLENDFACTOR_BLEND_COLOR, + SG_BLENDFACTOR_ONE_MINUS_BLEND_COLOR, + SG_BLENDFACTOR_BLEND_ALPHA, + SG_BLENDFACTOR_ONE_MINUS_BLEND_ALPHA, + _SG_BLENDFACTOR_NUM, + _SG_BLENDFACTOR_FORCE_U32 = 0x7FFFFFFF +} sg_blend_factor; + +/* + sg_blend_op + + Describes how the source and destination values are combined in the + fragment blending operation. It is used in the following struct items + when creating a pipeline object: + + sg_pipeline_desc + .colors[i] + .blend + .op_rgb + .op_alpha + + The default value is SG_BLENDOP_ADD. +*/ +typedef enum sg_blend_op { + _SG_BLENDOP_DEFAULT, // value 0 reserved for default-init + SG_BLENDOP_ADD, + SG_BLENDOP_SUBTRACT, + SG_BLENDOP_REVERSE_SUBTRACT, + SG_BLENDOP_MIN, + SG_BLENDOP_MAX, + _SG_BLENDOP_NUM, + _SG_BLENDOP_FORCE_U32 = 0x7FFFFFFF +} sg_blend_op; + +/* + sg_color_mask + + Selects the active color channels when writing a fragment color to the + framebuffer. This is used in the members + sg_pipeline_desc.colors[i].write_mask when creating a pipeline object. + + The default colormask is SG_COLORMASK_RGBA (write all colors channels) + + NOTE: since the color mask value 0 is reserved for the default value + (SG_COLORMASK_RGBA), use SG_COLORMASK_NONE if all color channels + should be disabled. +*/ +typedef enum sg_color_mask { + _SG_COLORMASK_DEFAULT = 0, // value 0 reserved for default-init + SG_COLORMASK_NONE = 0x10, // special value for 'all channels disabled + SG_COLORMASK_R = 0x1, + SG_COLORMASK_G = 0x2, + SG_COLORMASK_RG = 0x3, + SG_COLORMASK_B = 0x4, + SG_COLORMASK_RB = 0x5, + SG_COLORMASK_GB = 0x6, + SG_COLORMASK_RGB = 0x7, + SG_COLORMASK_A = 0x8, + SG_COLORMASK_RA = 0x9, + SG_COLORMASK_GA = 0xA, + SG_COLORMASK_RGA = 0xB, + SG_COLORMASK_BA = 0xC, + SG_COLORMASK_RBA = 0xD, + SG_COLORMASK_GBA = 0xE, + SG_COLORMASK_RGBA = 0xF, + _SG_COLORMASK_FORCE_U32 = 0x7FFFFFFF +} sg_color_mask; + +/* + sg_load_action + + Defines the load action that should be performed at the start of a render pass: + + SG_LOADACTION_CLEAR: clear the render target + SG_LOADACTION_LOAD: load the previous content of the render target + SG_LOADACTION_DONTCARE: leave the render target in an undefined state + + This is used in the sg_pass_action structure. + + The default load action for all pass attachments is SG_LOADACTION_CLEAR, + with the values rgba = { 0.5f, 0.5f, 0.5f, 1.0f }, depth=1.0f and stencil=0. + + If you want to override the default behaviour, it is important to not + only set the clear color, but the 'action' field as well (as long as this + is _SG_LOADACTION_DEFAULT, the value fields will be ignored). +*/ +typedef enum sg_load_action { + _SG_LOADACTION_DEFAULT, + SG_LOADACTION_CLEAR, + SG_LOADACTION_LOAD, + SG_LOADACTION_DONTCARE, + _SG_LOADACTION_FORCE_U32 = 0x7FFFFFFF +} sg_load_action; + +/* + sg_store_action + + Defines the store action that should be performed at the end of a render pass: + + SG_STOREACTION_STORE: store the rendered content to the color attachment image + SG_STOREACTION_DONTCARE: allows the GPU to discard the rendered content +*/ +typedef enum sg_store_action { + _SG_STOREACTION_DEFAULT, + SG_STOREACTION_STORE, + SG_STOREACTION_DONTCARE, + _SG_STOREACTION_FORCE_U32 = 0x7FFFFFFF +} sg_store_action; + + +/* + sg_pass_action + + The sg_pass_action struct defines the actions to be performed + at the start and end of a render pass. + + - at the start of the pass: whether the render attachments should be cleared, + loaded with their previous content, or start in an undefined state + - for clear operations: the clear value (color, depth, or stencil values) + - at the end of the pass: whether the rendering result should be + stored back into the render attachment or discarded +*/ +typedef struct sg_color_attachment_action { + sg_load_action load_action; // default: SG_LOADACTION_CLEAR + sg_store_action store_action; // default: SG_STOREACTION_STORE + sg_color clear_value; // default: { 0.5f, 0.5f, 0.5f, 1.0f } +} sg_color_attachment_action; + +typedef struct sg_depth_attachment_action { + sg_load_action load_action; // default: SG_LOADACTION_CLEAR + sg_store_action store_action; // default: SG_STOREACTION_DONTCARE + float clear_value; // default: 1.0 +} sg_depth_attachment_action; + +typedef struct sg_stencil_attachment_action { + sg_load_action load_action; // default: SG_LOADACTION_CLEAR + sg_store_action store_action; // default: SG_STOREACTION_DONTCARE + uint8_t clear_value; // default: 0 +} sg_stencil_attachment_action; + +typedef struct sg_pass_action { + sg_color_attachment_action colors[SG_MAX_COLOR_ATTACHMENTS]; + sg_depth_attachment_action depth; + sg_stencil_attachment_action stencil; +} sg_pass_action; + +/* + sg_swapchain + + Used in sg_begin_pass() to provide details about an external swapchain + (pixel formats, sample count and backend-API specific render surface objects). + + The following information must be provided: + + - the width and height of the swapchain surfaces in number of pixels, + - the pixel format of the render- and optional msaa-resolve-surface + - the pixel format of the optional depth- or depth-stencil-surface + - the MSAA sample count for the render and depth-stencil surface + + If the pixel formats and MSAA sample counts are left zero-initialized, + their defaults are taken from the sg_environment struct provided in the + sg_setup() call. + + The width and height *must* be > 0. + + Additionally the following backend API specific objects must be passed in + as 'type erased' void pointers: + + GL: + - on all GL backends, a GL framebuffer object must be provided. This + can be zero for the default framebuffer. + + D3D11: + - an ID3D11RenderTargetView for the rendering surface, without + MSAA rendering this surface will also be displayed + - an optional ID3D11DepthStencilView for the depth- or depth/stencil + buffer surface + - when MSAA rendering is used, another ID3D11RenderTargetView + which serves as MSAA resolve target and will be displayed + + WebGPU (same as D3D11, except different types) + - a WGPUTextureView for the rendering surface, without + MSAA rendering this surface will also be displayed + - an optional WGPUTextureView for the depth- or depth/stencil + buffer surface + - when MSAA rendering is used, another WGPUTextureView + which serves as MSAA resolve target and will be displayed + + Metal (NOTE that the roles of provided surfaces is slightly different + than on D3D11 or WebGPU in case of MSAA vs non-MSAA rendering): + + - A current CAMetalDrawable (NOT an MTLDrawable!) which will be presented. + This will either be rendered to directly (if no MSAA is used), or serve + as MSAA-resolve target. + - an optional MTLTexture for the depth- or depth-stencil buffer + - an optional multisampled MTLTexture which serves as intermediate + rendering surface which will then be resolved into the + CAMetalDrawable. + + NOTE that for Metal you must use an ObjC __bridge cast to + properly tunnel the ObjC object id through a C void*, e.g.: + + swapchain.metal.current_drawable = (__bridge const void*) [mtkView currentDrawable]; + + On all other backends you shouldn't need to mess with the reference count. + + It's a good practice to write a helper function which returns an initialized + sg_swapchain struct, which can then be plugged directly into + sg_pass.swapchain. Look at the function sglue_swapchain() in the sokol_glue.h + as an example. +*/ +typedef struct sg_metal_swapchain { + const void* current_drawable; // CAMetalDrawable (NOT MTLDrawable!!!) + const void* depth_stencil_texture; // MTLTexture + const void* msaa_color_texture; // MTLTexture +} sg_metal_swapchain; + +typedef struct sg_d3d11_swapchain { + const void* render_view; // ID3D11RenderTargetView + const void* resolve_view; // ID3D11RenderTargetView + const void* depth_stencil_view; // ID3D11DepthStencilView +} sg_d3d11_swapchain; + +typedef struct sg_wgpu_swapchain { + const void* render_view; // WGPUTextureView + const void* resolve_view; // WGPUTextureView + const void* depth_stencil_view; // WGPUTextureView +} sg_wgpu_swapchain; + +typedef struct sg_gl_swapchain { + uint32_t framebuffer; // GL framebuffer object +} sg_gl_swapchain; + +typedef struct sg_swapchain { + int width; + int height; + int sample_count; + sg_pixel_format color_format; + sg_pixel_format depth_format; + sg_metal_swapchain metal; + sg_d3d11_swapchain d3d11; + sg_wgpu_swapchain wgpu; + sg_gl_swapchain gl; +} sg_swapchain; + +/* + sg_attachments + + Used in sg_pass to provide render pass attachment views. Each + type of pass attachment has it corresponding view type: + + sg_attachments.colors[]: + populate with color-attachment views, e.g.: + + sg_make_view(&(sg_view_desc){ + .color_attachment = { ... }, + }); + + sg_attachments.resolves[]: + populate with resolve-attachment views, e.g.: + + sg_make_view(&(sg_view_desc){ + .resolve_attachment = { ... }, + }); + + sg_attachments.depth_stencil: + populate with depth-stencil-attachment views, e.g.: + + sg_make_view(&(sg_view_desc){ + .depth_stencil_attachment = { ... }, + }); +*/ +typedef struct sg_attachments { + sg_view colors[SG_MAX_COLOR_ATTACHMENTS]; + sg_view resolves[SG_MAX_COLOR_ATTACHMENTS]; + sg_view depth_stencil; +} sg_attachments; + +/* + sg_pass + + The sg_pass structure is passed as argument into the sg_begin_pass() + function. + + For a swapchain render pass, provide an sg_pass_action and sg_swapchain + struct (for instance via the sglue_swapchain() helper function from + sokol_glue.h): + + sg_begin_pass(&(sg_pass){ + .action = { ... }, + .swapchain = sglue_swapchain(), + }); + + For an offscreen render pass, provide an sg_pass_action struct with + attachment view objects: + + sg_begin_pass(&(sg_pass){ + .action = { ... }, + .attachments = { + .colors = { ... }, + .resolves = { ... }, + .depth_stencil = ..., + }, + }); + + You can also omit the .action object to get default pass action behaviour + (clear to color=grey, depth=1 and stencil=0). + + For a compute pass, just set the sg_pass.compute boolean to true: + + sg_begin_pass(&(sg_pass){ .compute = true }); +*/ +typedef struct sg_pass { + uint32_t _start_canary; + bool compute; + sg_pass_action action; + sg_attachments attachments; + sg_swapchain swapchain; + const char* label; + uint32_t _end_canary; +} sg_pass; + +/* + sg_bindings + + The sg_bindings structure defines the resource bindings for + the next draw call. + + To update the resource bindings, call sg_apply_bindings() with + a pointer to a populated sg_bindings struct. Note that + sg_apply_bindings() must be called after sg_apply_pipeline() + and that bindings are not preserved across sg_apply_pipeline() + calls, even when the new pipeline uses the same 'bindings layout'. + + A resource binding struct contains: + + - 1..N vertex buffers + - 1..N vertex buffer offsets + - 0..1 index buffer + - 0..1 index buffer offset + - 0..N resource views (texture-, storage-image, storage-buffer-views) + - 0..N samplers + + Where 'N' is defined in the following constants: + + - SG_MAX_VERTEXBUFFER_BINDSLOTS + - SG_MAX_VIEW_BINDSLOTS + - SG_MAX_SAMPLER_BINDSLOTS + + Note that inside compute passes vertex- and index-buffer-bindings are + disallowed. + + When using sokol-shdc for shader authoring, the `layout(binding=N)` + for texture-, storage-image- and storage-buffer-bindings directly + maps to the views-array index, for instance the following vertex- + and fragment-shader interface for sokol-shdc: + + @vs vs + layout(binding=0) uniform vs_params { ... }; + layout(binding=0) readonly buffer ssbo { ... }; + layout(binding=1) uniform texture2D vs_tex; + layout(binding=0) uniform sampler vs_smp; + ... + @end + + @fs fs + layout(binding=1) uniform fs_params { ... }; + layout(binding=2) uniform texture2D fs_tex; + layout(binding=1) uniform sampler fs_smp; + ... + @end + + ...would map to the following sg_bindings struct: + + const sg_bindings bnd = { + .vertex_buffers[0] = ..., + .views[0] = ssbo_view, + .views[1] = vs_tex_view, + .views[2] = fs_tex_view, + .samplers[0] = vs_smp, + .samplers[1] = fs_smp, + }; + + ...alternatively you can use code-generated slot indices: + + const sg_bindings bnd = { + .vertex_buffers[0] = ..., + .views[VIEW_ssbo] = ssbo_view, + .views[VIEW_vs_tex] = vs_tex_view, + .views[VIEW_fs_tex] = fs_tex_view, + .samplers[SMP_vs_smp] = vs_smp, + .samplers[SMP_fs_smp] = fs_smp, + }; + + Resource bindslots for a specific shader/pipeline may have gaps, and an + sg_bindings struct may have populated bind slots which are not used by a + specific shader. This allows to use the same sg_bindings struct across + different shader variants. + + When not using sokol-shdc, the bindslot indices in the sg_bindings + struct need to match the per-binding reflection info slot indices + in the sg_shader_desc struct (for details about that see the + sg_shader_desc struct documentation). + + The optional buffer offsets can be used to put different unrelated + chunks of vertex- and/or index-data into the same buffer objects. +*/ +typedef struct sg_bindings { + uint32_t _start_canary; + sg_buffer vertex_buffers[SG_MAX_VERTEXBUFFER_BINDSLOTS]; + int vertex_buffer_offsets[SG_MAX_VERTEXBUFFER_BINDSLOTS]; + sg_buffer index_buffer; + int index_buffer_offset; + sg_view views[SG_MAX_VIEW_BINDSLOTS]; + sg_sampler samplers[SG_MAX_SAMPLER_BINDSLOTS]; + uint32_t _end_canary; +} sg_bindings; + +/* + sg_buffer_usage + + Describes how a buffer object is going to be used: + + .vertex_buffer (default: true) + the buffer will be bound as vertex buffer via sg_bindings.vertex_buffers[] + .index_buffer (default: false) + the buffer will be bound as index buffer via sg_bindings.index_buffer + .storage_buffer (default: false) + the buffer will be bound as storage buffer via storage-buffer-view + in sg_bindings.views[] + .immutable (default: true) + the buffer content will never be updated from the CPU side (but + may be written to by a compute shader) + .dynamic_update (default: false) + the buffer content will be infrequently updated from the CPU side + .stream_upate (default: false) + the buffer content will be updated each frame from the CPU side +*/ +typedef struct sg_buffer_usage { + bool vertex_buffer; + bool index_buffer; + bool storage_buffer; + bool immutable; + bool dynamic_update; + bool stream_update; +} sg_buffer_usage; + +/* + sg_buffer_desc + + Creation parameters for sg_buffer objects, used in the sg_make_buffer() call. + + The default configuration is: + + .size: 0 (*must* be >0 for buffers without data) + .usage { .vertex_buffer = true, .immutable = true } + .data.ptr 0 (*must* be valid for immutable buffers without storage buffer usage) + .data.size 0 (*must* be > 0 for immutable buffers without storage buffer usage) + .label 0 (optional string label) + + For immutable buffers which are initialized with initial data, + keep the .size item zero-initialized, and set the size together with the + pointer to the initial data in the .data item. + + For immutable or mutable buffers without initial data, keep the .data item + zero-initialized, and set the buffer size in the .size item instead. + + You can also set both size values, but currently both size values must + be identical (this may change in the future when the dynamic resource + management may become more flexible). + + NOTE: Immutable buffers without storage-buffer-usage *must* be created + with initial content, this restriction doesn't apply to storage buffer usage, + because storage buffers may also get their initial content by running + a compute shader on them. + + NOTE: Buffers without initial data will have undefined content, e.g. + do *not* expect the buffer to be zero-initialized! + + ADVANCED TOPIC: Injecting native 3D-API buffers: + + The following struct members allow to inject your own GL, Metal + or D3D11 buffers into sokol_gfx: + + .gl_buffers[SG_NUM_INFLIGHT_FRAMES] + .mtl_buffers[SG_NUM_INFLIGHT_FRAMES] + .d3d11_buffer + + You must still provide all other struct items except the .data item, and + these must match the creation parameters of the native buffers you provide. + For sg_buffer_desc.usage.immutable buffers, only provide a single native + 3D-API buffer, otherwise you need to provide SG_NUM_INFLIGHT_FRAMES buffers + (only for GL and Metal, not D3D11). Providing multiple buffers for GL and + Metal is necessary because sokol_gfx will rotate through them when calling + sg_update_buffer() to prevent lock-stalls. + + Note that it is expected that immutable injected buffer have already been + initialized with content, and the .content member must be 0! + + Also you need to call sg_reset_state_cache() after calling native 3D-API + functions, and before calling any sokol_gfx function. +*/ +typedef struct sg_buffer_desc { + uint32_t _start_canary; + size_t size; + sg_buffer_usage usage; + sg_range data; + const char* label; + // optionally inject backend-specific resources + uint32_t gl_buffers[SG_NUM_INFLIGHT_FRAMES]; + const void* mtl_buffers[SG_NUM_INFLIGHT_FRAMES]; + const void* d3d11_buffer; + const void* wgpu_buffer; + uint32_t _end_canary; +} sg_buffer_desc; + +/* + sg_image_usage + + Describes the intended usage of an image object: + + .storage_image (default: false) + the image can be used as parent resource of a storage-image-view, + which allows compute shaders to write to the image in a compute + pass (for read-only access in compute shaders bind the image + via a texture view instead + .color_attachment (default: false) + the image can be used as parent resource of a color-attachment-view, + which is then passed into sg_begin_pass via sg_pass.attachments.colors[] + so that fragment shaders can render into the image + .resolve_attachment (default: false) + the image can be used as parent resource of a resolve-attachment-view, + which is then passed into sg_begin_pass via sg_pass.attachments.resolves[] + as target for an MSAA-resolve operation in sg_end_pass() + .depth_stencil_attachment (default: false) + the image can be used as parent resource of a depth-stencil-attachmnet-view + which is then passes into sg_begin_pass via sg_pass.attachments.depth_stencil + as depth-stencil-buffer + .immutable (default: true) + the image content cannot be updated from the CPU side + (but may be updated by the GPU in a render- or compute-pass) + .dynamic_update (default: false) + the image content is updated infrequently by the CPU + .stream_update (default: false) + the image content is updated each frame by the CPU via + + Note that creating a texture view from the image to be used for + texture-sampling in vertex-, fragment- or compute-shaders + is always implicitly allowed. +*/ +typedef struct sg_image_usage { + bool storage_image; + bool color_attachment; + bool resolve_attachment; + bool depth_stencil_attachment; + bool immutable; + bool dynamic_update; + bool stream_update; +} sg_image_usage; + +/* + sg_view_type + + Allows to query the type of a view object via the function sg_query_view_type() +*/ +typedef enum sg_view_type { + SG_VIEWTYPE_INVALID, + SG_VIEWTYPE_STORAGEBUFFER, + SG_VIEWTYPE_STORAGEIMAGE, + SG_VIEWTYPE_TEXTURE, + SG_VIEWTYPE_COLORATTACHMENT, + SG_VIEWTYPE_RESOLVEATTACHMENT, + SG_VIEWTYPE_DEPTHSTENCILATTACHMENT, + _SG_VIEWTYPE_FORCE_U32 = 0x7FFFFFFF +} sg_view_type; + +/* + sg_image_data + + Defines the content of an image through an array of sg_range structs, each + range pointing to the pixel data for one mip-level. For array-, cubemap- and + 3D-images each mip-level contains all slice-surfaces for that mip-level in a + single tightly packed memory block. + + The size of a single surface in a mip-level for a regular 2D texture + can be computed via: + + sg_query_surface_pitch(pixel_format, mip_width, mip_height, 1); + + For array- and 3d-images the size of a single miplevel is: + + num_slices * sg_query_surface_pitch(pixel_format, mip_width, mip_height, 1); + + For cubemap-images the size of a single mip-level is: + + 6 * sg_query_surface_pitch(pixel_format, mip_width, mip_height, 1); + + The order of cubemap-faces is in a mip-level data chunk is: + + [0] => +X + [1] => -X + [2] => +Y + [3] => -Y + [4] => +Z + [5] => -Z +*/ +typedef struct sg_image_data { + sg_range mip_levels[SG_MAX_MIPMAPS]; +} sg_image_data; + +/* + sg_image_desc + + Creation parameters for sg_image objects, used in the sg_make_image() call. + + The default configuration is: + + .type SG_IMAGETYPE_2D + .usage .immutable = true + .width 0 (must be set to >0) + .height 0 (must be set to >0) + .num_slices 1 (3D textures: depth; array textures: number of layers) + .num_mipmaps 1 + .pixel_format SG_PIXELFORMAT_RGBA8 for textures, or sg_desc.environment.defaults.color_format for render targets + .sample_count 1 for textures, or sg_desc.environment.defaults.sample_count for render targets + .data an sg_image_data struct to define the initial content + .label 0 (optional string label for trace hooks) + + Q: Why is the default sample_count for render targets identical with the + "default sample count" from sg_desc.environment.defaults.sample_count? + + A: So that it matches the default sample count in pipeline objects. Even + though it is a bit strange/confusing that offscreen render targets by default + get the same sample count as 'default swapchains', but it's better that + an offscreen render target created with default parameters matches + a pipeline object created with default parameters. + + NOTE: + + Regular images used as texture binding with usage.immutable must be fully + initialized by providing a valid .data member which points to initialization + data. + + Images with usage.*_attachment or usage.storage_image must + *not* be created with initial content. Be aware that the initial + content of pass attachment and storage images is undefined + (not guaranteed to be zeroed). + + ADVANCED TOPIC: Injecting native 3D-API textures: + + The following struct members allow to inject your own GL, Metal or D3D11 + textures into sokol_gfx: + + .gl_textures[SG_NUM_INFLIGHT_FRAMES] + .mtl_textures[SG_NUM_INFLIGHT_FRAMES] + .d3d11_texture + .wgpu_texture + + For GL, you can also specify the texture target or leave it empty to use + the default texture target for the image type (GL_TEXTURE_2D for + SG_IMAGETYPE_2D etc) + + The same rules apply as for injecting native buffers (see sg_buffer_desc + documentation for more details). +*/ +typedef struct sg_image_desc { + uint32_t _start_canary; + sg_image_type type; + sg_image_usage usage; + int width; + int height; + int num_slices; + int num_mipmaps; + sg_pixel_format pixel_format; + int sample_count; + sg_image_data data; + const char* label; + // optionally inject backend-specific resources + uint32_t gl_textures[SG_NUM_INFLIGHT_FRAMES]; + uint32_t gl_texture_target; + const void* mtl_textures[SG_NUM_INFLIGHT_FRAMES]; + const void* d3d11_texture; + const void* wgpu_texture; + uint32_t _end_canary; +} sg_image_desc; + +/* + sg_sampler_desc + + Creation parameters for sg_sampler objects, used in the sg_make_sampler() call + + .min_filter: SG_FILTER_NEAREST + .mag_filter: SG_FILTER_NEAREST + .mipmap_filter SG_FILTER_NEAREST + .wrap_u: SG_WRAP_REPEAT + .wrap_v: SG_WRAP_REPEAT + .wrap_w: SG_WRAP_REPEAT (only SG_IMAGETYPE_3D) + .min_lod 0.0f + .max_lod FLT_MAX + .border_color SG_BORDERCOLOR_OPAQUE_BLACK + .compare SG_COMPAREFUNC_NEVER + .max_anisotropy 1 (must be 1..16) +*/ +typedef struct sg_sampler_desc { + uint32_t _start_canary; + sg_filter min_filter; + sg_filter mag_filter; + sg_filter mipmap_filter; + sg_wrap wrap_u; + sg_wrap wrap_v; + sg_wrap wrap_w; + float min_lod; + float max_lod; + sg_border_color border_color; + sg_compare_func compare; + uint32_t max_anisotropy; + const char* label; + // optionally inject backend-specific resources + uint32_t gl_sampler; + const void* mtl_sampler; + const void* d3d11_sampler; + const void* wgpu_sampler; + uint32_t _end_canary; +} sg_sampler_desc; + +/* + sg_shader_desc + + Used as parameter of sg_make_shader() to create a shader object which + communicates shader source or bytecode and shader interface + reflection information to sokol-gfx. + + If you use sokol-shdc you can ignore the following information since + the sg_shader_desc struct will be code-generated. + + Otherwise you need to provide the following information to the + sg_make_shader() call: + + - a vertex- and fragment-shader function: + - the shader source or bytecode + - an optional entry point name + - for D3D11: an optional compile target when source code is provided + (the defaults are "vs_4_0" and "ps_4_0") + + - ...or alternatively, a compute function: + - the shader source or bytecode + - an optional entry point name + - for D3D11: an optional compile target when source code is provided + (the default is "cs_5_0") + + - vertex attributes required by some backends (not for compute shaders): + - the vertex attribute base type (undefined, float, signed int, unsigned int), + this information is only used in the validation layer to check that the + pipeline object vertex formats are compatible with the input vertex attribute + type used in the vertex shader. NOTE that the default base type + 'undefined' skips the validation layer check. + - for the GL backend: optional vertex attribute names used for name lookup + - for the D3D11 backend: semantic names and indices + + - only for compute shaders on the Metal backend: + - the workgroup size aka 'threads per thread-group' + + In other 3D APIs this is declared in the shader code: + - GLSL: `layout(local_size_x=x, local_size_y=y, local_size_y=z) in;` + - HLSL: `[numthreads(x, y, z)]` + - WGSL: `@workgroup_size(x, y, z)` + ...but in Metal the workgroup size is declared on the CPU side + + - reflection information for each uniform block binding used by the shader: + - the shader stage the uniform block appears in (SG_SHADERSTAGE_*) + - the size in bytes of the uniform block + - backend-specific bindslots: + - HLSL: the constant buffer register `register(b0..7)` + - MSL: the buffer attribute `[[buffer(0..7)]]` + - WGSL: the binding in `@group(0) @binding(0..15)` + - GLSL only: a description of the uniform block interior + - the memory layout standard (SG_UNIFORMLAYOUT_*) + - for each member in the uniform block: + - the member type (SG_UNIFORM_*) + - if the member is an array, the array count + - the member name + + - reflection information for each texture-, storage-buffer and + storage-image bindings by the shader, each with an associated + view type: + - texture bindings => texture views + - storage-buffer bindings => storage-buffer views + - storage-image bindings => storage-image views + + - texture bindings must provide the following information: + - the shader stage the texture binding appears in (SG_SHADERSTAGE_*) + - the image type (SG_IMAGETYPE_*) + - the image-sample type (SG_IMAGESAMPLETYPE_*) + - whether the texture is multisampled + - backend specific bindslots: + - HLSL: the texture register `register(t0..31)` + - MSL: the texture attribute `[[texture(0..31)]]` + - WGSL: the binding in `@group(1) @binding(0..127)` + + - storage-buffer bindings must provide the following information: + - the shader stage the storage buffer appears in (SG_SHADERSTAGE_*) + - whether the storage buffer is readonly + - backend specific bindslots: + - HLSL: + - for storage buffer bindings: `register(t0..31)` + - for read/write storage buffer bindings: `register(u0..31)` + - MSL: the buffer attribute `[[buffer(8..23)]]` + - WGSL: the binding in `@group(1) @binding(0..127)` + - GL: the binding in `layout(binding=0..sg_limits.max_storage_buffer_bindings_per_stage)` + + - storage-image bindings must provide the following information: + - the shader stage (*must* be SG_SHADERSTAGE_COMPUTE) + - whether the storage image is writeonly or readwrite (for readonly + access use a regular texture binding instead) + - the image type expected by the shader (SG_IMAGETYPE_*) + - the access pixel format expected by the shader (SG_PIXELFORMAT_*), + note that only a subset of pixel formats is allowed for storage image + bindings + - backend specific bindslots: + - HLSL: the UAV register `register(u0..31)` + - MSL: the texture attribute `[[texture(0..31)]]` + - WGSL: the binding in `@group(1) @binding(0..127)` + - GLSL: the binding in `layout(binding=0..sg_imits.max_storage_buffer_bindings_per_stage, [access_format])` + + - reflection information for each sampler used by the shader: + - the shader stage the sampler appears in (SG_SHADERSTAGE_*) + - the sampler type (SG_SAMPLERTYPE_*) + - backend specific bindslots: + - HLSL: the sampler register `register(s0..11)` + - MSL: the sampler attribute `[[sampler(0..11)]]` + - WGSL: the binding in `@group(0) @binding(0..127)` + + - reflection information for each texture-sampler pair used by + the shader: + - the shader stage (SG_SHADERSTAGE_*) + - the texture's array index in the sg_shader_desc.views[] array + - the sampler's array index in the sg_shader_desc.samplers[] array + - GLSL only: the name of the combined image-sampler object + + The number and order of items in the sg_shader_desc.attrs[] + array corresponds to the items in sg_pipeline_desc.layout.attrs. + + - sg_shader_desc.attrs[N] => sg_pipeline_desc.layout.attrs[N] + + NOTE that vertex attribute indices currently cannot have gaps. + + The items index in the sg_shader_desc.uniform_blocks[] array corresponds + to the ub_slot arg in sg_apply_uniforms(): + + - sg_shader_desc.uniform_blocks[N] => sg_apply_uniforms(N, ...) + + The items in the sg_shader_desc.views[] array directly map to + the views in the sg_bindings.views[] array! + + For all GL backends, shader source-code must be provided. For D3D11 and Metal, + either shader source-code or byte-code can be provided. + + NOTE that the uniform-block, view and sampler arrays may have gaps. This + allows to use the same sg_bindings struct for different but related + shader variations. + + For D3D11, if source code is provided, the d3dcompiler_47.dll will be loaded + on demand. If this fails, shader creation will fail. When compiling HLSL + source code, you can provide an optional target string via + sg_shader_stage_desc.d3d11_target, the default target is "vs_4_0" for the + vertex shader stage and "ps_4_0" for the pixel shader stage. + You may optionally provide the file path to enable the default #include handler + behavior when compiling source code. +*/ +typedef enum sg_shader_stage { + SG_SHADERSTAGE_NONE, + SG_SHADERSTAGE_VERTEX, + SG_SHADERSTAGE_FRAGMENT, + SG_SHADERSTAGE_COMPUTE, + _SG_SHADERSTAGE_FORCE_U32 = 0x7FFFFFFF, +} sg_shader_stage; + +typedef struct sg_shader_function { + const char* source; + sg_range bytecode; + const char* entry; + const char* d3d11_target; // default: "vs_4_0" or "ps_4_0" + const char* d3d11_filepath; +} sg_shader_function; + +typedef enum sg_shader_attr_base_type { + SG_SHADERATTRBASETYPE_UNDEFINED, + SG_SHADERATTRBASETYPE_FLOAT, + SG_SHADERATTRBASETYPE_SINT, + SG_SHADERATTRBASETYPE_UINT, + _SG_SHADERATTRBASETYPE_FORCE_U32 = 0x7FFFFFFF, +} sg_shader_attr_base_type; + +typedef struct sg_shader_vertex_attr { + sg_shader_attr_base_type base_type; // default: UNDEFINED (disables validation) + const char* glsl_name; // [optional] GLSL attribute name + const char* hlsl_sem_name; // HLSL semantic name + uint8_t hlsl_sem_index; // HLSL semantic index +} sg_shader_vertex_attr; + +typedef struct sg_glsl_shader_uniform { + sg_uniform_type type; + uint16_t array_count; // 0 or 1 for scalars, >1 for arrays + const char* glsl_name; // glsl name binding is required on GL 4.1 and WebGL2 +} sg_glsl_shader_uniform; + +typedef struct sg_shader_uniform_block { + sg_shader_stage stage; + uint32_t size; + uint8_t hlsl_register_b_n; // HLSL register(bn) + uint8_t msl_buffer_n; // MSL [[buffer(n)]] + uint8_t wgsl_group0_binding_n; // WGSL @group(0) @binding(n) + sg_uniform_layout layout; + sg_glsl_shader_uniform glsl_uniforms[SG_MAX_UNIFORMBLOCK_MEMBERS]; +} sg_shader_uniform_block; + +typedef struct sg_shader_texture_view { + sg_shader_stage stage; + sg_image_type image_type; + sg_image_sample_type sample_type; + bool multisampled; + uint8_t hlsl_register_t_n; // HLSL register(tn) bind slot + uint8_t msl_texture_n; // MSL [[texture(n)]] bind slot + uint8_t wgsl_group1_binding_n; // WGSL @group(1) @binding(n) bind slot +} sg_shader_texture_view; + +typedef struct sg_shader_storage_buffer_view { + sg_shader_stage stage; + bool readonly; + uint8_t hlsl_register_t_n; // HLSL register(tn) bind slot (for readonly access) + uint8_t hlsl_register_u_n; // HLSL register(un) bind slot (for read/write access) + uint8_t msl_buffer_n; // MSL [[buffer(n)]] bind slot + uint8_t wgsl_group1_binding_n; // WGSL @group(1) @binding(n) bind slot + uint8_t glsl_binding_n; // GLSL layout(binding=n) +} sg_shader_storage_buffer_view; + +typedef struct sg_shader_storage_image_view { + sg_shader_stage stage; + sg_image_type image_type; + sg_pixel_format access_format; // shader-access pixel format + bool writeonly; // false means read/write access + uint8_t hlsl_register_u_n; // HLSL register(un) bind slot + uint8_t msl_texture_n; // MSL [[texture(n)]] bind slot + uint8_t wgsl_group1_binding_n; // WGSL @group(2) @binding(n) bind slot + uint8_t glsl_binding_n; // GLSL layout(binding=n) +} sg_shader_storage_image_view; + +typedef struct sg_shader_view { + sg_shader_texture_view texture; + sg_shader_storage_buffer_view storage_buffer; + sg_shader_storage_image_view storage_image; +} sg_shader_view; + +typedef struct sg_shader_sampler { + sg_shader_stage stage; + sg_sampler_type sampler_type; + uint8_t hlsl_register_s_n; // HLSL register(sn) bind slot + uint8_t msl_sampler_n; // MSL [[sampler(n)]] bind slot + uint8_t wgsl_group1_binding_n; // WGSL @group(1) @binding(n) bind slot +} sg_shader_sampler; + +typedef struct sg_shader_texture_sampler_pair { + sg_shader_stage stage; + uint8_t view_slot; // must be SG_VIEWTYPE_TEXTURE + uint8_t sampler_slot; + const char* glsl_name; // glsl name binding required because of GL 4.1 and WebGL2 +} sg_shader_texture_sampler_pair; + +typedef struct sg_mtl_shader_threads_per_threadgroup { + int x, y, z; +} sg_mtl_shader_threads_per_threadgroup; + +typedef struct sg_shader_desc { + uint32_t _start_canary; + sg_shader_function vertex_func; + sg_shader_function fragment_func; + sg_shader_function compute_func; + sg_shader_vertex_attr attrs[SG_MAX_VERTEX_ATTRIBUTES]; + sg_shader_uniform_block uniform_blocks[SG_MAX_UNIFORMBLOCK_BINDSLOTS]; + sg_shader_view views[SG_MAX_VIEW_BINDSLOTS]; + sg_shader_sampler samplers[SG_MAX_SAMPLER_BINDSLOTS]; + sg_shader_texture_sampler_pair texture_sampler_pairs[SG_MAX_TEXTURE_SAMPLER_PAIRS]; + sg_mtl_shader_threads_per_threadgroup mtl_threads_per_threadgroup; + const char* label; + uint32_t _end_canary; +} sg_shader_desc; + +/* + sg_pipeline_desc + + The sg_pipeline_desc struct defines all creation parameters for an + sg_pipeline object, used as argument to the sg_make_pipeline() function: + + Pipeline objects come in two flavours: + + - render pipelines for use in render passes + - compute pipelines for use in compute passes + + A compute pipeline only requires a compute shader object but no + 'render state', while a render pipeline requires a vertex/fragment shader + object and additional render state declarations: + + - the vertex layout for all input vertex buffers + - a shader object + - the 3D primitive type (points, lines, triangles, ...) + - the index type (none, 16- or 32-bit) + - all the fixed-function-pipeline state (depth-, stencil-, blend-state, etc...) + + If the vertex data has no gaps between vertex components, you can omit + the .layout.buffers[].stride and layout.attrs[].offset items (leave them + default-initialized to 0), sokol-gfx will then compute the offsets and + strides from the vertex component formats (.layout.attrs[].format). + Please note that ALL vertex attribute offsets must be 0 in order for the + automatic offset computation to kick in. + + Note that if you use vertex-pulling from storage buffers instead of + fixed-function vertex input you can simply omit the entire nested .layout + struct. + + The default configuration is as follows: + + .compute: false (must be set to true for a compute pipeline) + .shader: 0 (must be initialized with a valid sg_shader id!) + .layout: + .buffers[]: vertex buffer layouts + .stride: 0 (if no stride is given it will be computed) + .step_func SG_VERTEXSTEP_PER_VERTEX + .step_rate 1 + .attrs[]: vertex attribute declarations + .buffer_index 0 the vertex buffer bind slot + .offset 0 (offsets can be omitted if the vertex layout has no gaps) + .format SG_VERTEXFORMAT_INVALID (must be initialized!) + .depth: + .pixel_format: sg_desc.context.depth_format + .compare: SG_COMPAREFUNC_ALWAYS + .write_enabled: false + .bias: 0.0f + .bias_slope_scale: 0.0f + .bias_clamp: 0.0f + .stencil: + .enabled: false + .front/back: + .compare: SG_COMPAREFUNC_ALWAYS + .fail_op: SG_STENCILOP_KEEP + .depth_fail_op: SG_STENCILOP_KEEP + .pass_op: SG_STENCILOP_KEEP + .read_mask: 0 + .write_mask: 0 + .ref: 0 + .color_count 1 + .colors[0..color_count] + .pixel_format sg_desc.context.color_format + .write_mask: SG_COLORMASK_RGBA + .blend: + .enabled: false + .src_factor_rgb: SG_BLENDFACTOR_ONE + .dst_factor_rgb: SG_BLENDFACTOR_ZERO + .op_rgb: SG_BLENDOP_ADD + .src_factor_alpha: SG_BLENDFACTOR_ONE + .dst_factor_alpha: SG_BLENDFACTOR_ZERO + .op_alpha: SG_BLENDOP_ADD + .primitive_type: SG_PRIMITIVETYPE_TRIANGLES + .index_type: SG_INDEXTYPE_NONE + .cull_mode: SG_CULLMODE_NONE + .face_winding: SG_FACEWINDING_CW + .sample_count: sg_desc.context.sample_count + .blend_color: (sg_color) { 0.0f, 0.0f, 0.0f, 0.0f } + .alpha_to_coverage_enabled: false + .label 0 (optional string label for trace hooks) +*/ +typedef struct sg_vertex_buffer_layout_state { + int stride; + sg_vertex_step step_func; + int step_rate; +} sg_vertex_buffer_layout_state; + +typedef struct sg_vertex_attr_state { + int buffer_index; + int offset; + sg_vertex_format format; +} sg_vertex_attr_state; + +typedef struct sg_vertex_layout_state { + sg_vertex_buffer_layout_state buffers[SG_MAX_VERTEXBUFFER_BINDSLOTS]; + sg_vertex_attr_state attrs[SG_MAX_VERTEX_ATTRIBUTES]; +} sg_vertex_layout_state; + +typedef struct sg_stencil_face_state { + sg_compare_func compare; + sg_stencil_op fail_op; + sg_stencil_op depth_fail_op; + sg_stencil_op pass_op; +} sg_stencil_face_state; + +typedef struct sg_stencil_state { + bool enabled; + sg_stencil_face_state front; + sg_stencil_face_state back; + uint8_t read_mask; + uint8_t write_mask; + uint8_t ref; +} sg_stencil_state; + +typedef struct sg_depth_state { + sg_pixel_format pixel_format; + sg_compare_func compare; + bool write_enabled; + float bias; + float bias_slope_scale; + float bias_clamp; +} sg_depth_state; + +typedef struct sg_blend_state { + bool enabled; + sg_blend_factor src_factor_rgb; + sg_blend_factor dst_factor_rgb; + sg_blend_op op_rgb; + sg_blend_factor src_factor_alpha; + sg_blend_factor dst_factor_alpha; + sg_blend_op op_alpha; +} sg_blend_state; + +typedef struct sg_color_target_state { + sg_pixel_format pixel_format; + sg_color_mask write_mask; + sg_blend_state blend; +} sg_color_target_state; + +typedef struct sg_pipeline_desc { + uint32_t _start_canary; + bool compute; + sg_shader shader; + sg_vertex_layout_state layout; + sg_depth_state depth; + sg_stencil_state stencil; + int color_count; + sg_color_target_state colors[SG_MAX_COLOR_ATTACHMENTS]; + sg_primitive_type primitive_type; + sg_index_type index_type; + sg_cull_mode cull_mode; + sg_face_winding face_winding; + int sample_count; + sg_color blend_color; + bool alpha_to_coverage_enabled; + const char* label; + uint32_t _end_canary; +} sg_pipeline_desc; + +/* + sg_view_desc + + Creation params for sg_view objects, passed into sg_make_view() calls. + + View objects are passed into sg_apply_bindings() (for texture-, storage-buffer- + and storage-image views), and sg_begin_pass() (for color-, resolve- + and depth-stencil-attachment views). + + The view type is determined by initializing one of the sub-structs of + sg_view_desc: + + .texture a texture-view object will be created + .image the sg_image parent resource + .mip_levels optional mip-level range, keep zero-initialized for the + entire mipmap chain + .base the first mip level + .count number of mip levels, keeping this zero-initialized means + 'all remaining mip levels' + .slices optional slice range, keep zero-initialized to include + all slices + .base the first slice + .count number of slices, keeping this zero-initializied means 'all remaining slices' + + .storage_buffer a storage-buffer-view object will be created + .buffer the sg_buffer parent resource, must have been created + with `sg_buffer_desc.usage.storage_buffer = true` + .offset optional 256-byte aligned byte-offset into the buffer + + .storage_image a storage-image-view object will be created + .image the sg_image parent resource, must have been created + with `sg_image_desc.usage.storage_image = true` + .mip_level selects the mip-level for the compute shader to write + .slice selects the slice for the compute shader to write + + .color_attachment a color-attachment-view object will be created + .image the sg_image parent resource, must have been created + with `sg_image_desc.usage.color_attachment = true` + .mip_level selects the mip-level to render into + .slice selects the slice to render into + + .resolve_attachment a resolve-attachment-view object will be created + .image the sg_image parent resource, must have been created + with `sg_image_desc.usage.resolve_attachment = true` + .mip_level selects the mip-level to msaa-resolve into + .slice selects the slice to msaa-resolve into + + .depth_stencil_attachment a depth-stencil-attachment-view object will be created + .image the sg_image parent resource, must have been created + with `sg_image_desc.usage.depth_stencil_attachment = true` + .mip_level selects the mip-level to render into + .slice selects the slice to render into +*/ +typedef struct sg_buffer_view_desc { + sg_buffer buffer; + int offset; +} sg_buffer_view_desc; + +typedef struct sg_image_view_desc { + sg_image image; + int mip_level; + int slice; // cube texture: face; array texture: layer; 3D texture: depth-slice +} sg_image_view_desc; + +typedef struct sg_texture_view_range { + int base; + int count; +} sg_texture_view_range; + +typedef struct sg_texture_view_desc { + sg_image image; + sg_texture_view_range mip_levels; + sg_texture_view_range slices; // cube texture: face; array texture: layer; 3D texture: depth-slice +} sg_texture_view_desc; + +typedef struct sg_view_desc { + uint32_t _start_canary; + sg_texture_view_desc texture; + sg_buffer_view_desc storage_buffer; + sg_image_view_desc storage_image; + sg_image_view_desc color_attachment; + sg_image_view_desc resolve_attachment; + sg_image_view_desc depth_stencil_attachment; + const char* label; + uint32_t _end_canary; +} sg_view_desc; + +/* + sg_trace_hooks + + Installable callback functions to keep track of the sokol-gfx calls, + this is useful for debugging, or keeping track of resource creation + and destruction. + + Trace hooks are installed with sg_install_trace_hooks(), this returns + another sg_trace_hooks struct with the previous set of + trace hook function pointers. These should be invoked by the + new trace hooks to form a proper call chain. +*/ +typedef struct sg_trace_hooks { + void* user_data; + void (*reset_state_cache)(void* user_data); + void (*make_buffer)(const sg_buffer_desc* desc, sg_buffer result, void* user_data); + void (*make_image)(const sg_image_desc* desc, sg_image result, void* user_data); + void (*make_sampler)(const sg_sampler_desc* desc, sg_sampler result, void* user_data); + void (*make_shader)(const sg_shader_desc* desc, sg_shader result, void* user_data); + void (*make_pipeline)(const sg_pipeline_desc* desc, sg_pipeline result, void* user_data); + void (*make_view)(const sg_view_desc* desc, sg_view result, void* user_data); + void (*destroy_buffer)(sg_buffer buf, void* user_data); + void (*destroy_image)(sg_image img, void* user_data); + void (*destroy_sampler)(sg_sampler smp, void* user_data); + void (*destroy_shader)(sg_shader shd, void* user_data); + void (*destroy_pipeline)(sg_pipeline pip, void* user_data); + void (*destroy_view)(sg_view view, void* user_data); + void (*update_buffer)(sg_buffer buf, const sg_range* data, void* user_data); + void (*update_image)(sg_image img, const sg_image_data* data, void* user_data); + void (*append_buffer)(sg_buffer buf, const sg_range* data, int result, void* user_data); + void (*begin_pass)(const sg_pass* pass, void* user_data); + void (*apply_viewport)(int x, int y, int width, int height, bool origin_top_left, void* user_data); + void (*apply_scissor_rect)(int x, int y, int width, int height, bool origin_top_left, void* user_data); + void (*apply_pipeline)(sg_pipeline pip, void* user_data); + void (*apply_bindings)(const sg_bindings* bindings, void* user_data); + void (*apply_uniforms)(int ub_index, const sg_range* data, void* user_data); + void (*draw)(int base_element, int num_elements, int num_instances, void* user_data); + void (*draw_ex)(int base_element, int num_elements, int num_instances, int base_vertex, int base_instance, void* user_data); + void (*dispatch)(int num_groups_x, int num_groups_y, int num_groups_z, void* user_data); + void (*end_pass)(void* user_data); + void (*commit)(void* user_data); + void (*alloc_buffer)(sg_buffer result, void* user_data); + void (*alloc_image)(sg_image result, void* user_data); + void (*alloc_sampler)(sg_sampler result, void* user_data); + void (*alloc_shader)(sg_shader result, void* user_data); + void (*alloc_pipeline)(sg_pipeline result, void* user_data); + void (*alloc_view)(sg_view result, void* user_data); + void (*dealloc_buffer)(sg_buffer buf_id, void* user_data); + void (*dealloc_image)(sg_image img_id, void* user_data); + void (*dealloc_sampler)(sg_sampler smp_id, void* user_data); + void (*dealloc_shader)(sg_shader shd_id, void* user_data); + void (*dealloc_pipeline)(sg_pipeline pip_id, void* user_data); + void (*dealloc_view)(sg_view view_id, void* user_data); + void (*init_buffer)(sg_buffer buf_id, const sg_buffer_desc* desc, void* user_data); + void (*init_image)(sg_image img_id, const sg_image_desc* desc, void* user_data); + void (*init_sampler)(sg_sampler smp_id, const sg_sampler_desc* desc, void* user_data); + void (*init_shader)(sg_shader shd_id, const sg_shader_desc* desc, void* user_data); + void (*init_pipeline)(sg_pipeline pip_id, const sg_pipeline_desc* desc, void* user_data); + void (*init_view)(sg_view view_id, const sg_view_desc* desc, void* user_data); + void (*uninit_buffer)(sg_buffer buf_id, void* user_data); + void (*uninit_image)(sg_image img_id, void* user_data); + void (*uninit_sampler)(sg_sampler smp_id, void* user_data); + void (*uninit_shader)(sg_shader shd_id, void* user_data); + void (*uninit_pipeline)(sg_pipeline pip_id, void* user_data); + void (*uninit_view)(sg_view view_id, void* user_data); + void (*fail_buffer)(sg_buffer buf_id, void* user_data); + void (*fail_image)(sg_image img_id, void* user_data); + void (*fail_sampler)(sg_sampler smp_id, void* user_data); + void (*fail_shader)(sg_shader shd_id, void* user_data); + void (*fail_pipeline)(sg_pipeline pip_id, void* user_data); + void (*fail_view)(sg_view view_id, void* user_data); + void (*push_debug_group)(const char* name, void* user_data); + void (*pop_debug_group)(void* user_data); +} sg_trace_hooks; + +/* + sg_buffer_info + sg_image_info + sg_sampler_info + sg_shader_info + sg_pipeline_info + sg_view_info + + These structs contain various internal resource attributes which + might be useful for debug-inspection. Please don't rely on the + actual content of those structs too much, as they are quite closely + tied to sokol_gfx.h internals and may change more frequently than + the other public API elements. + + The *_info structs are used as the return values of the following functions: + + sg_query_buffer_info() + sg_query_image_info() + sg_query_sampler_info() + sg_query_shader_info() + sg_query_pipeline_info() + sg_query_view_info() +*/ +typedef struct sg_slot_info { + sg_resource_state state; // the current state of this resource slot + uint32_t res_id; // type-neutral resource if (e.g. sg_buffer.id) + uint32_t uninit_count; +} sg_slot_info; + +typedef struct sg_buffer_info { + sg_slot_info slot; // resource pool slot info + uint32_t update_frame_index; // frame index of last sg_update_buffer() + uint32_t append_frame_index; // frame index of last sg_append_buffer() + int append_pos; // current position in buffer for sg_append_buffer() + bool append_overflow; // is buffer in overflow state (due to sg_append_buffer) + int num_slots; // number of renaming-slots for dynamically updated buffers + int active_slot; // currently active write-slot for dynamically updated buffers +} sg_buffer_info; + +typedef struct sg_image_info { + sg_slot_info slot; // resource pool slot info + uint32_t upd_frame_index; // frame index of last sg_update_image() + int num_slots; // number of renaming-slots for dynamically updated images + int active_slot; // currently active write-slot for dynamically updated images +} sg_image_info; + +typedef struct sg_sampler_info { + sg_slot_info slot; // resource pool slot info +} sg_sampler_info; + +typedef struct sg_shader_info { + sg_slot_info slot; // resource pool slot info +} sg_shader_info; + +typedef struct sg_pipeline_info { + sg_slot_info slot; // resource pool slot info +} sg_pipeline_info; + +typedef struct sg_view_info { + sg_slot_info slot; // resource pool slot info +} sg_view_info; + +/* + sg_frame_stats + + Allows to track generic and backend-specific stats about a + render frame. Obtained by calling sg_query_frame_stats(). The returned + struct contains information about the *previous* frame. +*/ +typedef struct sg_frame_stats_gl { + uint32_t num_bind_buffer; + uint32_t num_active_texture; + uint32_t num_bind_texture; + uint32_t num_bind_sampler; + uint32_t num_bind_image_texture; + uint32_t num_use_program; + uint32_t num_render_state; + uint32_t num_vertex_attrib_pointer; + uint32_t num_vertex_attrib_divisor; + uint32_t num_enable_vertex_attrib_array; + uint32_t num_disable_vertex_attrib_array; + uint32_t num_uniform; + uint32_t num_memory_barriers; +} sg_frame_stats_gl; + +typedef struct sg_frame_stats_d3d11_pass { + uint32_t num_om_set_render_targets; + uint32_t num_clear_render_target_view; + uint32_t num_clear_depth_stencil_view; + uint32_t num_resolve_subresource; +} sg_frame_stats_d3d11_pass; + +typedef struct sg_frame_stats_d3d11_pipeline { + uint32_t num_rs_set_state; + uint32_t num_om_set_depth_stencil_state; + uint32_t num_om_set_blend_state; + uint32_t num_ia_set_primitive_topology; + uint32_t num_ia_set_input_layout; + uint32_t num_vs_set_shader; + uint32_t num_vs_set_constant_buffers; + uint32_t num_ps_set_shader; + uint32_t num_ps_set_constant_buffers; + uint32_t num_cs_set_shader; + uint32_t num_cs_set_constant_buffers; +} sg_frame_stats_d3d11_pipeline; + +typedef struct sg_frame_stats_d3d11_bindings { + uint32_t num_ia_set_vertex_buffers; + uint32_t num_ia_set_index_buffer; + uint32_t num_vs_set_shader_resources; + uint32_t num_vs_set_samplers; + uint32_t num_ps_set_shader_resources; + uint32_t num_ps_set_samplers; + uint32_t num_cs_set_shader_resources; + uint32_t num_cs_set_samplers; + uint32_t num_cs_set_unordered_access_views; +} sg_frame_stats_d3d11_bindings; + +typedef struct sg_frame_stats_d3d11_uniforms { + uint32_t num_update_subresource; +} sg_frame_stats_d3d11_uniforms; + +typedef struct sg_frame_stats_d3d11_draw { + uint32_t num_draw_indexed_instanced; + uint32_t num_draw_indexed; + uint32_t num_draw_instanced; + uint32_t num_draw; +} sg_frame_stats_d3d11_draw; + +typedef struct sg_frame_stats_d3d11 { + sg_frame_stats_d3d11_pass pass; + sg_frame_stats_d3d11_pipeline pipeline; + sg_frame_stats_d3d11_bindings bindings; + sg_frame_stats_d3d11_uniforms uniforms; + sg_frame_stats_d3d11_draw draw; + uint32_t num_map; + uint32_t num_unmap; +} sg_frame_stats_d3d11; + +typedef struct sg_frame_stats_metal_idpool { + uint32_t num_added; + uint32_t num_released; + uint32_t num_garbage_collected; +} sg_frame_stats_metal_idpool; + +typedef struct sg_frame_stats_metal_pipeline { + uint32_t num_set_blend_color; + uint32_t num_set_cull_mode; + uint32_t num_set_front_facing_winding; + uint32_t num_set_stencil_reference_value; + uint32_t num_set_depth_bias; + uint32_t num_set_render_pipeline_state; + uint32_t num_set_depth_stencil_state; +} sg_frame_stats_metal_pipeline; + +typedef struct sg_frame_stats_metal_bindings { + uint32_t num_set_vertex_buffer; + uint32_t num_set_vertex_buffer_offset; + uint32_t num_skip_redundant_vertex_buffer; + uint32_t num_set_vertex_texture; + uint32_t num_skip_redundant_vertex_texture; + uint32_t num_set_vertex_sampler_state; + uint32_t num_skip_redundant_vertex_sampler_state; + uint32_t num_set_fragment_buffer; + uint32_t num_set_fragment_buffer_offset; + uint32_t num_skip_redundant_fragment_buffer; + uint32_t num_set_fragment_texture; + uint32_t num_skip_redundant_fragment_texture; + uint32_t num_set_fragment_sampler_state; + uint32_t num_skip_redundant_fragment_sampler_state; + uint32_t num_set_compute_buffer; + uint32_t num_set_compute_buffer_offset; + uint32_t num_skip_redundant_compute_buffer; + uint32_t num_set_compute_texture; + uint32_t num_skip_redundant_compute_texture; + uint32_t num_set_compute_sampler_state; + uint32_t num_skip_redundant_compute_sampler_state; +} sg_frame_stats_metal_bindings; + +typedef struct sg_frame_stats_metal_uniforms { + uint32_t num_set_vertex_buffer_offset; + uint32_t num_set_fragment_buffer_offset; + uint32_t num_set_compute_buffer_offset; +} sg_frame_stats_metal_uniforms; + +typedef struct sg_frame_stats_metal { + sg_frame_stats_metal_idpool idpool; + sg_frame_stats_metal_pipeline pipeline; + sg_frame_stats_metal_bindings bindings; + sg_frame_stats_metal_uniforms uniforms; +} sg_frame_stats_metal; + +typedef struct sg_frame_stats_wgpu_uniforms { + uint32_t num_set_bindgroup; + uint32_t size_write_buffer; +} sg_frame_stats_wgpu_uniforms; + +typedef struct sg_frame_stats_wgpu_bindings { + uint32_t num_set_vertex_buffer; + uint32_t num_skip_redundant_vertex_buffer; + uint32_t num_set_index_buffer; + uint32_t num_skip_redundant_index_buffer; + uint32_t num_create_bindgroup; + uint32_t num_discard_bindgroup; + uint32_t num_set_bindgroup; + uint32_t num_skip_redundant_bindgroup; + uint32_t num_bindgroup_cache_hits; + uint32_t num_bindgroup_cache_misses; + uint32_t num_bindgroup_cache_collisions; + uint32_t num_bindgroup_cache_invalidates; + uint32_t num_bindgroup_cache_hash_vs_key_mismatch; +} sg_frame_stats_wgpu_bindings; + +typedef struct sg_frame_stats_wgpu { + sg_frame_stats_wgpu_uniforms uniforms; + sg_frame_stats_wgpu_bindings bindings; +} sg_frame_stats_wgpu; + +typedef struct sg_resource_stats { + uint32_t total_alive; // number of live objects in pool + uint32_t total_free; // number of free objects in pool + uint32_t allocated; // number of allocated objects in current frame + uint32_t deallocated; // number of deallocated object in current frame + uint32_t inited; // number of initialized objects in current frame + uint32_t uninited; // number of deinitialized objects in current frame +} sg_resource_stats; + +typedef struct sg_frame_stats { + uint32_t frame_index; // current frame counter, starts at 0 + + uint32_t num_passes; + uint32_t num_apply_viewport; + uint32_t num_apply_scissor_rect; + uint32_t num_apply_pipeline; + uint32_t num_apply_bindings; + uint32_t num_apply_uniforms; + uint32_t num_draw; + uint32_t num_draw_ex; + uint32_t num_dispatch; + uint32_t num_update_buffer; + uint32_t num_append_buffer; + uint32_t num_update_image; + + uint32_t size_apply_uniforms; + uint32_t size_update_buffer; + uint32_t size_append_buffer; + uint32_t size_update_image; + + sg_resource_stats buffers; + sg_resource_stats images; + sg_resource_stats samplers; + sg_resource_stats views; + sg_resource_stats shaders; + sg_resource_stats pipelines; + + sg_frame_stats_gl gl; + sg_frame_stats_d3d11 d3d11; + sg_frame_stats_metal metal; + sg_frame_stats_wgpu wgpu; +} sg_frame_stats; + +/* + sg_log_item + + An enum with a unique item for each log message, warning, error + and validation layer message. Note that these messages are only + visible when a logger function is installed in the sg_setup() call. +*/ +#define _SG_LOG_ITEMS \ + _SG_LOGITEM_XMACRO(OK, "Ok") \ + _SG_LOGITEM_XMACRO(MALLOC_FAILED, "memory allocation failed") \ + _SG_LOGITEM_XMACRO(GL_TEXTURE_FORMAT_NOT_SUPPORTED, "pixel format not supported for texture (gl)") \ + _SG_LOGITEM_XMACRO(GL_3D_TEXTURES_NOT_SUPPORTED, "3d textures not supported (gl)") \ + _SG_LOGITEM_XMACRO(GL_ARRAY_TEXTURES_NOT_SUPPORTED, "array textures not supported (gl)") \ + _SG_LOGITEM_XMACRO(GL_STORAGEBUFFER_GLSL_BINDING_OUT_OF_RANGE, "GLSL storage buffer bindslot is out of range (sg_limits.max_storage_buffer_bindings_per_stage) (gl)") \ + _SG_LOGITEM_XMACRO(GL_STORAGEIMAGE_GLSL_BINDING_OUT_OF_RANGE, "GLSL storage image bindslot is out of range (sg.limits.max_storage_image_bindings_per_stage) (gl)") \ + _SG_LOGITEM_XMACRO(GL_SHADER_COMPILATION_FAILED, "shader compilation failed (gl)") \ + _SG_LOGITEM_XMACRO(GL_SHADER_LINKING_FAILED, "shader linking failed (gl)") \ + _SG_LOGITEM_XMACRO(GL_VERTEX_ATTRIBUTE_NOT_FOUND_IN_SHADER, "vertex attribute not found in shader; NOTE: may be caused by GL driver's GLSL compiler removing unused globals") \ + _SG_LOGITEM_XMACRO(GL_UNIFORMBLOCK_NAME_NOT_FOUND_IN_SHADER, "uniform block name not found in shader; NOTE: may be caused by GL driver's GLSL compiler removing unused globals") \ + _SG_LOGITEM_XMACRO(GL_IMAGE_SAMPLER_NAME_NOT_FOUND_IN_SHADER, "image-sampler name not found in shader; NOTE: may be caused by GL driver's GLSL compiler removing unused globals") \ + _SG_LOGITEM_XMACRO(GL_FRAMEBUFFER_STATUS_UNDEFINED, "framebuffer completeness check failed with GL_FRAMEBUFFER_UNDEFINED (gl)") \ + _SG_LOGITEM_XMACRO(GL_FRAMEBUFFER_STATUS_INCOMPLETE_ATTACHMENT, "framebuffer completeness check failed with GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT (gl)") \ + _SG_LOGITEM_XMACRO(GL_FRAMEBUFFER_STATUS_INCOMPLETE_MISSING_ATTACHMENT, "framebuffer completeness check failed with GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT (gl)") \ + _SG_LOGITEM_XMACRO(GL_FRAMEBUFFER_STATUS_UNSUPPORTED, "framebuffer completeness check failed with GL_FRAMEBUFFER_UNSUPPORTED (gl)") \ + _SG_LOGITEM_XMACRO(GL_FRAMEBUFFER_STATUS_INCOMPLETE_MULTISAMPLE, "framebuffer completeness check failed with GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE (gl)") \ + _SG_LOGITEM_XMACRO(GL_FRAMEBUFFER_STATUS_UNKNOWN, "framebuffer completeness check failed (unknown reason) (gl)") \ + _SG_LOGITEM_XMACRO(D3D11_FEATURE_LEVEL_0_DETECTED, "D3D11 Feature Level 0 device detected, this restricts the number of UAV slots to 8! (d3d11)") \ + _SG_LOGITEM_XMACRO(D3D11_CREATE_BUFFER_FAILED, "CreateBuffer() failed (d3d11)") \ + _SG_LOGITEM_XMACRO(D3D11_CREATE_BUFFER_SRV_FAILED, "CreateShaderResourceView() failed for storage buffer (d3d11)") \ + _SG_LOGITEM_XMACRO(D3D11_CREATE_BUFFER_UAV_FAILED, "CreateUnorderedAccessView() failed for storage buffer (d3d11)") \ + _SG_LOGITEM_XMACRO(D3D11_CREATE_DEPTH_TEXTURE_UNSUPPORTED_PIXEL_FORMAT, "pixel format not supported for depth-stencil texture (d3d11)") \ + _SG_LOGITEM_XMACRO(D3D11_CREATE_DEPTH_TEXTURE_FAILED, "CreateTexture2D() failed for depth-stencil texture (d3d11)") \ + _SG_LOGITEM_XMACRO(D3D11_CREATE_2D_TEXTURE_UNSUPPORTED_PIXEL_FORMAT, "pixel format not supported for 2d-, cube- or array-texture (d3d11)") \ + _SG_LOGITEM_XMACRO(D3D11_CREATE_2D_TEXTURE_FAILED, "CreateTexture2D() failed for 2d-, cube- or array-texture (d3d11)") \ + _SG_LOGITEM_XMACRO(D3D11_CREATE_2D_SRV_FAILED, "CreateShaderResourceView() failed for 2d-, cube- or array-texture (d3d11)") \ + _SG_LOGITEM_XMACRO(D3D11_CREATE_3D_TEXTURE_UNSUPPORTED_PIXEL_FORMAT, "pixel format not supported for 3D texture (d3d11)") \ + _SG_LOGITEM_XMACRO(D3D11_CREATE_3D_TEXTURE_FAILED, "CreateTexture3D() failed (d3d11)") \ + _SG_LOGITEM_XMACRO(D3D11_CREATE_3D_SRV_FAILED, "CreateShaderResourceView() failed for 3d texture (d3d11)") \ + _SG_LOGITEM_XMACRO(D3D11_CREATE_MSAA_TEXTURE_FAILED, "CreateTexture2D() failed for MSAA render target texture (d3d11)") \ + _SG_LOGITEM_XMACRO(D3D11_CREATE_SAMPLER_STATE_FAILED, "CreateSamplerState() failed (d3d11)") \ + _SG_LOGITEM_XMACRO(D3D11_UNIFORMBLOCK_HLSL_REGISTER_B_OUT_OF_RANGE, "sg_shader_desc.uniform_blocks[].hlsl_register_b_n is out of range (must be 0..7)") \ + _SG_LOGITEM_XMACRO(D3D11_STORAGEBUFFER_HLSL_REGISTER_T_OUT_OF_RANGE, "sg_shader_desc.views[].storage_buffer.hlsl_register_t_n is out of range (must be 0..31)") \ + _SG_LOGITEM_XMACRO(D3D11_STORAGEBUFFER_HLSL_REGISTER_U_OUT_OF_RANGE, "sg_shader_desc.views[].storage_buffer.hlsl_register_u_n is out of range (must be 0..31)") \ + _SG_LOGITEM_XMACRO(D3D11_IMAGE_HLSL_REGISTER_T_OUT_OF_RANGE, "sg_shader_desc.views[].texture.hlsl_register_t_n is out of range (must be 0..31)") \ + _SG_LOGITEM_XMACRO(D3D11_STORAGEIMAGE_HLSL_REGISTER_U_OUT_OF_RANGE, "sg_shader_desc.views[].storage_image.hlsl_register_u_n is out of range (must be 0..31)") \ + _SG_LOGITEM_XMACRO(D3D11_SAMPLER_HLSL_REGISTER_S_OUT_OF_RANGE, "sampler 'hlsl_register_s_n' is out of rang (must be 0..11)") \ + _SG_LOGITEM_XMACRO(D3D11_LOAD_D3DCOMPILER_47_DLL_FAILED, "loading d3dcompiler_47.dll failed (d3d11)") \ + _SG_LOGITEM_XMACRO(D3D11_SHADER_COMPILATION_FAILED, "shader compilation failed (d3d11)") \ + _SG_LOGITEM_XMACRO(D3D11_SHADER_COMPILATION_OUTPUT, "") \ + _SG_LOGITEM_XMACRO(D3D11_CREATE_CONSTANT_BUFFER_FAILED, "CreateBuffer() failed for uniform constant buffer (d3d11)") \ + _SG_LOGITEM_XMACRO(D3D11_CREATE_INPUT_LAYOUT_FAILED, "CreateInputLayout() failed (d3d11)") \ + _SG_LOGITEM_XMACRO(D3D11_CREATE_RASTERIZER_STATE_FAILED, "CreateRasterizerState() failed (d3d11)") \ + _SG_LOGITEM_XMACRO(D3D11_CREATE_DEPTH_STENCIL_STATE_FAILED, "CreateDepthStencilState() failed (d3d11)") \ + _SG_LOGITEM_XMACRO(D3D11_CREATE_BLEND_STATE_FAILED, "CreateBlendState() failed (d3d11)") \ + _SG_LOGITEM_XMACRO(D3D11_CREATE_RTV_FAILED, "CreateRenderTargetView() failed (d3d11)") \ + _SG_LOGITEM_XMACRO(D3D11_CREATE_DSV_FAILED, "CreateDepthStencilView() failed (d3d11)") \ + _SG_LOGITEM_XMACRO(D3D11_CREATE_UAV_FAILED, "CreateUnorderedAccessView() failed (d3d11)") \ + _SG_LOGITEM_XMACRO(D3D11_MAP_FOR_UPDATE_BUFFER_FAILED, "Map() failed when updating buffer (d3d11)") \ + _SG_LOGITEM_XMACRO(D3D11_MAP_FOR_APPEND_BUFFER_FAILED, "Map() failed when appending to buffer (d3d11)") \ + _SG_LOGITEM_XMACRO(D3D11_MAP_FOR_UPDATE_IMAGE_FAILED, "Map() failed when updating image (d3d11)") \ + _SG_LOGITEM_XMACRO(METAL_CREATE_BUFFER_FAILED, "failed to create buffer object (metal)") \ + _SG_LOGITEM_XMACRO(METAL_TEXTURE_FORMAT_NOT_SUPPORTED, "pixel format not supported for texture (metal)") \ + _SG_LOGITEM_XMACRO(METAL_CREATE_TEXTURE_FAILED, "failed to create texture object (metal)") \ + _SG_LOGITEM_XMACRO(METAL_CREATE_SAMPLER_FAILED, "failed to create sampler object (metal)") \ + _SG_LOGITEM_XMACRO(METAL_SHADER_COMPILATION_FAILED, "shader compilation failed (metal)") \ + _SG_LOGITEM_XMACRO(METAL_SHADER_CREATION_FAILED, "shader creation failed (metal)") \ + _SG_LOGITEM_XMACRO(METAL_SHADER_COMPILATION_OUTPUT, "") \ + _SG_LOGITEM_XMACRO(METAL_SHADER_ENTRY_NOT_FOUND, "shader entry function not found (metal)") \ + _SG_LOGITEM_XMACRO(METAL_UNIFORMBLOCK_MSL_BUFFER_SLOT_OUT_OF_RANGE, "uniform block 'msl_buffer_n' is out of range (must be 0..7)") \ + _SG_LOGITEM_XMACRO(METAL_STORAGEBUFFER_MSL_BUFFER_SLOT_OUT_OF_RANGE, "storage buffer 'msl_buffer_n' is out of range (must be 8..23)") \ + _SG_LOGITEM_XMACRO(METAL_STORAGEIMAGE_MSL_TEXTURE_SLOT_OUT_OF_RANGE, "storage image 'msl_texture_n' is out of range (must be 0..31)") \ + _SG_LOGITEM_XMACRO(METAL_IMAGE_MSL_TEXTURE_SLOT_OUT_OF_RANGE, "image 'msl_texture_n' is out of range (must be 0..31)") \ + _SG_LOGITEM_XMACRO(METAL_SAMPLER_MSL_SAMPLER_SLOT_OUT_OF_RANGE, "sampler 'msl_sampler_n' is out of range (must be 0..11)") \ + _SG_LOGITEM_XMACRO(METAL_CREATE_CPS_FAILED, "failed to create compute pipeline state (metal)") \ + _SG_LOGITEM_XMACRO(METAL_CREATE_CPS_OUTPUT, "") \ + _SG_LOGITEM_XMACRO(METAL_CREATE_RPS_FAILED, "failed to create render pipeline state (metal)") \ + _SG_LOGITEM_XMACRO(METAL_CREATE_RPS_OUTPUT, "") \ + _SG_LOGITEM_XMACRO(METAL_CREATE_DSS_FAILED, "failed to create depth stencil state (metal)") \ + _SG_LOGITEM_XMACRO(WGPU_BINDGROUPS_POOL_EXHAUSTED, "bindgroups pool exhausted (increase sg_desc.bindgroups_cache_size) (wgpu)") \ + _SG_LOGITEM_XMACRO(WGPU_BINDGROUPSCACHE_SIZE_GREATER_ONE, "sg_desc.wgpu_bindgroups_cache_size must be > 1 (wgpu)") \ + _SG_LOGITEM_XMACRO(WGPU_BINDGROUPSCACHE_SIZE_POW2, "sg_desc.wgpu_bindgroups_cache_size must be a power of 2 (wgpu)") \ + _SG_LOGITEM_XMACRO(WGPU_CREATEBINDGROUP_FAILED, "wgpuDeviceCreateBindGroup failed") \ + _SG_LOGITEM_XMACRO(WGPU_CREATE_BUFFER_FAILED, "wgpuDeviceCreateBuffer() failed") \ + _SG_LOGITEM_XMACRO(WGPU_CREATE_TEXTURE_FAILED, "wgpuDeviceCreateTexture() failed") \ + _SG_LOGITEM_XMACRO(WGPU_CREATE_TEXTURE_VIEW_FAILED, "wgpuTextureCreateView() failed") \ + _SG_LOGITEM_XMACRO(WGPU_CREATE_SAMPLER_FAILED, "wgpuDeviceCreateSampler() failed") \ + _SG_LOGITEM_XMACRO(WGPU_CREATE_SHADER_MODULE_FAILED, "wgpuDeviceCreateShaderModule() failed") \ + _SG_LOGITEM_XMACRO(WGPU_SHADER_CREATE_BINDGROUP_LAYOUT_FAILED, "wgpuDeviceCreateBindGroupLayout() for shader stage failed") \ + _SG_LOGITEM_XMACRO(WGPU_UNIFORMBLOCK_WGSL_GROUP0_BINDING_OUT_OF_RANGE, "uniform block 'wgsl_group0_binding_n' is out of range (must be 0..15)") \ + _SG_LOGITEM_XMACRO(WGPU_TEXTURE_WGSL_GROUP1_BINDING_OUT_OF_RANGE, "texture 'wgsl_group1_binding_n' is out of range (must be 0..127)") \ + _SG_LOGITEM_XMACRO(WGPU_STORAGEBUFFER_WGSL_GROUP1_BINDING_OUT_OF_RANGE, "storage buffer 'wgsl_group1_binding_n' is out of range (must be 0..127)") \ + _SG_LOGITEM_XMACRO(WGPU_STORAGEIMAGE_WGSL_GROUP1_BINDING_OUT_OF_RANGE, "storage image 'wgsl_group1_binding_n' is out of range (must be 0..127)") \ + _SG_LOGITEM_XMACRO(WGPU_SAMPLER_WGSL_GROUP1_BINDING_OUT_OF_RANGE, "sampler 'wgsl_group1_binding_n' is out of range (must be 0..127)") \ + _SG_LOGITEM_XMACRO(WGPU_CREATE_PIPELINE_LAYOUT_FAILED, "wgpuDeviceCreatePipelineLayout() failed") \ + _SG_LOGITEM_XMACRO(WGPU_CREATE_RENDER_PIPELINE_FAILED, "wgpuDeviceCreateRenderPipeline() failed") \ + _SG_LOGITEM_XMACRO(WGPU_CREATE_COMPUTE_PIPELINE_FAILED, "wgpuDeviceCreateComputePipeline() failed") \ + _SG_LOGITEM_XMACRO(IDENTICAL_COMMIT_LISTENER, "attempting to add identical commit listener") \ + _SG_LOGITEM_XMACRO(COMMIT_LISTENER_ARRAY_FULL, "commit listener array full") \ + _SG_LOGITEM_XMACRO(TRACE_HOOKS_NOT_ENABLED, "sg_install_trace_hooks() called, but SOKOL_TRACE_HOOKS is not defined") \ + _SG_LOGITEM_XMACRO(DEALLOC_BUFFER_INVALID_STATE, "sg_dealloc_buffer(): buffer must be in ALLOC state") \ + _SG_LOGITEM_XMACRO(DEALLOC_IMAGE_INVALID_STATE, "sg_dealloc_image(): image must be in alloc state") \ + _SG_LOGITEM_XMACRO(DEALLOC_SAMPLER_INVALID_STATE, "sg_dealloc_sampler(): sampler must be in alloc state") \ + _SG_LOGITEM_XMACRO(DEALLOC_SHADER_INVALID_STATE, "sg_dealloc_shader(): shader must be in ALLOC state") \ + _SG_LOGITEM_XMACRO(DEALLOC_PIPELINE_INVALID_STATE, "sg_dealloc_pipeline(): pipeline must be in ALLOC state") \ + _SG_LOGITEM_XMACRO(DEALLOC_VIEW_INVALID_STATE, "sg_dealloc_view(): view must be in ALLOC state") \ + _SG_LOGITEM_XMACRO(INIT_BUFFER_INVALID_STATE, "sg_init_buffer(): buffer must be in ALLOC state") \ + _SG_LOGITEM_XMACRO(INIT_IMAGE_INVALID_STATE, "sg_init_image(): image must be in ALLOC state") \ + _SG_LOGITEM_XMACRO(INIT_SAMPLER_INVALID_STATE, "sg_init_sampler(): sampler must be in ALLOC state") \ + _SG_LOGITEM_XMACRO(INIT_SHADER_INVALID_STATE, "sg_init_shader(): shader must be in ALLOC state") \ + _SG_LOGITEM_XMACRO(INIT_PIPELINE_INVALID_STATE, "sg_init_pipeline(): pipeline must be in ALLOC state") \ + _SG_LOGITEM_XMACRO(INIT_VIEW_INVALID_STATE, "sg_init_view(): view must be in ALLOC state") \ + _SG_LOGITEM_XMACRO(UNINIT_BUFFER_INVALID_STATE, "sg_uninit_buffer(): buffer must be in VALID, FAILED or ALLOC state") \ + _SG_LOGITEM_XMACRO(UNINIT_IMAGE_INVALID_STATE, "sg_uninit_image(): image must be in VALID, FAILED or ALLOC state") \ + _SG_LOGITEM_XMACRO(UNINIT_SAMPLER_INVALID_STATE, "sg_uninit_sampler(): sampler must be in VALID, FAILED or ALLOC state") \ + _SG_LOGITEM_XMACRO(UNINIT_SHADER_INVALID_STATE, "sg_uninit_shader(): shader must be in VALID, FAILED or ALLOC state") \ + _SG_LOGITEM_XMACRO(UNINIT_PIPELINE_INVALID_STATE, "sg_uninit_pipeline(): pipeline must be in VALID, FAILED or ALLOC state") \ + _SG_LOGITEM_XMACRO(UNINIT_VIEW_INVALID_STATE, "sg_uninit_view(): view must be in VALID, FAILED or ALLOC state") \ + _SG_LOGITEM_XMACRO(FAIL_BUFFER_INVALID_STATE, "sg_fail_buffer(): buffer must be in ALLOC state") \ + _SG_LOGITEM_XMACRO(FAIL_IMAGE_INVALID_STATE, "sg_fail_image(): image must be in ALLOC state") \ + _SG_LOGITEM_XMACRO(FAIL_SAMPLER_INVALID_STATE, "sg_fail_sampler(): sampler must be in ALLOC state") \ + _SG_LOGITEM_XMACRO(FAIL_SHADER_INVALID_STATE, "sg_fail_shader(): shader must be in ALLOC state") \ + _SG_LOGITEM_XMACRO(FAIL_PIPELINE_INVALID_STATE, "sg_fail_pipeline(): pipeline must be in ALLOC state") \ + _SG_LOGITEM_XMACRO(FAIL_VIEW_INVALID_STATE, "sg_fail_view(): view must be in ALLOC state") \ + _SG_LOGITEM_XMACRO(BUFFER_POOL_EXHAUSTED, "buffer pool exhausted") \ + _SG_LOGITEM_XMACRO(IMAGE_POOL_EXHAUSTED, "image pool exhausted") \ + _SG_LOGITEM_XMACRO(SAMPLER_POOL_EXHAUSTED, "sampler pool exhausted") \ + _SG_LOGITEM_XMACRO(SHADER_POOL_EXHAUSTED, "shader pool exhausted") \ + _SG_LOGITEM_XMACRO(PIPELINE_POOL_EXHAUSTED, "pipeline pool exhausted") \ + _SG_LOGITEM_XMACRO(VIEW_POOL_EXHAUSTED, "view pool exhausted") \ + _SG_LOGITEM_XMACRO(BEGINPASS_TOO_MANY_COLOR_ATTACHMENTS, "sg_begin_pass: too many color attachments (sg_limits.max_color_attachments)") \ + _SG_LOGITEM_XMACRO(BEGINPASS_TOO_MANY_RESOLVE_ATTACHMENTS, "sg_begin_pass: too many resolve attachments (sg_limits.max_color_attachments)") \ + _SG_LOGITEM_XMACRO(BEGINPASS_ATTACHMENTS_ALIVE, "sg_begin_pass: an attachment was provided that no longer exists") \ + _SG_LOGITEM_XMACRO(DRAW_WITHOUT_BINDINGS, "attempting to draw without resource bindings") \ + _SG_LOGITEM_XMACRO(SHADERDESC_TOO_MANY_VERTEXSTAGE_TEXTURES, "sg_shader_desc: too many texture bindings on vertex shader stage (sg_limits.max_texture_bindings_per_stage)") \ + _SG_LOGITEM_XMACRO(SHADERDESC_TOO_MANY_FRAGMENTSTAGE_TEXTURES, "sg_shader_desc: too many texture bindings on fragment shader stage (sg_limits.max_texture_bindings_per_stage)") \ + _SG_LOGITEM_XMACRO(SHADERDESC_TOO_MANY_COMPUTESTAGE_TEXTURES, "sg_shader_desc: too many texture bindings on compute shader stage (sg_limits.max_texture_bindings_per_stage)") \ + _SG_LOGITEM_XMACRO(SHADERDESC_TOO_MANY_VERTEXSTAGE_STORAGEBUFFERS, "sg_shader_desc: too many storage buffer bindings on vertex shader stage (sg_limits.max_storage_buffer_bindings_per_stage)") \ + _SG_LOGITEM_XMACRO(SHADERDESC_TOO_MANY_FRAGMENTSTAGE_STORAGEBUFFERS, "sg_shader_desc: too many storage buffer bindings on fragment shader stage (sg_limits.max_storage_buffer_bindings_per_stage)") \ + _SG_LOGITEM_XMACRO(SHADERDESC_TOO_MANY_COMPUTESTAGE_STORAGEBUFFERS, "sg_shader_desc: too many storage buffer bindings on compute shader stage (sg_limits.max_storage_buffer_bindings_per_stage)") \ + _SG_LOGITEM_XMACRO(SHADERDESC_TOO_MANY_VERTEXSTAGE_STORAGEIMAGES, "sg_shader_desc: too many storage image bindings on vertex shader stage (sg_limits.max_storage_image_bindings_per_stage)") \ + _SG_LOGITEM_XMACRO(SHADERDESC_TOO_MANY_FRAGMENTSTAGE_STORAGEIMAGES, "sg_shader_desc: too many storage image bindings on fragment shader stage (sg_limits.max_storage_image_bindings_per_stage)") \ + _SG_LOGITEM_XMACRO(SHADERDESC_TOO_MANY_COMPUTESTAGE_STORAGEIMAGES, "sg_shader_desc: too many storage image bindings on compute shader stage (sg_limits.max_storage_image_bindings_per_stage)") \ + _SG_LOGITEM_XMACRO(SHADERDESC_TOO_MANY_VERTEXSTAGE_TEXTURESAMPLERPAIRS, "sg_shader_desc: too many texture-sampler-pairs on vertex shader stage (sg_limits.max_texture_bindings_per_stage)") \ + _SG_LOGITEM_XMACRO(SHADERDESC_TOO_MANY_FRAGMENTSTAGE_TEXTURESAMPLERPAIRS, "sg_shader_desc: too many texture-sampler-pairs on fragment shader stage (sg_limits.max_texture_bindings_per_stage)") \ + _SG_LOGITEM_XMACRO(SHADERDESC_TOO_MANY_COMPUTESTAGE_TEXTURESAMPLERPAIRS, "sg_shader_desc: too many texture-sampler-pairs on compute shader stage (sg_limits.max_texture_bindings_per_stage)") \ + _SG_LOGITEM_XMACRO(VALIDATE_BUFFERDESC_CANARY, "sg_buffer_desc not initialized") \ + _SG_LOGITEM_XMACRO(VALIDATE_BUFFERDESC_IMMUTABLE_DYNAMIC_STREAM, "sg_buffer_desc.usage: only one of .immutable, .dynamic_update, .stream_update can be true") \ + _SG_LOGITEM_XMACRO(VALIDATE_BUFFERDESC_SEPARATE_BUFFER_TYPES, "sg_buffer_desc.usage: on WebGL2, only one of .vertex_buffer or .index_buffer can be true (check sg_features.separate_buffer_types)") \ + _SG_LOGITEM_XMACRO(VALIDATE_BUFFERDESC_EXPECT_NONZERO_SIZE, "sg_buffer_desc.size must be greater zero") \ + _SG_LOGITEM_XMACRO(VALIDATE_BUFFERDESC_EXPECT_MATCHING_DATA_SIZE, "sg_buffer_desc.size and .data.size must be equal") \ + _SG_LOGITEM_XMACRO(VALIDATE_BUFFERDESC_EXPECT_ZERO_DATA_SIZE, "sg_buffer_desc.data.size expected to be zero") \ + _SG_LOGITEM_XMACRO(VALIDATE_BUFFERDESC_EXPECT_NO_DATA, "sg_buffer_desc.data.ptr must be null for dynamic/stream buffers") \ + _SG_LOGITEM_XMACRO(VALIDATE_BUFFERDESC_EXPECT_DATA, "sg_buffer_desc: initial content data must be provided for immutable buffers without storage buffer usage") \ + _SG_LOGITEM_XMACRO(VALIDATE_BUFFERDESC_STORAGEBUFFER_SUPPORTED, "storage buffers not supported by the backend 3D API (requires OpenGL >= 4.3)") \ + _SG_LOGITEM_XMACRO(VALIDATE_BUFFERDESC_STORAGEBUFFER_SIZE_MULTIPLE_4, "size of storage buffers must be a multiple of 4") \ + _SG_LOGITEM_XMACRO(VALIDATE_IMAGEDATA_NODATA, "sg_image_data: no data (.ptr and/or .size is zero)") \ + _SG_LOGITEM_XMACRO(VALIDATE_IMAGEDATA_DATA_SIZE, "sg_image_data: data size doesn't match expected surface size") \ + _SG_LOGITEM_XMACRO(VALIDATE_IMAGEDESC_CANARY, "sg_image_desc not initialized") \ + _SG_LOGITEM_XMACRO(VALIDATE_IMAGEDESC_IMMUTABLE_DYNAMIC_STREAM, "sg_image_desc.usage: only one of .immutable, .dynamic_update, .stream_update can be true") \ + _SG_LOGITEM_XMACRO(VALIDATE_IMAGEDESC_IMAGETYPE_2D_NUMSLICES, "sg_image_desc.num_slices must be exactly 1 for SG_IMAGETYPE_2D") \ + _SG_LOGITEM_XMACRO(VALIDATE_IMAGEDESC_IMAGETYPE_CUBE_NUMSLICES, "sg_image_desc.num_slices must be exactly 6 for SG_IMAGETYPE_CUBE") \ + _SG_LOGITEM_XMACRO(VALIDATE_IMAGEDESC_IMAGETYPE_ARRAY_NUMSLICES, "sg_image_desc.num_slices must be ((>= 1) && (<= sg_limits.max_image_array_layers)) for SG_IMAGETYPE_ARRAY") \ + _SG_LOGITEM_XMACRO(VALIDATE_IMAGEDESC_IMAGETYPE_3D_NUMSLICES, "sg_image_desc.num_slices must be ((>= 1) && (<= sg_limits.max_image_size_3d)) for SG_IMAGETYPE_ARRAY") \ + _SG_LOGITEM_XMACRO(VALIDATE_IMAGEDESC_NUMSLICES, "sg_image_desc.num_slices must be > 0") \ + _SG_LOGITEM_XMACRO(VALIDATE_IMAGEDESC_WIDTH, "sg_image_desc.width must be > 0") \ + _SG_LOGITEM_XMACRO(VALIDATE_IMAGEDESC_HEIGHT, "sg_image_desc.height must be > 0") \ + _SG_LOGITEM_XMACRO(VALIDATE_IMAGEDESC_NONRT_PIXELFORMAT, "invalid pixel format for non-render-target image") \ + _SG_LOGITEM_XMACRO(VALIDATE_IMAGEDESC_MSAA_BUT_NO_ATTACHMENT, "non-attachment images cannot be multisampled") \ + _SG_LOGITEM_XMACRO(VALIDATE_IMAGEDESC_DEPTH_3D_IMAGE, "3D images cannot have a depth/stencil image format") \ + _SG_LOGITEM_XMACRO(VALIDATE_IMAGEDESC_ATTACHMENT_EXPECT_IMMUTABLE, "attachment and storage images must be sg_image_usage.immutable") \ + _SG_LOGITEM_XMACRO(VALIDATE_IMAGEDESC_ATTACHMENT_EXPECT_NO_DATA, "render/storage attachment images cannot be initialized with data") \ + _SG_LOGITEM_XMACRO(VALIDATE_IMAGEDESC_ATTACHMENT_PIXELFORMAT, "invalid pixel format for render attachment image") \ + _SG_LOGITEM_XMACRO(VALIDATE_IMAGEDESC_ATTACHMENT_RESOLVE_EXPECT_NO_MSAA, "resolve attachment images cannot be multisampled") \ + _SG_LOGITEM_XMACRO(VALIDATE_IMAGEDESC_ATTACHMENT_NO_MSAA_SUPPORT, "multisampling not supported for this pixel format") \ + _SG_LOGITEM_XMACRO(VALIDATE_IMAGEDESC_ATTACHMENT_MSAA_NUM_MIPMAPS, "multisample images must have num_mipmaps == 1") \ + _SG_LOGITEM_XMACRO(VALIDATE_IMAGEDESC_ATTACHMENT_MSAA_3D_IMAGE, "3D images cannot have a sample_count > 1") \ + _SG_LOGITEM_XMACRO(VALIDATE_IMAGEDESC_ATTACHMENT_MSAA_CUBE_IMAGE, "cube images cannot have sample_count > 1") \ + _SG_LOGITEM_XMACRO(VALIDATE_IMAGEDESC_ATTACHMENT_MSAA_ARRAY_IMAGE, "array images cannot have sample_count > 1") \ + _SG_LOGITEM_XMACRO(VALIDATE_IMAGEDESC_STORAGEIMAGE_PIXELFORMAT, "invalid pixel format for storage image") \ + _SG_LOGITEM_XMACRO(VALIDATE_IMAGEDESC_STORAGEIMAGE_EXPECT_NO_MSAA, "storage images cannot be multisampled") \ + _SG_LOGITEM_XMACRO(VALIDATE_IMAGEDESC_INJECTED_NO_DATA, "images with injected textures cannot be initialized with data") \ + _SG_LOGITEM_XMACRO(VALIDATE_IMAGEDESC_DYNAMIC_NO_DATA, "dynamic/stream-update images cannot be initialized with data") \ + _SG_LOGITEM_XMACRO(VALIDATE_IMAGEDESC_COMPRESSED_IMMUTABLE, "compressed images must be immutable") \ + _SG_LOGITEM_XMACRO(VALIDATE_SAMPLERDESC_CANARY, "sg_sampler_desc not initialized") \ + _SG_LOGITEM_XMACRO(VALIDATE_SAMPLERDESC_ANISTROPIC_REQUIRES_LINEAR_FILTERING, "sg_sampler_desc.max_anisotropy > 1 requires min/mag/mipmap_filter to be SG_FILTER_LINEAR") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_CANARY, "sg_shader_desc not initialized") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_VERTEX_SOURCE, "vertex shader source code expected") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_FRAGMENT_SOURCE, "fragment shader source code expected") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_COMPUTE_SOURCE, "compute shader source code expected") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_VERTEX_SOURCE_OR_BYTECODE, "vertex shader source or byte code expected") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_FRAGMENT_SOURCE_OR_BYTECODE, "fragment shader source or byte code expected") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_COMPUTE_SOURCE_OR_BYTECODE, "compute shader source or byte code expected") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_INVALID_SHADER_COMBO, "cannot combine compute shaders with vertex or fragment shaders") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_NO_BYTECODE_SIZE, "shader byte code length (in bytes) required") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_METAL_THREADS_PER_THREADGROUP_INITIALIZED, "sg_shader_desc.mtl_threads_per_threadgroup must be initialized for compute shaders (metal)") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_METAL_THREADS_PER_THREADGROUP_MULTIPLE_32, "sg_shader_desc.mtl_threads_per_threadgroup (x * y * z) must be a multiple of 32 (metal)") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_UNIFORMBLOCK_NO_CONT_MEMBERS, "sg_shader_desc.uniform_blocks[].glsl_uniforms[]: items must occupy continuous slots") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_UNIFORMBLOCK_SIZE_IS_ZERO, "sg_shader_desc.uniform_blocks[].size cannot be zero") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_UNIFORMBLOCK_METAL_BUFFER_SLOT_COLLISION, "sg_shader_desc.uniform_blocks[].msl_buffer_n must be unique across uniform blocks and storage buffers in same shader stage") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_UNIFORMBLOCK_HLSL_REGISTER_B_COLLISION, "sg_shader_desc.uniform_blocks[].hlsl_register_b_n must be unique across uniform blocks in same shader stage") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_UNIFORMBLOCK_WGSL_GROUP0_BINDING_COLLISION, "sg_shader_desc.uniform_blocks[].wgsl_group0_binding_n must be unique across all uniform blocks") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_UNIFORMBLOCK_NO_MEMBERS, "sg_shader_desc.uniform_blocks[].glsl_uniforms[]: GL backend requires uniform block member declarations") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_UNIFORMBLOCK_UNIFORM_GLSL_NAME, "sg_shader_desc.uniform_blocks[].glsl_uniforms[].glsl_name missing") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_UNIFORMBLOCK_SIZE_MISMATCH, "sg_shader_desc.uniform_blocks[].glsl_uniforms[]: size of uniform block members doesn't match uniform block size") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_UNIFORMBLOCK_ARRAY_COUNT, "sg_shader_desc.uniform_blocks[].glsl_uniforms[].array_count must be >= 1") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_UNIFORMBLOCK_STD140_ARRAY_TYPE, "sg_shader_desc.uniform_blocks[].glsl_uniforms[].type: uniform arrays only allowed for FLOAT4, INT4, MAT4 in std140 layout") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_VIEW_STORAGEBUFFER_METAL_BUFFER_SLOT_COLLISION, "sg_shader_desc.views[].storage_buffer.storagemsl_buffer_n must be unique across uniform blocks and storage buffer in same shader stage") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_VIEW_STORAGEBUFFER_HLSL_REGISTER_T_COLLISION, "sg_shader_desc.views[].storage_buffer.hlsl_register_t_n must be unique across read-only storage buffers and images in same shader stage") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_VIEW_STORAGEBUFFER_HLSL_REGISTER_U_COLLISION, "sg_shader_desc.views[].storage_buffer.hlsl_register_u_n must be unique across read/write storage buffers and storage images in same shader stage") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_VIEW_STORAGEBUFFER_GLSL_BINDING_COLLISION, "sg_shader_desc.views[].storage_buffer.glsl_binding_n must be unique across shader stages") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_VIEW_STORAGEBUFFER_WGSL_GROUP1_BINDING_COLLISION, "sg_shader_desc.views[].storage_buffer.wgsl_group1_binding_n must be unique across all images, samplers and storage buffers") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_VIEW_STORAGEIMAGE_EXPECT_COMPUTE_STAGE, "sg_shader_desc.views[].storage_image: storage images are allowed on the compute stage") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_VIEW_STORAGEIMAGE_METAL_TEXTURE_SLOT_COLLISION, "sg_shader_desc.views[].storage_image.msl_texture_n must be unique across images and storage images in same shader stage") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_VIEW_STORAGEIMAGE_HLSL_REGISTER_U_COLLISION, "sg_shader_desc.views[].storage_image.hlsl_register_u_n must be unique across storage images and read/write storage buffers in same shader stage") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_VIEW_STORAGEIMAGE_GLSL_BINDING_COLLISION, "sg_shader_desc.views[].storage_image.glsl_binding_n must be unique across shader stages") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_VIEW_STORAGEIMAGE_WGSL_GROUP1_BINDING_COLLISION, "sg_shader_desc.views[].storage_image.wgsl_group1_binding_n must be unique in same shader stage") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_VIEW_TEXTURE_METAL_TEXTURE_SLOT_COLLISION, "sg_shader_desc.views[].texture.msl_texture_n must be unique across textures and storage images in same shader stage") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_VIEW_TEXTURE_HLSL_REGISTER_T_COLLISION, "sg_shader_desc.views[].texture.hlsl_register_t_n must be unique across textures and storage buffers in same shader stage") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_VIEW_TEXTURE_WGSL_GROUP1_BINDING_COLLISION, "sg_shader_desc.views[].texture.wgsl_group1_binding_n must be unique across all images, samplers and storage buffers") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_SAMPLER_METAL_SAMPLER_SLOT_COLLISION, "sg_shader_desc.samplers[].msl_sampler_n must be unique in same shader stage") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_SAMPLER_HLSL_REGISTER_S_COLLISION, "sg_shader_desc.samplers[].hlsl_register_s_n must be unique in same shader stage") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_SAMPLER_WGSL_GROUP1_BINDING_COLLISION, "sg_shader_desc.samplers[].wgsl_group1_binding_n must be unique across all images, samplers and storage buffers") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_TEXTURE_SAMPLER_PAIR_VIEW_SLOT_OUT_OF_RANGE, "texture-sampler-pair view slot index is out of range (sg_shader_desc.texture_sampler_pairs[].view_slot)") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_TEXTURE_SAMPLER_PAIR_SAMPLER_SLOT_OUT_OF_RANGE, "texture-sampler-pair sampler slot index is out of range (sg_shader_desc.texture_sampler_pairs[].sampler_slot)") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_TEXTURE_SAMPLER_PAIR_TEXTURE_STAGE_MISMATCH, "texture-sampler-pair stage doesn't match referenced texture stage") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_TEXTURE_SAMPLER_PAIR_EXPECT_TEXTURE_VIEW, "texture-sampler-pair view must be a texture view (sg_shader_desc.texture_sampler_pairs[].view_slot => sg_shaders_desc.views[i].texture)") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_TEXTURE_SAMPLER_PAIR_SAMPLER_STAGE_MISMATCH, "texture-sampler-pair stage doesn't match referenced sampler stage") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_TEXTURE_SAMPLER_PAIR_GLSL_NAME, "texture-sampler-pair 'glsl_name' missing") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_NONFILTERING_SAMPLER_REQUIRED, "image sample type UNFILTERABLE_FLOAT, UINT, SINT can only be used with NONFILTERING sampler") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_COMPARISON_SAMPLER_REQUIRED, "image sample type DEPTH can only be used with COMPARISON sampler") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_TEXVIEW_NOT_REFERENCED_BY_TEXTURE_SAMPLER_PAIRS, "one or more texture views are not referenced by by texture-sampler-pairs (sg_shader_desc.texture_sampler_pairs[].view_slot)") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_SAMPLER_NOT_REFERENCED_BY_TEXTURE_SAMPLER_PAIRS, "one or more samplers are not referenced by texture-sampler-pairs (sg_shader_desc.texture_sampler_pairs[].sampler_slot)") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_ATTR_STRING_TOO_LONG, "vertex attribute name/semantic string too long (max len 16)") \ + _SG_LOGITEM_XMACRO(VALIDATE_PIPELINEDESC_CANARY, "sg_pipeline_desc not initialized") \ + _SG_LOGITEM_XMACRO(VALIDATE_PIPELINEDESC_SHADER, "sg_pipeline_desc.shader missing or invalid") \ + _SG_LOGITEM_XMACRO(VALIDATE_PIPELINEDESC_COMPUTE_SHADER_EXPECTED, "sg_pipeline_desc.shader must be a compute shader") \ + _SG_LOGITEM_XMACRO(VALIDATE_PIPELINEDESC_NO_COMPUTE_SHADER_EXPECTED, "sg_pipeline_desc.compute is false, but shader is a compute shader") \ + _SG_LOGITEM_XMACRO(VALIDATE_PIPELINEDESC_NO_CONT_ATTRS, "sg_pipeline_desc.layout.attrs is not continuous") \ + _SG_LOGITEM_XMACRO(VALIDATE_PIPELINEDESC_ATTR_BASETYPE_MISMATCH, "sg_pipeline_desc.layout.attrs[].format is incompatible with sg_shader_desc.attrs[].base_type") \ + _SG_LOGITEM_XMACRO(VALIDATE_PIPELINEDESC_LAYOUT_STRIDE4, "sg_pipeline_desc.layout.buffers[].stride must be multiple of 4") \ + _SG_LOGITEM_XMACRO(VALIDATE_PIPELINEDESC_ATTR_SEMANTICS, "D3D11 missing vertex attribute semantics in shader") \ + _SG_LOGITEM_XMACRO(VALIDATE_PIPELINEDESC_SHADER_READONLY_STORAGEBUFFERS, "sg_pipeline_desc.shader: only readonly storage buffer bindings allowed in render pipelines") \ + _SG_LOGITEM_XMACRO(VALIDATE_PIPELINEDESC_BLENDOP_MINMAX_REQUIRES_BLENDFACTOR_ONE, "SG_BLENDOP_MIN/MAX requires all blend factors to be SG_BLENDFACTOR_ONE") \ + _SG_LOGITEM_XMACRO(VALIDATE_VIEWDESC_CANARY, "sg_view_desc not initialized") \ + _SG_LOGITEM_XMACRO(VALIDATE_VIEWDESC_UNIQUE_VIEWTYPE, "sg_view_desc: only one view type can be active") \ + _SG_LOGITEM_XMACRO(VALIDATE_VIEWDESC_ANY_VIEWTYPE, "sg_view_desc: exactly one view type must be active") \ + _SG_LOGITEM_XMACRO(VALIDATE_VIEWDESC_RESOURCE_ALIVE, "sg_view_desc: resource object is no longer alive (.buffer or .image)") \ + _SG_LOGITEM_XMACRO(VALIDATE_VIEWDESC_RESOURCE_FAILED, "sg_view_desc: resource object cannot be in FAILED state (.buffer or .image)") \ + _SG_LOGITEM_XMACRO(VALIDATE_VIEWDESC_STORAGEBUFFER_OFFSET_VS_BUFFER_SIZE, "sg_view_desc.storage_buffer.offset is >= buffer size") \ + _SG_LOGITEM_XMACRO(VALIDATE_VIEWDESC_STORAGEBUFFER_OFFSET_MULTIPLE_256, "sg_view_desc.storage_buffer.offset must be a multiple of 256") \ + _SG_LOGITEM_XMACRO(VALIDATE_VIEWDESC_STORAGEBUFFER_USAGE, "sg_view_desc.storage_buffer.buffer must have been created with sg_buffer_desc.usage.storage_buffer = true") \ + _SG_LOGITEM_XMACRO(VALIDATE_VIEWDESC_STORAGEIMAGE_USAGE, "sg_view_desc.storage_image.image must have been created with sg_image_desc.usage.storage_image = true") \ + _SG_LOGITEM_XMACRO(VALIDATE_VIEWDESC_COLORATTACHMENT_USAGE, "sg_view_desc.color_attachment.image must have been created with sg_image_desc.usage.color_attachment = true") \ + _SG_LOGITEM_XMACRO(VALIDATE_VIEWDESC_RESOLVEATTACHMENT_USAGE, "sg_view_desc.resolve_attachment.image must have been created with sg_image_desc.usage.resolve_attachment = true") \ + _SG_LOGITEM_XMACRO(VALIDATE_VIEWDESC_DEPTHSTENCILATTACHMENT_USAGE, "sg_view_desc.depth_stencil_attachment.image must have been created with sg_image_desc.usage.depth_stencil_attachment = true") \ + _SG_LOGITEM_XMACRO(VALIDATE_VIEWDESC_IMAGE_MIPLEVEL, "sg_view_desc: image/attachment view mip level is out of range (must be >=0 and =0 and <6)") \ + _SG_LOGITEM_XMACRO(VALIDATE_VIEWDESC_IMAGE_ARRAY_SLICE, "sg_view_desc: image/attachment view slice is out of range for 2D array image (must be >=0 and =0 and =0 and 0") \ + _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_SWAPCHAIN_EXPECT_WIDTH_NOTSET, "sg_begin_pass: expected pass.swapchain.width == 0") \ + _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_SWAPCHAIN_EXPECT_HEIGHT, "sg_begin_pass: expected pass.swapchain.height > 0") \ + _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_SWAPCHAIN_EXPECT_HEIGHT_NOTSET, "sg_begin_pass: expected pass.swapchain.height == 0") \ + _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_SWAPCHAIN_EXPECT_SAMPLECOUNT, "sg_begin_pass: expected pass.swapchain.sample_count > 0") \ + _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_SWAPCHAIN_EXPECT_SAMPLECOUNT_NOTSET, "sg_begin_pass: expected pass.swapchain.sample_count == 0") \ + _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_SWAPCHAIN_EXPECT_COLORFORMAT, "sg_begin_pass: expected pass.swapchain.color_format to be valid") \ + _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_SWAPCHAIN_EXPECT_COLORFORMAT_NOTSET, "sg_begin_pass: expected pass.swapchain.color_format to be unset") \ + _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_SWAPCHAIN_EXPECT_DEPTHFORMAT_NOTSET, "sg_begin_pass: expected pass.swapchain.depth_format to be unset") \ + _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_SWAPCHAIN_METAL_EXPECT_CURRENTDRAWABLE, "sg_begin_pass: expected pass.swapchain.metal.current_drawable != 0") \ + _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_SWAPCHAIN_METAL_EXPECT_CURRENTDRAWABLE_NOTSET, "sg_begin_pass: expected pass.swapchain.metal.current_drawable == 0") \ + _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_SWAPCHAIN_METAL_EXPECT_DEPTHSTENCILTEXTURE, "sg_begin_pass: expected pass.swapchain.metal.depth_stencil_texture != 0") \ + _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_SWAPCHAIN_METAL_EXPECT_DEPTHSTENCILTEXTURE_NOTSET, "sg_begin_pass: expected pass.swapchain.metal.depth_stencil_texture == 0") \ + _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_SWAPCHAIN_METAL_EXPECT_MSAACOLORTEXTURE, "sg_begin_pass: expected pass.swapchain.metal.msaa_color_texture != 0") \ + _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_SWAPCHAIN_METAL_EXPECT_MSAACOLORTEXTURE_NOTSET, "sg_begin_pass: expected pass.swapchain.metal.msaa_color_texture == 0") \ + _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_SWAPCHAIN_D3D11_EXPECT_RENDERVIEW, "sg_begin_pass: expected pass.swapchain.d3d11.render_view != 0") \ + _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_SWAPCHAIN_D3D11_EXPECT_RENDERVIEW_NOTSET, "sg_begin_pass: expected pass.swapchain.d3d11.render_view == 0") \ + _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_SWAPCHAIN_D3D11_EXPECT_RESOLVEVIEW, "sg_begin_pass: expected pass.swapchain.d3d11.resolve_view != 0") \ + _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_SWAPCHAIN_D3D11_EXPECT_RESOLVEVIEW_NOTSET, "sg_begin_pass: expected pass.swapchain.d3d11.resolve_view == 0") \ + _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_SWAPCHAIN_D3D11_EXPECT_DEPTHSTENCILVIEW, "sg_begin_pass: expected pass.swapchain.d3d11.depth_stencil_view != 0") \ + _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_SWAPCHAIN_D3D11_EXPECT_DEPTHSTENCILVIEW_NOTSET, "sg_begin_pass: expected pass.swapchain.d3d11.depth_stencil_view == 0") \ + _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_SWAPCHAIN_WGPU_EXPECT_RENDERVIEW, "sg_begin_pass: expected pass.swapchain.wgpu.render_view != 0") \ + _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_SWAPCHAIN_WGPU_EXPECT_RENDERVIEW_NOTSET, "sg_begin_pass: expected pass.swapchain.wgpu.render_view == 0") \ + _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_SWAPCHAIN_WGPU_EXPECT_RESOLVEVIEW, "sg_begin_pass: expected pass.swapchain.wgpu.resolve_view != 0") \ + _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_SWAPCHAIN_WGPU_EXPECT_RESOLVEVIEW_NOTSET, "sg_begin_pass: expected pass.swapchain.wgpu.resolve_view == 0") \ + _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_SWAPCHAIN_WGPU_EXPECT_DEPTHSTENCILVIEW, "sg_begin_pass: expected pass.swapchain.wgpu.depth_stencil_view != 0") \ + _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_SWAPCHAIN_WGPU_EXPECT_DEPTHSTENCILVIEW_NOTSET, "sg_begin_pass: expected pass.swapchain.wgpu.depth_stencil_view == 0") \ + _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_SWAPCHAIN_GL_EXPECT_FRAMEBUFFER_NOTSET, "sg_begin_pass: expected pass.swapchain.gl.framebuffer == 0") \ + _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_COLORATTACHMENTVIEWS_CONTINUOUS, "sg_begin_pass: color attachment view array must be continuous") \ + _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_COLORATTACHMENTVIEW_ALIVE, "sg_begin_pass: color attachment view no longer alive") \ + _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_COLORATTACHMENTVIEW_VALID, "sg_begin_pass: color attachment view not in valid state (SG_RESOURCESTATE_VALID)") \ + _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_COLORATTACHMENTVIEW_TYPE, "sg_begin_pass: color attachment view has wrong type (must be sg_view_desc.color_attachment)") \ + _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_COLORATTACHMENTVIEW_IMAGE_ALIVE, "sg_begin_pass: color attachment view's image object is uninitialized or no longer alive") \ + _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_COLORATTACHMENTVIEW_IMAGE_VALID, "sg_begin_pass: color attachment view's image is not in valid state (SG_RESOURCESTATE_VALID)") \ + _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_COLORATTACHMENTVIEW_SIZES, "sg_begin_pass: all color attachments must have the same width and height") \ + _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_COLORATTACHMENTVIEW_SAMPLECOUNT, "sg_begin_pass: when resolve attachments are provided, the color attachment sample count must be 1") \ + _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_COLORATTACHMENTVIEW_SAMPLECOUNTS_EQUAL, "sg_begin_pass: all color attachments must have the same sample count") \ + _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_RESOLVEATTACHMENTVIEW_NO_COLORATTACHMENTVIEW, "sg_begin_pass: a resolve attachment view must have an associated color attachment view at the same index") \ + _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_RESOLVEATTACHMENTVIEW_ALIVE, "sg_begin_pass: resolve attachment view no longer alive") \ + _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_RESOLVEATTACHMENTVIEW_VALID, "sg_begin_pass: resolve attachment view not in valid state (SG_RESOURCESTATE_VALID)") \ + _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_RESOLVEATTACHMENTVIEW_TYPE, "sg_begin_pass: resolve attachment view has wrong type (must be sg_view_desc.resolve_attachment)") \ + _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_RESOLVEATTACHMENTVIEW_IMAGE_ALIVE, "sg_begin_pass: resolve attachment view's image object is uninitialized or no longer alive") \ + _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_RESOLVEATTACHMENTVIEW_IMAGE_VALID, "sg_begin_pass: resolve attachment view's image is not in valid state (SG_RESOURCESTATE_VALID)") \ + _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_RESOLVEATTACHMENTVIEW_SIZES, "sg_begin_pass: all attachments must have the same width and height") \ + _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_DEPTHSTENCILATTACHMENTVIEWS_CONTINUOUS, "sg_begin_pass: color attachment view array must be continuous") \ + _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_DEPTHSTENCILATTACHMENTVIEW_ALIVE, "sg_begin_pass: depth-stencil attachment view no longer alive") \ + _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_DEPTHSTENCILATTACHMENTVIEW_VALID, "sg_begin_pass: depth-stencil attachment view not in valid state (SG_RESOURCESTATE_VALID)") \ + _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_DEPTHSTENCILATTACHMENTVIEW_TYPE, "sg_begin_pass: depth-stencil attachment view has wrong type (must be sg_view_desc.depth_stencil_attachment)") \ + _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_DEPTHSTENCILATTACHMENTVIEW_IMAGE_ALIVE, "sg_begin_pass: depth-stencil attachment view's image object is uninitialized or no longer alive") \ + _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_DEPTHSTENCILATTACHMENTVIEW_IMAGE_VALID, "sg_begin_pass: depth-stencil attachment view's image is not in valid state (SG_RESOURCESTATE_VALID)") \ + _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_DEPTHSTENCILATTACHMENTVIEW_SIZES, "sg_begin_pass: attachments must have the same width and height") \ + _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_DEPTHSTENCILATTACHMENTVIEW_SAMPLECOUNT, "sg_begin_pass: all color attachments must have the same sample count") \ + _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_ATTACHMENTS_EXPECTED, "sg_begin_pass: offscreen render passes must have at least one color- or depth-stencil attachment") \ + _SG_LOGITEM_XMACRO(VALIDATE_AVP_RENDERPASS_EXPECTED, "sg_apply_viewport: must be called in a render pass") \ + _SG_LOGITEM_XMACRO(VALIDATE_ASR_RENDERPASS_EXPECTED, "sg_apply_scissor_rect: must be called in a render pass") \ + _SG_LOGITEM_XMACRO(VALIDATE_APIP_PIPELINE_VALID_ID, "sg_apply_pipeline: invalid pipeline id provided") \ + _SG_LOGITEM_XMACRO(VALIDATE_APIP_PIPELINE_EXISTS, "sg_apply_pipeline: pipeline object no longer alive") \ + _SG_LOGITEM_XMACRO(VALIDATE_APIP_PIPELINE_VALID, "sg_apply_pipeline: pipeline object not in valid state (SG_RESOURCESTATE_VALID)") \ + _SG_LOGITEM_XMACRO(VALIDATE_APIP_PASS_EXPECTED, "sg_apply_pipeline: must be called in a pass") \ + _SG_LOGITEM_XMACRO(VALIDATE_APIP_PIPELINE_SHADER_ALIVE, "sg_apply_pipeline: shader object associated with pipeline no longer alive") \ + _SG_LOGITEM_XMACRO(VALIDATE_APIP_PIPELINE_SHADER_VALID, "sg_apply_pipeline: shader object associated with pipeline not in valid state") \ + _SG_LOGITEM_XMACRO(VALIDATE_APIP_COMPUTEPASS_EXPECTED, "sg_apply_pipeline: trying to apply compute pipeline in render pass") \ + _SG_LOGITEM_XMACRO(VALIDATE_APIP_RENDERPASS_EXPECTED, "sg_apply_pipeline: trying to apply render pipeline in compute pass") \ + _SG_LOGITEM_XMACRO(VALIDATE_APIP_SWAPCHAIN_COLOR_COUNT, "sg_apply_pipeline: the pipeline .color_count must be 1 in swapchain render passes") \ + _SG_LOGITEM_XMACRO(VALIDATE_APIP_SWAPCHAIN_COLOR_FORMAT, "sg_apply_pipeline: the pipeline .colors[0].pixel_format doesn't match the sg_pass.swapchain.color_format") \ + _SG_LOGITEM_XMACRO(VALIDATE_APIP_SWAPCHAIN_DEPTH_FORMAT, "sg_apply_pipeline: the pipeline .depth.pixel_format doesn't match the sg_pass.swapchain.depth_format") \ + _SG_LOGITEM_XMACRO(VALIDATE_APIP_SWAPCHAIN_SAMPLE_COUNT, "sg_apply_pipeline: the pipeline .sample_count doesn't match the sg_pass.swapchain.sample_count") \ + _SG_LOGITEM_XMACRO(VALIDATE_APIP_ATTACHMENTS_ALIVE, "sg_apply_pipeline: at least one pass attachment view or base image object is no longer alive") \ + _SG_LOGITEM_XMACRO(VALIDATE_APIP_COLORATTACHMENTS_COUNT, "sg_apply_pipeline: the pipeline .color_count doesn't match the number of render pass color attachments") \ + _SG_LOGITEM_XMACRO(VALIDATE_APIP_COLORATTACHMENTS_VIEW_VALID, "sg_apply_pipeline: a pass color attachment view is not in valid state (SG_RESOURCESTATE_VALID)") \ + _SG_LOGITEM_XMACRO(VALIDATE_APIP_COLORATTACHMENTS_IMAGE_VALID, "sg_apply_pipeline: a pass color attachment view's image object is not in valid state (SG_RESOURCESTATE_VALID)") \ + _SG_LOGITEM_XMACRO(VALIDATE_APIP_COLORATTACHMENTS_FORMAT, "sg_apply_pipeline: a pipeline .colors[n].pixel_format doesn't match sg_pass.attachments.colors[n] image pixel format") \ + _SG_LOGITEM_XMACRO(VALIDATE_APIP_DEPTHSTENCILATTACHMENT_VIEW_VALID, "sg_apply_pipeline: the pass depth-stencil attachment view is not in valid state (SG_RESOURCESTATE_VALID)") \ + _SG_LOGITEM_XMACRO(VALIDATE_APIP_DEPTHSTENCILATTACHMENT_IMAGE_VALID, "sg_apply_pipeline: the pass depth-stencil attachment view's image object is not in valid state (SG_RESOURCESTATE_VALID)") \ + _SG_LOGITEM_XMACRO(VALIDATE_APIP_DEPTHSTENCILATTACHMENT_FORMAT, "sg_apply_pipeline: pipeline .depth.pixel_format doesn't match sg_pass.attachments.depth_stencil image pixel format") \ + _SG_LOGITEM_XMACRO(VALIDATE_APIP_ATTACHMENT_SAMPLE_COUNT, "sg_apply_pipeline: pipeline MSAA sample count doesn't match pass attachment sample count") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_PASS_EXPECTED, "sg_apply_bindings: must be called in a pass") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_EMPTY_BINDINGS, "sg_apply_bindings: the provided sg_bindings struct is empty") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_NO_PIPELINE, "sg_apply_bindings: must be called after sg_apply_pipeline") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_PIPELINE_ALIVE, "sg_apply_bindings: currently applied pipeline object no longer alive") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_PIPELINE_VALID, "sg_apply_bindings: currently applied pipeline object not in valid state") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_PIPELINE_SHADER_ALIVE, "sg_apply_bindings: shader associated with currently applied pipeline is no longer alive") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_PIPELINE_SHADER_VALID, "sg_apply_bindings: shader associated with currently applied pipeline is not in valid state") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_COMPUTE_EXPECTED_NO_VBUFS, "sg_apply_bindings: vertex buffer bindings not allowed in a compute pass") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_COMPUTE_EXPECTED_NO_IBUF, "sg_apply_bindings: index buffer binding not allowed in compute pass") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_EXPECTED_VBUF, "sg_apply_bindings: vertex buffer binding is missing or buffer handle is invalid") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_VBUF_ALIVE, "sg_apply_bindings: vertex buffer no longer alive") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_VBUF_USAGE, "sg_apply_bindings: buffer in vertex buffer bind slot must have usage.vertex_buffer") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_VBUF_OVERFLOW, "sg_apply_bindings: buffer in vertex buffer bind slot is overflown") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_EXPECTED_NO_IBUF, "sg_apply_bindings: pipeline object defines non-indexed rendering, but index buffer binding provided") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_EXPECTED_IBUF, "sg_apply_bindings: pipeline object defines indexed rendering, but no index buffer binding provided") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_IBUF_ALIVE, "sg_apply_bindings: index buffer no longer alive") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_IBUF_USAGE, "sg_apply_bindings: buffer in index buffer bind slot must have usage.index_buffer") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_IBUF_OVERFLOW, "sg_apply_bindings: buffer in index buffer slot is overflown") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_EXPECTED_VIEW_BINDING, "sg_apply_bindings: view binding is missing or the view handle is invalid") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_VIEW_ALIVE, "sg_apply_bindings: view no longer alive") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_EXPECT_TEXVIEW, "sg_apply_bindings: view type mismatch in bindslot (shader expects a texture view)") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_EXPECT_SBVIEW, "sg_apply_bindings: view type mismatch in bindslot (shader expects a storage buffer view)") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_EXPECT_SIMGVIEW, "sg_apply_bindings: view type mismatch in bindslot (shader expects a storage image view)") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_TEXVIEW_IMAGETYPE_MISMATCH, "sg_apply_bindings: image type of bound texture doesn't match shader desc") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_TEXVIEW_EXPECTED_MULTISAMPLED_IMAGE, "sg_apply_bindings: texture bindings expects image with sample_count > 1") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_TEXVIEW_EXPECTED_NON_MULTISAMPLED_IMAGE, "sg_apply_bindings: texture bindings expects image with sample_count == 1") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_TEXVIEW_EXPECTED_FILTERABLE_IMAGE, "sg_apply_bindings: filterable image expected") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_TEXVIEW_EXPECTED_DEPTH_IMAGE, "sg_apply_bindings: depth image expected") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_SBVIEW_READWRITE_IMMUTABLE, "sg_apply_bindings: storage buffers bound as read/write must have usage immutable") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_SIMGVIEW_COMPUTE_PASS_EXPECTED, "sg_apply_bindings: storage image bindings can only appear on compute passes") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_SIMGVIEW_IMAGETYPE_MISMATCH, "sg_apply_bindings: image type of bound storage image doesn't match shader desc") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_SIMGVIEW_ACCESSFORMAT, "sg_apply_bindings: pixel format of storage image view doesn't match access format in shader desc") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_EXPECTED_SAMPLER_BINDING, "sg_apply_bindings: sampler binding is missing or the sampler handle is invalid") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_UNEXPECTED_SAMPLER_COMPARE_NEVER, "sg_apply_bindings: shader expects SG_SAMPLERTYPE_COMPARISON but sampler has SG_COMPAREFUNC_NEVER") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_EXPECTED_SAMPLER_COMPARE_NEVER, "sg_apply_bindings: shader expects SG_SAMPLERTYPE_FILTERING or SG_SAMPLERTYPE_NONFILTERING but sampler doesn't have SG_COMPAREFUNC_NEVER") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_EXPECTED_NONFILTERING_SAMPLER, "sg_apply_bindings: shader expected SG_SAMPLERTYPE_NONFILTERING, but sampler has SG_FILTER_LINEAR filters") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_SAMPLER_ALIVE, "sg_apply_bindings: bound sampler no longer alive") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_SAMPLER_VALID, "sg_apply_bindings: bound sampler not in valid state") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_TEXTURE_BINDING_VS_DEPTHSTENCIL_ATTACHMENT, "sg_apply_bindings: cannot bind texture in the same pass it is used as depth-stencil attachment") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_TEXTURE_BINDING_VS_COLOR_ATTACHMENT, "sg_apply_bindings: cannot bind texture in the same pass it is used as color attachment") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_TEXTURE_BINDING_VS_RESOLVE_ATTACHMENT, "sg_apply_bindings: cannot bind texture in the same pass it is used as resolve attachment") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_TEXTURE_VS_STORAGEIMAGE_BINDING, "sg_apply_bindings: an image cannot be bound as a texture and storage image at the same time") \ + _SG_LOGITEM_XMACRO(VALIDATE_AU_PASS_EXPECTED, "sg_apply_uniforms: must be called in a pass") \ + _SG_LOGITEM_XMACRO(VALIDATE_AU_NO_PIPELINE, "sg_apply_uniforms: must be called after sg_apply_pipeline()") \ + _SG_LOGITEM_XMACRO(VALIDATE_AU_PIPELINE_ALIVE, "sg_apply_uniforms: currently applied pipeline object no longer alive") \ + _SG_LOGITEM_XMACRO(VALIDATE_AU_PIPELINE_VALID, "sg_apply_uniforms: currently applied pipeline object not in valid state") \ + _SG_LOGITEM_XMACRO(VALIDATE_AU_PIPELINE_SHADER_ALIVE, "sg_apply_uniforms: shader associated with currently applied pipeline is no longer alive") \ + _SG_LOGITEM_XMACRO(VALIDATE_AU_PIPELINE_SHADER_VALID, "sg_apply_uniforms: shader associated with currently applied pipeline is not in valid state") \ + _SG_LOGITEM_XMACRO(VALIDATE_AU_NO_UNIFORMBLOCK_AT_SLOT, "sg_apply_uniforms: no uniform block declaration at this shader stage UB slot") \ + _SG_LOGITEM_XMACRO(VALIDATE_AU_SIZE, "sg_apply_uniforms: data size doesn't match declared uniform block size") \ + _SG_LOGITEM_XMACRO(VALIDATE_DRAW_RENDERPASS_EXPECTED, "sg_draw: must be called in a render pass") \ + _SG_LOGITEM_XMACRO(VALIDATE_DRAW_BASEELEMENT_GE_ZERO, "sg_draw: base_element cannot be < 0") \ + _SG_LOGITEM_XMACRO(VALIDATE_DRAW_NUMELEMENTS_GE_ZERO, "sg_draw: num_elements cannot be < 0") \ + _SG_LOGITEM_XMACRO(VALIDATE_DRAW_NUMINSTANCES_GE_ZERO, "sg_draw: num_instances cannot be < 0") \ + _SG_LOGITEM_XMACRO(VALIDATE_DRAW_EX_RENDERPASS_EXPECTED, "sg_draw: must be called in a render pass") \ + _SG_LOGITEM_XMACRO(VALIDATE_DRAW_EX_BASEELEMENT_GE_ZERO, "sg_draw_ex: base_element cannot be < 0") \ + _SG_LOGITEM_XMACRO(VALIDATE_DRAW_EX_NUMELEMENTS_GE_ZERO, "sg_draw_ex: num_elements cannot be < 0") \ + _SG_LOGITEM_XMACRO(VALIDATE_DRAW_EX_NUMINSTANCES_GE_ZERO, "sg_draw_ex: num_instances cannot be < 0") \ + _SG_LOGITEM_XMACRO(VALIDATE_DRAW_EX_BASEINSTANCE_GE_ZERO, "sg_draw_ex: base_instance cannot be < 0") \ + _SG_LOGITEM_XMACRO(VALIDATE_DRAW_EX_BASEVERTEX_VS_INDEXED, "sg_draw_ex(): base_vertex must be == 0 for non-indexed rendering") \ + _SG_LOGITEM_XMACRO(VALIDATE_DRAW_EX_BASEINSTANCE_VS_INSTANCED, "sg_draw_ex(): base_instance must be == 0 for non-instanced rendering") \ + _SG_LOGITEM_XMACRO(VALIDATE_DRAW_EX_BASEVERTEX_NOT_SUPPORTED, "sg_draw_ex(): base_vertex != 0 not supported on this backend (sg_features.draw_base_vertex)") \ + _SG_LOGITEM_XMACRO(VALIDATE_DRAW_EX_BASEINSTANCE_NOT_SUPPORTED, "sg_draw_ex(): base_instance > 0 not supported on this backend (sg_features.draw_base_instance)") \ + _SG_LOGITEM_XMACRO(VALIDATE_DRAW_REQUIRED_BINDINGS_OR_UNIFORMS_MISSING, "sg_draw: call to sg_apply_bindings() and/or sg_apply_uniforms() missing after sg_apply_pipeline()") \ + _SG_LOGITEM_XMACRO(VALIDATE_DISPATCH_COMPUTEPASS_EXPECTED, "sg_dispatch: must be called in a compute pass") \ + _SG_LOGITEM_XMACRO(VALIDATE_DISPATCH_NUMGROUPSX, "sg_dispatch: num_groups_x must be >=0 and <65536") \ + _SG_LOGITEM_XMACRO(VALIDATE_DISPATCH_NUMGROUPSY, "sg_dispatch: num_groups_y must be >=0 and <65536") \ + _SG_LOGITEM_XMACRO(VALIDATE_DISPATCH_NUMGROUPSZ, "sg_dispatch: num_groups_z must be >=0 and <65536") \ + _SG_LOGITEM_XMACRO(VALIDATE_DISPATCH_REQUIRED_BINDINGS_OR_UNIFORMS_MISSING, "sg_dispatch: call to sg_apply_bindings() and/or sg_apply_uniforms() missing after sg_apply_pipeline()") \ + _SG_LOGITEM_XMACRO(VALIDATE_UPDATEBUF_USAGE, "sg_update_buffer: cannot update immutable buffer") \ + _SG_LOGITEM_XMACRO(VALIDATE_UPDATEBUF_SIZE, "sg_update_buffer: update size is bigger than buffer size") \ + _SG_LOGITEM_XMACRO(VALIDATE_UPDATEBUF_ONCE, "sg_update_buffer: only one update allowed per buffer and frame") \ + _SG_LOGITEM_XMACRO(VALIDATE_UPDATEBUF_APPEND, "sg_update_buffer: cannot call sg_update_buffer and sg_append_buffer in same frame") \ + _SG_LOGITEM_XMACRO(VALIDATE_APPENDBUF_USAGE, "sg_append_buffer: cannot append to immutable buffer") \ + _SG_LOGITEM_XMACRO(VALIDATE_APPENDBUF_SIZE, "sg_append_buffer: overall appended size is bigger than buffer size") \ + _SG_LOGITEM_XMACRO(VALIDATE_APPENDBUF_UPDATE, "sg_append_buffer: cannot call sg_append_buffer and sg_update_buffer in same frame") \ + _SG_LOGITEM_XMACRO(VALIDATE_UPDIMG_USAGE, "sg_update_image: cannot update immutable image") \ + _SG_LOGITEM_XMACRO(VALIDATE_UPDIMG_ONCE, "sg_update_image: only one update allowed per image and frame") \ + _SG_LOGITEM_XMACRO(VALIDATION_FAILED, "validation layer checks failed") \ + +#define _SG_LOGITEM_XMACRO(item,msg) SG_LOGITEM_##item, +typedef enum sg_log_item { + _SG_LOG_ITEMS +} sg_log_item; +#undef _SG_LOGITEM_XMACRO + +/* + sg_desc + + The sg_desc struct contains configuration values for sokol_gfx, + it is used as parameter to the sg_setup() call. + + The default configuration is: + + .buffer_pool_size 128 + .image_pool_size 128 + .sampler_pool_size 64 + .shader_pool_size 32 + .pipeline_pool_size 64 + .view_pool_size 256 + .uniform_buffer_size 4 MB (4*1024*1024) + .max_commit_listeners 1024 + .disable_validation false + .mtl_force_managed_storage_mode false + .wgpu_disable_bindgroups_cache false + .wgpu_bindgroups_cache_size 1024 + + .allocator.alloc_fn 0 (in this case, malloc() will be called) + .allocator.free_fn 0 (in this case, free() will be called) + .allocator.user_data 0 + + .environment.defaults.color_format: default value depends on selected backend: + all GL backends: SG_PIXELFORMAT_RGBA8 + Metal and D3D11: SG_PIXELFORMAT_BGRA8 + WebGPU: *no default* (must be queried from WebGPU swapchain object) + .environment.defaults.depth_format: SG_PIXELFORMAT_DEPTH_STENCIL + .environment.defaults.sample_count: 1 + + Metal specific: + (NOTE: All Objective-C object references are transferred through + a bridged cast (__bridge const void*) to sokol_gfx, which will use an + unretained bridged cast (__bridge id) to retrieve the Objective-C + references back. Since the bridge cast is unretained, the caller + must hold a strong reference to the Objective-C object until sg_setup() + returns. + + .mtl_force_managed_storage_mode + when enabled, Metal buffers and texture resources are created in managed storage + mode, otherwise sokol-gfx will decide whether to create buffers and + textures in managed or shared storage mode (this is mainly a debugging option) + .mtl_use_command_buffer_with_retained_references + when true, the sokol-gfx Metal backend will use Metal command buffers which + bump the reference count of resource objects as long as they are inflight, + this is slower than the default command-buffer-with-unretained-references + method, this may be a workaround when confronted with lifetime validation + errors from the Metal validation layer until a proper fix has been implemented + .environment.metal.device + a pointer to the MTLDevice object + + D3D11 specific: + .environment.d3d11.device + a pointer to the ID3D11Device object, this must have been created + before sg_setup() is called + .environment.d3d11.device_context + a pointer to the ID3D11DeviceContext object + .d3d11_shader_debugging + set this to true to compile shaders which are provided as HLSL source + code with debug information and without optimization, this allows + shader debugging in tools like RenderDoc, to output source code + instead of byte code from sokol-shdc, omit the `--binary` cmdline + option + + WebGPU specific: + .wgpu_disable_bindgroups_cache + When this is true, the WebGPU backend will create and immediately + release a BindGroup object in the sg_apply_bindings() call, only + use this for debugging purposes. + .wgpu_bindgroups_cache_size + The size of the bindgroups cache for re-using BindGroup objects + between sg_apply_bindings() calls. The smaller the cache size, + the more likely are cache slot collisions which will cause + a BindGroups object to be destroyed and a new one created. + Use the information returned by sg_query_stats() to check + if this is a frequent occurrence, and increase the cache size as + needed (the default is 1024). + NOTE: wgpu_bindgroups_cache_size must be a power-of-2 number! + .environment.wgpu.device + a WGPUDevice handle + + When using sokol_gfx.h and sokol_app.h together, consider using the + helper function sglue_environment() in the sokol_glue.h header to + initialize the sg_desc.environment nested struct. sglue_environment() returns + a completely initialized sg_environment struct with information + provided by sokol_app.h. +*/ +typedef struct sg_environment_defaults { + sg_pixel_format color_format; + sg_pixel_format depth_format; + int sample_count; +} sg_environment_defaults; + +typedef struct sg_metal_environment { + const void* device; +} sg_metal_environment; + +typedef struct sg_d3d11_environment { + const void* device; + const void* device_context; +} sg_d3d11_environment; + +typedef struct sg_wgpu_environment { + const void* device; +} sg_wgpu_environment; + +typedef struct sg_environment { + sg_environment_defaults defaults; + sg_metal_environment metal; + sg_d3d11_environment d3d11; + sg_wgpu_environment wgpu; +} sg_environment; + +/* + sg_commit_listener + + Used with function sg_add_commit_listener() to add a callback + which will be called in sg_commit(). This is useful for libraries + building on top of sokol-gfx to be notified about when a frame + ends (instead of having to guess, or add a manual 'new-frame' + function. +*/ +typedef struct sg_commit_listener { + void (*func)(void* user_data); + void* user_data; +} sg_commit_listener; + +/* + sg_allocator + + Used in sg_desc to provide custom memory-alloc and -free functions + to sokol_gfx.h. If memory management should be overridden, both the + alloc_fn and free_fn function must be provided (e.g. it's not valid to + override one function but not the other). +*/ +typedef struct sg_allocator { + void* (*alloc_fn)(size_t size, void* user_data); + void (*free_fn)(void* ptr, void* user_data); + void* user_data; +} sg_allocator; + +/* + sg_logger + + Used in sg_desc to provide a logging function. Please be aware + that without logging function, sokol-gfx will be completely + silent, e.g. it will not report errors, warnings and + validation layer messages. For maximum error verbosity, + compile in debug mode (e.g. NDEBUG *not* defined) and provide a + compatible logger function in the sg_setup() call + (for instance the standard logging function from sokol_log.h). +*/ +typedef struct sg_logger { + void (*func)( + const char* tag, // always "sg" + uint32_t log_level, // 0=panic, 1=error, 2=warning, 3=info + uint32_t log_item_id, // SG_LOGITEM_* + const char* message_or_null, // a message string, may be nullptr in release mode + uint32_t line_nr, // line number in sokol_gfx.h + const char* filename_or_null, // source filename, may be nullptr in release mode + void* user_data); + void* user_data; +} sg_logger; + +typedef struct sg_desc { + uint32_t _start_canary; + int buffer_pool_size; + int image_pool_size; + int sampler_pool_size; + int shader_pool_size; + int pipeline_pool_size; + int view_pool_size; + int uniform_buffer_size; + int max_commit_listeners; + bool disable_validation; // disable validation layer even in debug mode, useful for tests + bool enforce_portable_limits; // if true, enforce portable resource binding limits (SG_MAX_PORTABLE_*) + bool d3d11_shader_debugging; // if true, HLSL shaders are compiled with D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION + bool mtl_force_managed_storage_mode; // for debugging: use Metal managed storage mode for resources even with UMA + bool mtl_use_command_buffer_with_retained_references; // Metal: use a managed MTLCommandBuffer which ref-counts used resources + bool wgpu_disable_bindgroups_cache; // set to true to disable the WebGPU backend BindGroup cache + int wgpu_bindgroups_cache_size; // number of slots in the WebGPU bindgroup cache (must be 2^N) + sg_allocator allocator; + sg_logger logger; // optional log function override + sg_environment environment; + uint32_t _end_canary; +} sg_desc; + +// setup and misc functions +SOKOL_GFX_API_DECL void sg_setup(const sg_desc* desc); +SOKOL_GFX_API_DECL void sg_shutdown(void); +SOKOL_GFX_API_DECL bool sg_isvalid(void); +SOKOL_GFX_API_DECL void sg_reset_state_cache(void); +SOKOL_GFX_API_DECL sg_trace_hooks sg_install_trace_hooks(const sg_trace_hooks* trace_hooks); +SOKOL_GFX_API_DECL void sg_push_debug_group(const char* name); +SOKOL_GFX_API_DECL void sg_pop_debug_group(void); +SOKOL_GFX_API_DECL bool sg_add_commit_listener(sg_commit_listener listener); +SOKOL_GFX_API_DECL bool sg_remove_commit_listener(sg_commit_listener listener); + +// resource creation, destruction and updating +SOKOL_GFX_API_DECL sg_buffer sg_make_buffer(const sg_buffer_desc* desc); +SOKOL_GFX_API_DECL sg_image sg_make_image(const sg_image_desc* desc); +SOKOL_GFX_API_DECL sg_sampler sg_make_sampler(const sg_sampler_desc* desc); +SOKOL_GFX_API_DECL sg_shader sg_make_shader(const sg_shader_desc* desc); +SOKOL_GFX_API_DECL sg_pipeline sg_make_pipeline(const sg_pipeline_desc* desc); +SOKOL_GFX_API_DECL sg_view sg_make_view(const sg_view_desc* desc); +SOKOL_GFX_API_DECL void sg_destroy_buffer(sg_buffer buf); +SOKOL_GFX_API_DECL void sg_destroy_image(sg_image img); +SOKOL_GFX_API_DECL void sg_destroy_sampler(sg_sampler smp); +SOKOL_GFX_API_DECL void sg_destroy_shader(sg_shader shd); +SOKOL_GFX_API_DECL void sg_destroy_pipeline(sg_pipeline pip); +SOKOL_GFX_API_DECL void sg_destroy_view(sg_view view); +SOKOL_GFX_API_DECL void sg_update_buffer(sg_buffer buf, const sg_range* data); +SOKOL_GFX_API_DECL void sg_update_image(sg_image img, const sg_image_data* data); +SOKOL_GFX_API_DECL int sg_append_buffer(sg_buffer buf, const sg_range* data); +SOKOL_GFX_API_DECL bool sg_query_buffer_overflow(sg_buffer buf); +SOKOL_GFX_API_DECL bool sg_query_buffer_will_overflow(sg_buffer buf, size_t size); + +// render and compute functions +SOKOL_GFX_API_DECL void sg_begin_pass(const sg_pass* pass); +SOKOL_GFX_API_DECL void sg_apply_viewport(int x, int y, int width, int height, bool origin_top_left); +SOKOL_GFX_API_DECL void sg_apply_viewportf(float x, float y, float width, float height, bool origin_top_left); +SOKOL_GFX_API_DECL void sg_apply_scissor_rect(int x, int y, int width, int height, bool origin_top_left); +SOKOL_GFX_API_DECL void sg_apply_scissor_rectf(float x, float y, float width, float height, bool origin_top_left); +SOKOL_GFX_API_DECL void sg_apply_pipeline(sg_pipeline pip); +SOKOL_GFX_API_DECL void sg_apply_bindings(const sg_bindings* bindings); +SOKOL_GFX_API_DECL void sg_apply_uniforms(int ub_slot, const sg_range* data); +SOKOL_GFX_API_DECL void sg_draw(int base_element, int num_elements, int num_instances); +SOKOL_GFX_API_DECL void sg_draw_ex(int base_element, int num_elements, int num_instances, int base_vertex, int base_instance); +SOKOL_GFX_API_DECL void sg_dispatch(int num_groups_x, int num_groups_y, int num_groups_z); +SOKOL_GFX_API_DECL void sg_end_pass(void); +SOKOL_GFX_API_DECL void sg_commit(void); + +// getting information +SOKOL_GFX_API_DECL sg_desc sg_query_desc(void); +SOKOL_GFX_API_DECL sg_backend sg_query_backend(void); +SOKOL_GFX_API_DECL sg_features sg_query_features(void); +SOKOL_GFX_API_DECL sg_limits sg_query_limits(void); +SOKOL_GFX_API_DECL sg_pixelformat_info sg_query_pixelformat(sg_pixel_format fmt); +SOKOL_GFX_API_DECL int sg_query_row_pitch(sg_pixel_format fmt, int width, int row_align_bytes); +SOKOL_GFX_API_DECL int sg_query_surface_pitch(sg_pixel_format fmt, int width, int height, int row_align_bytes); +// get current state of a resource (INITIAL, ALLOC, VALID, FAILED, INVALID) +SOKOL_GFX_API_DECL sg_resource_state sg_query_buffer_state(sg_buffer buf); +SOKOL_GFX_API_DECL sg_resource_state sg_query_image_state(sg_image img); +SOKOL_GFX_API_DECL sg_resource_state sg_query_sampler_state(sg_sampler smp); +SOKOL_GFX_API_DECL sg_resource_state sg_query_shader_state(sg_shader shd); +SOKOL_GFX_API_DECL sg_resource_state sg_query_pipeline_state(sg_pipeline pip); +SOKOL_GFX_API_DECL sg_resource_state sg_query_view_state(sg_view view); +// get runtime information about a resource +SOKOL_GFX_API_DECL sg_buffer_info sg_query_buffer_info(sg_buffer buf); +SOKOL_GFX_API_DECL sg_image_info sg_query_image_info(sg_image img); +SOKOL_GFX_API_DECL sg_sampler_info sg_query_sampler_info(sg_sampler smp); +SOKOL_GFX_API_DECL sg_shader_info sg_query_shader_info(sg_shader shd); +SOKOL_GFX_API_DECL sg_pipeline_info sg_query_pipeline_info(sg_pipeline pip); +SOKOL_GFX_API_DECL sg_view_info sg_query_view_info(sg_view view); +// get desc structs matching a specific resource (NOTE that not all creation attributes may be provided) +SOKOL_GFX_API_DECL sg_buffer_desc sg_query_buffer_desc(sg_buffer buf); +SOKOL_GFX_API_DECL sg_image_desc sg_query_image_desc(sg_image img); +SOKOL_GFX_API_DECL sg_sampler_desc sg_query_sampler_desc(sg_sampler smp); +SOKOL_GFX_API_DECL sg_shader_desc sg_query_shader_desc(sg_shader shd); +SOKOL_GFX_API_DECL sg_pipeline_desc sg_query_pipeline_desc(sg_pipeline pip); +SOKOL_GFX_API_DECL sg_view_desc sg_query_view_desc(sg_view view); +// get resource creation desc struct with their default values replaced +SOKOL_GFX_API_DECL sg_buffer_desc sg_query_buffer_defaults(const sg_buffer_desc* desc); +SOKOL_GFX_API_DECL sg_image_desc sg_query_image_defaults(const sg_image_desc* desc); +SOKOL_GFX_API_DECL sg_sampler_desc sg_query_sampler_defaults(const sg_sampler_desc* desc); +SOKOL_GFX_API_DECL sg_shader_desc sg_query_shader_defaults(const sg_shader_desc* desc); +SOKOL_GFX_API_DECL sg_pipeline_desc sg_query_pipeline_defaults(const sg_pipeline_desc* desc); +SOKOL_GFX_API_DECL sg_view_desc sg_query_view_defaults(const sg_view_desc* desc); +// assorted query functions +SOKOL_GFX_API_DECL size_t sg_query_buffer_size(sg_buffer buf); +SOKOL_GFX_API_DECL sg_buffer_usage sg_query_buffer_usage(sg_buffer buf); +SOKOL_GFX_API_DECL sg_image_type sg_query_image_type(sg_image img); +SOKOL_GFX_API_DECL int sg_query_image_width(sg_image img); +SOKOL_GFX_API_DECL int sg_query_image_height(sg_image img); +SOKOL_GFX_API_DECL int sg_query_image_num_slices(sg_image img); +SOKOL_GFX_API_DECL int sg_query_image_num_mipmaps(sg_image img); +SOKOL_GFX_API_DECL sg_pixel_format sg_query_image_pixelformat(sg_image img); +SOKOL_GFX_API_DECL sg_image_usage sg_query_image_usage(sg_image img); +SOKOL_GFX_API_DECL int sg_query_image_sample_count(sg_image img); +SOKOL_GFX_API_DECL sg_view_type sg_query_view_type(sg_view view); +SOKOL_GFX_API_DECL sg_image sg_query_view_image(sg_view view); +SOKOL_GFX_API_DECL sg_buffer sg_query_view_buffer(sg_view view); + +// separate resource allocation and initialization (for async setup) +SOKOL_GFX_API_DECL sg_buffer sg_alloc_buffer(void); +SOKOL_GFX_API_DECL sg_image sg_alloc_image(void); +SOKOL_GFX_API_DECL sg_sampler sg_alloc_sampler(void); +SOKOL_GFX_API_DECL sg_shader sg_alloc_shader(void); +SOKOL_GFX_API_DECL sg_pipeline sg_alloc_pipeline(void); +SOKOL_GFX_API_DECL sg_view sg_alloc_view(void); +SOKOL_GFX_API_DECL void sg_dealloc_buffer(sg_buffer buf); +SOKOL_GFX_API_DECL void sg_dealloc_image(sg_image img); +SOKOL_GFX_API_DECL void sg_dealloc_sampler(sg_sampler smp); +SOKOL_GFX_API_DECL void sg_dealloc_shader(sg_shader shd); +SOKOL_GFX_API_DECL void sg_dealloc_pipeline(sg_pipeline pip); +SOKOL_GFX_API_DECL void sg_dealloc_view(sg_view view); +SOKOL_GFX_API_DECL void sg_init_buffer(sg_buffer buf, const sg_buffer_desc* desc); +SOKOL_GFX_API_DECL void sg_init_image(sg_image img, const sg_image_desc* desc); +SOKOL_GFX_API_DECL void sg_init_sampler(sg_sampler smg, const sg_sampler_desc* desc); +SOKOL_GFX_API_DECL void sg_init_shader(sg_shader shd, const sg_shader_desc* desc); +SOKOL_GFX_API_DECL void sg_init_pipeline(sg_pipeline pip, const sg_pipeline_desc* desc); +SOKOL_GFX_API_DECL void sg_init_view(sg_view view, const sg_view_desc* desc); +SOKOL_GFX_API_DECL void sg_uninit_buffer(sg_buffer buf); +SOKOL_GFX_API_DECL void sg_uninit_image(sg_image img); +SOKOL_GFX_API_DECL void sg_uninit_sampler(sg_sampler smp); +SOKOL_GFX_API_DECL void sg_uninit_shader(sg_shader shd); +SOKOL_GFX_API_DECL void sg_uninit_pipeline(sg_pipeline pip); +SOKOL_GFX_API_DECL void sg_uninit_view(sg_view view); +SOKOL_GFX_API_DECL void sg_fail_buffer(sg_buffer buf); +SOKOL_GFX_API_DECL void sg_fail_image(sg_image img); +SOKOL_GFX_API_DECL void sg_fail_sampler(sg_sampler smp); +SOKOL_GFX_API_DECL void sg_fail_shader(sg_shader shd); +SOKOL_GFX_API_DECL void sg_fail_pipeline(sg_pipeline pip); +SOKOL_GFX_API_DECL void sg_fail_view(sg_view view); + +// frame stats +SOKOL_GFX_API_DECL void sg_enable_frame_stats(void); +SOKOL_GFX_API_DECL void sg_disable_frame_stats(void); +SOKOL_GFX_API_DECL bool sg_frame_stats_enabled(void); +SOKOL_GFX_API_DECL sg_frame_stats sg_query_frame_stats(void); + +/* Backend-specific structs and functions, these may come in handy for mixing + sokol-gfx rendering with 'native backend' rendering functions. + + This group of functions will be expanded as needed. +*/ + +typedef struct sg_d3d11_buffer_info { + const void* buf; // ID3D11Buffer* +} sg_d3d11_buffer_info; + +typedef struct sg_d3d11_image_info { + const void* tex2d; // ID3D11Texture2D* + const void* tex3d; // ID3D11Texture3D* + const void* res; // ID3D11Resource* (either tex2d or tex3d) +} sg_d3d11_image_info; + +typedef struct sg_d3d11_sampler_info { + const void* smp; // ID3D11SamplerState* +} sg_d3d11_sampler_info; + +typedef struct sg_d3d11_shader_info { + const void* cbufs[SG_MAX_UNIFORMBLOCK_BINDSLOTS]; // ID3D11Buffer* (constant buffers by bind slot) + const void* vs; // ID3D11VertexShader* + const void* fs; // ID3D11PixelShader* +} sg_d3d11_shader_info; + +typedef struct sg_d3d11_pipeline_info { + const void* il; // ID3D11InputLayout* + const void* rs; // ID3D11RasterizerState* + const void* dss; // ID3D11DepthStencilState* + const void* bs; // ID3D11BlendState* +} sg_d3d11_pipeline_info; + +typedef struct sg_d3d11_view_info { + const void* srv; // ID3D11ShaderResourceView + const void* uav; // ID3D11UnorderedAccessView + const void* rtv; // ID3D11RenderTargetView + const void* dsv; // ID3D11DepthStencilView +} sg_d3d11_view_info; + +typedef struct sg_mtl_buffer_info { + const void* buf[SG_NUM_INFLIGHT_FRAMES]; // id + int active_slot; +} sg_mtl_buffer_info; + +typedef struct sg_mtl_image_info { + const void* tex[SG_NUM_INFLIGHT_FRAMES]; // id + int active_slot; +} sg_mtl_image_info; + +typedef struct sg_mtl_sampler_info { + const void* smp; // id +} sg_mtl_sampler_info; + +typedef struct sg_mtl_shader_info { + const void* vertex_lib; // id + const void* fragment_lib; // id + const void* vertex_func; // id + const void* fragment_func; // id +} sg_mtl_shader_info; + +typedef struct sg_mtl_pipeline_info { + const void* rps; // id + const void* dss; // id +} sg_mtl_pipeline_info; + +typedef struct sg_wgpu_buffer_info { + const void* buf; // WGPUBuffer +} sg_wgpu_buffer_info; + +typedef struct sg_wgpu_image_info { + const void* tex; // WGPUTexture +} sg_wgpu_image_info; + +typedef struct sg_wgpu_sampler_info { + const void* smp; // WGPUSampler +} sg_wgpu_sampler_info; + +typedef struct sg_wgpu_shader_info { + const void* vs_mod; // WGPUShaderModule + const void* fs_mod; // WGPUShaderModule + const void* bgl; // WGPUBindGroupLayout; +} sg_wgpu_shader_info; + +typedef struct sg_wgpu_pipeline_info { + const void* render_pipeline; // WGPURenderPipeline + const void* compute_pipeline; // WGPUComputePipeline +} sg_wgpu_pipeline_info; + +typedef struct sg_wgpu_view_info { + const void* view; // WGPUTextureView +} sg_wgpu_view_info; + +typedef struct sg_gl_buffer_info { + uint32_t buf[SG_NUM_INFLIGHT_FRAMES]; + int active_slot; +} sg_gl_buffer_info; + +typedef struct sg_gl_image_info { + uint32_t tex[SG_NUM_INFLIGHT_FRAMES]; + uint32_t tex_target; + int active_slot; +} sg_gl_image_info; + +typedef struct sg_gl_sampler_info { + uint32_t smp; +} sg_gl_sampler_info; + +typedef struct sg_gl_shader_info { + uint32_t prog; +} sg_gl_shader_info; + +typedef struct sg_gl_view_info { + uint32_t tex_view[SG_NUM_INFLIGHT_FRAMES]; + uint32_t msaa_render_buffer; + uint32_t msaa_resolve_frame_buffer; +} sg_gl_view_info; + +// D3D11: return ID3D11Device +SOKOL_GFX_API_DECL const void* sg_d3d11_device(void); +// D3D11: return ID3D11DeviceContext +SOKOL_GFX_API_DECL const void* sg_d3d11_device_context(void); +// D3D11: get internal buffer resource objects +SOKOL_GFX_API_DECL sg_d3d11_buffer_info sg_d3d11_query_buffer_info(sg_buffer buf); +// D3D11: get internal image resource objects +SOKOL_GFX_API_DECL sg_d3d11_image_info sg_d3d11_query_image_info(sg_image img); +// D3D11: get internal sampler resource objects +SOKOL_GFX_API_DECL sg_d3d11_sampler_info sg_d3d11_query_sampler_info(sg_sampler smp); +// D3D11: get internal shader resource objects +SOKOL_GFX_API_DECL sg_d3d11_shader_info sg_d3d11_query_shader_info(sg_shader shd); +// D3D11: get internal pipeline resource objects +SOKOL_GFX_API_DECL sg_d3d11_pipeline_info sg_d3d11_query_pipeline_info(sg_pipeline pip); +// D3D11: get internal view resource objects +SOKOL_GFX_API_DECL sg_d3d11_view_info sg_d3d11_query_view_info(sg_view view); + +// Metal: return __bridge-casted MTLDevice +SOKOL_GFX_API_DECL const void* sg_mtl_device(void); +// Metal: return __bridge-casted MTLRenderCommandEncoder when inside render pass (otherwise zero) +SOKOL_GFX_API_DECL const void* sg_mtl_render_command_encoder(void); +// Metal: return __bridge-casted MTLComputeCommandEncoder when inside compute pass (otherwise zero) +SOKOL_GFX_API_DECL const void* sg_mtl_compute_command_encoder(void); +// Metal: get internal __bridge-casted buffer resource objects +SOKOL_GFX_API_DECL sg_mtl_buffer_info sg_mtl_query_buffer_info(sg_buffer buf); +// Metal: get internal __bridge-casted image resource objects +SOKOL_GFX_API_DECL sg_mtl_image_info sg_mtl_query_image_info(sg_image img); +// Metal: get internal __bridge-casted sampler resource objects +SOKOL_GFX_API_DECL sg_mtl_sampler_info sg_mtl_query_sampler_info(sg_sampler smp); +// Metal: get internal __bridge-casted shader resource objects +SOKOL_GFX_API_DECL sg_mtl_shader_info sg_mtl_query_shader_info(sg_shader shd); +// Metal: get internal __bridge-casted pipeline resource objects +SOKOL_GFX_API_DECL sg_mtl_pipeline_info sg_mtl_query_pipeline_info(sg_pipeline pip); + +// WebGPU: return WGPUDevice object +SOKOL_GFX_API_DECL const void* sg_wgpu_device(void); +// WebGPU: return WGPUQueue object +SOKOL_GFX_API_DECL const void* sg_wgpu_queue(void); +// WebGPU: return this frame's WGPUCommandEncoder +SOKOL_GFX_API_DECL const void* sg_wgpu_command_encoder(void); +// WebGPU: return WGPURenderPassEncoder of current pass (returns 0 when outside pass or in a compute pass) +SOKOL_GFX_API_DECL const void* sg_wgpu_render_pass_encoder(void); +// WebGPU: return WGPUComputePassEncoder of current pass (returns 0 when outside pass or in a render pass) +SOKOL_GFX_API_DECL const void* sg_wgpu_compute_pass_encoder(void); +// WebGPU: get internal buffer resource objects +SOKOL_GFX_API_DECL sg_wgpu_buffer_info sg_wgpu_query_buffer_info(sg_buffer buf); +// WebGPU: get internal image resource objects +SOKOL_GFX_API_DECL sg_wgpu_image_info sg_wgpu_query_image_info(sg_image img); +// WebGPU: get internal sampler resource objects +SOKOL_GFX_API_DECL sg_wgpu_sampler_info sg_wgpu_query_sampler_info(sg_sampler smp); +// WebGPU: get internal shader resource objects +SOKOL_GFX_API_DECL sg_wgpu_shader_info sg_wgpu_query_shader_info(sg_shader shd); +// WebGPU: get internal pipeline resource objects +SOKOL_GFX_API_DECL sg_wgpu_pipeline_info sg_wgpu_query_pipeline_info(sg_pipeline pip); +// WebGPU: get internal view resource objects +SOKOL_GFX_API_DECL sg_wgpu_view_info sg_wgpu_query_view_info(sg_view view); + +// GL: get internal buffer resource objects +SOKOL_GFX_API_DECL sg_gl_buffer_info sg_gl_query_buffer_info(sg_buffer buf); +// GL: get internal image resource objects +SOKOL_GFX_API_DECL sg_gl_image_info sg_gl_query_image_info(sg_image img); +// GL: get internal sampler resource objects +SOKOL_GFX_API_DECL sg_gl_sampler_info sg_gl_query_sampler_info(sg_sampler smp); +// GL: get internal shader resource objects +SOKOL_GFX_API_DECL sg_gl_shader_info sg_gl_query_shader_info(sg_shader shd); +// GL: get internal view resource objects +SOKOL_GFX_API_DECL sg_gl_view_info sg_gl_query_view_info(sg_view view); + +#ifdef __cplusplus +} // extern "C" + +// reference-based equivalents for c++ +inline void sg_setup(const sg_desc& desc) { return sg_setup(&desc); } + +inline sg_buffer sg_make_buffer(const sg_buffer_desc& desc) { return sg_make_buffer(&desc); } +inline sg_image sg_make_image(const sg_image_desc& desc) { return sg_make_image(&desc); } +inline sg_sampler sg_make_sampler(const sg_sampler_desc& desc) { return sg_make_sampler(&desc); } +inline sg_shader sg_make_shader(const sg_shader_desc& desc) { return sg_make_shader(&desc); } +inline sg_pipeline sg_make_pipeline(const sg_pipeline_desc& desc) { return sg_make_pipeline(&desc); } +inline sg_view sg_make_view(const sg_view_desc& desc) { return sg_make_view(&desc); } +inline void sg_update_image(sg_image img, const sg_image_data& data) { return sg_update_image(img, &data); } + +inline void sg_begin_pass(const sg_pass& pass) { return sg_begin_pass(&pass); } +inline void sg_apply_bindings(const sg_bindings& bindings) { return sg_apply_bindings(&bindings); } +inline void sg_apply_uniforms(int ub_slot, const sg_range& data) { return sg_apply_uniforms(ub_slot, &data); } + +inline sg_buffer_desc sg_query_buffer_defaults(const sg_buffer_desc& desc) { return sg_query_buffer_defaults(&desc); } +inline sg_image_desc sg_query_image_defaults(const sg_image_desc& desc) { return sg_query_image_defaults(&desc); } +inline sg_sampler_desc sg_query_sampler_defaults(const sg_sampler_desc& desc) { return sg_query_sampler_defaults(&desc); } +inline sg_shader_desc sg_query_shader_defaults(const sg_shader_desc& desc) { return sg_query_shader_defaults(&desc); } +inline sg_pipeline_desc sg_query_pipeline_defaults(const sg_pipeline_desc& desc) { return sg_query_pipeline_defaults(&desc); } +inline sg_view_desc sg_query_view_defaults(const sg_view_desc& desc) { return sg_query_view_defaults(&desc); } + +inline void sg_init_buffer(sg_buffer buf, const sg_buffer_desc& desc) { return sg_init_buffer(buf, &desc); } +inline void sg_init_image(sg_image img, const sg_image_desc& desc) { return sg_init_image(img, &desc); } +inline void sg_init_sampler(sg_sampler smp, const sg_sampler_desc& desc) { return sg_init_sampler(smp, &desc); } +inline void sg_init_shader(sg_shader shd, const sg_shader_desc& desc) { return sg_init_shader(shd, &desc); } +inline void sg_init_pipeline(sg_pipeline pip, const sg_pipeline_desc& desc) { return sg_init_pipeline(pip, &desc); } +inline void sg_init_view(sg_view view, const sg_view_desc& desc) { return sg_init_view(view, &desc); } + +inline void sg_update_buffer(sg_buffer buf_id, const sg_range& data) { return sg_update_buffer(buf_id, &data); } +inline int sg_append_buffer(sg_buffer buf_id, const sg_range& data) { return sg_append_buffer(buf_id, &data); } +#endif +#endif // SOKOL_GFX_INCLUDED + +// ██ ███ ███ ██████ ██ ███████ ███ ███ ███████ ███ ██ ████████ █████ ████████ ██ ██████ ███ ██ +// ██ ████ ████ ██ ██ ██ ██ ████ ████ ██ ████ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██ +// ██ ██ ████ ██ ██████ ██ █████ ██ ████ ██ █████ ██ ██ ██ ██ ███████ ██ ██ ██ ██ ██ ██ ██ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██ ██ ██ ██ ███████ ███████ ██ ██ ███████ ██ ████ ██ ██ ██ ██ ██ ██████ ██ ████ +// +// >>implementation +#ifdef SOKOL_GFX_IMPL +#define SOKOL_GFX_IMPL_INCLUDED (1) + +#if !(defined(SOKOL_GLCORE)||defined(SOKOL_GLES3)||defined(SOKOL_D3D11)||defined(SOKOL_METAL)||defined(SOKOL_WGPU)||defined(SOKOL_DUMMY_BACKEND)) +#error "Please select a backend with SOKOL_GLCORE, SOKOL_GLES3, SOKOL_D3D11, SOKOL_METAL, SOKOL_WGPU or SOKOL_DUMMY_BACKEND" +#endif +#if defined(SOKOL_MALLOC) || defined(SOKOL_CALLOC) || defined(SOKOL_FREE) +#error "SOKOL_MALLOC/CALLOC/FREE macros are no longer supported, please use sg_desc.allocator to override memory allocation functions" +#endif + +#include // malloc, free, qsort +#include // memset +#include // FLT_MAX + +#ifndef SOKOL_API_IMPL + #define SOKOL_API_IMPL +#endif +#ifndef SOKOL_DEBUG + #ifndef NDEBUG + #define SOKOL_DEBUG + #endif +#endif +#ifndef SOKOL_ASSERT + #include + #define SOKOL_ASSERT(c) assert(c) +#endif +#ifndef SOKOL_UNREACHABLE + #define SOKOL_UNREACHABLE SOKOL_ASSERT(false) +#endif + +#ifndef _SOKOL_PRIVATE + #if defined(__GNUC__) || defined(__clang__) + #define _SOKOL_PRIVATE __attribute__((unused)) static + #else + #define _SOKOL_PRIVATE static + #endif +#endif + +#ifndef _SOKOL_UNUSED + #define _SOKOL_UNUSED(x) (void)(x) +#endif + +#if defined(SOKOL_TRACE_HOOKS) +#define _SG_TRACE_ARGS(fn, ...) if (_sg.hooks.fn) { _sg.hooks.fn(__VA_ARGS__, _sg.hooks.user_data); } +#define _SG_TRACE_NOARGS(fn) if (_sg.hooks.fn) { _sg.hooks.fn(_sg.hooks.user_data); } +#else +#define _SG_TRACE_ARGS(fn, ...) +#define _SG_TRACE_NOARGS(fn) +#endif + +// default clear values +#ifndef SG_DEFAULT_CLEAR_RED +#define SG_DEFAULT_CLEAR_RED (0.5f) +#endif +#ifndef SG_DEFAULT_CLEAR_GREEN +#define SG_DEFAULT_CLEAR_GREEN (0.5f) +#endif +#ifndef SG_DEFAULT_CLEAR_BLUE +#define SG_DEFAULT_CLEAR_BLUE (0.5f) +#endif +#ifndef SG_DEFAULT_CLEAR_ALPHA +#define SG_DEFAULT_CLEAR_ALPHA (1.0f) +#endif +#ifndef SG_DEFAULT_CLEAR_DEPTH +#define SG_DEFAULT_CLEAR_DEPTH (1.0f) +#endif +#ifndef SG_DEFAULT_CLEAR_STENCIL +#define SG_DEFAULT_CLEAR_STENCIL (0) +#endif + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable:4115) // named type definition in parentheses +#pragma warning(disable:4505) // unreferenced local function has been removed +#pragma warning(disable:4201) // nonstandard extension used: nameless struct/union (needed by d3d11.h) +#pragma warning(disable:4054) // 'type cast': from function pointer +#pragma warning(disable:4055) // 'type cast': from data pointer +#endif + +#if defined(SOKOL_D3D11) + #if defined(__GNUC__) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wunknown-pragmas" + #endif + #ifndef D3D11_NO_HELPERS + #define D3D11_NO_HELPERS + #endif + #ifndef WIN32_LEAN_AND_MEAN + #define WIN32_LEAN_AND_MEAN + #endif + #ifndef NOMINMAX + #define NOMINMAX + #endif + #include + #include + #pragma comment (lib, "kernel32") + #pragma comment (lib, "user32") + #pragma comment (lib, "dxgi") + #pragma comment (lib, "d3d11") + #if defined(__GNUC__) + #pragma GCC diagnostic pop + #endif +#elif defined(SOKOL_METAL) + // see https://clang.llvm.org/docs/LanguageExtensions.html#automatic-reference-counting + #if !defined(__cplusplus) + #if __has_feature(objc_arc) && !__has_feature(objc_arc_fields) + #error "sokol_gfx.h requires __has_feature(objc_arc_field) if ARC is enabled (use a more recent compiler version)" + #endif + #endif + #include + #include + #if defined(TARGET_OS_IPHONE) && !TARGET_OS_IPHONE + #define _SG_TARGET_MACOS (1) + #else + #define _SG_TARGET_IOS (1) + #if defined(TARGET_IPHONE_SIMULATOR) && TARGET_IPHONE_SIMULATOR + #define _SG_TARGET_IOS_SIMULATOR (1) + #endif + #endif + #import + #import // needed for CAMetalDrawable +#elif defined(SOKOL_WGPU) + #include + #if defined(__EMSCRIPTEN__) + #include + #endif +#elif defined(SOKOL_GLCORE) || defined(SOKOL_GLES3) + #define _SOKOL_ANY_GL (1) + + // include platform specific GL headers (or on Win32: use an embedded GL loader) + #if !defined(SOKOL_EXTERNAL_GL_LOADER) + #if defined(_WIN32) + #if defined(SOKOL_GLCORE) + #define _SOKOL_USE_WIN32_GL_LOADER (1) + #ifndef WIN32_LEAN_AND_MEAN + #define WIN32_LEAN_AND_MEAN + #endif + #ifndef NOMINMAX + #define NOMINMAX + #endif + #include + #pragma comment (lib, "kernel32") // GetProcAddress() + #endif + #elif defined(__APPLE__) + #include + #ifndef GL_SILENCE_DEPRECATION + #define GL_SILENCE_DEPRECATION + #endif + #if defined(TARGET_OS_IPHONE) && !TARGET_OS_IPHONE + #include + #else + #include + #include + #endif + #elif defined(__EMSCRIPTEN__) + #if defined(SOKOL_GLES3) + #include + #endif + #elif defined(__ANDROID__) + #include + #elif defined(__linux__) || defined(__unix__) + #if defined(SOKOL_GLCORE) + #define GL_GLEXT_PROTOTYPES + #include + #else + #include + #include + #endif + #endif + #endif + + // broad GL feature availability defines (DON'T merge this into the above ifdef-block!) + #if defined(_WIN32) + #if defined(GL_VERSION_4_3) || defined(_SOKOL_USE_WIN32_GL_LOADER) + #define _SOKOL_GL_HAS_COMPUTE (1) + #define _SOKOL_GL_HAS_TEXVIEWS (1) + #endif + #if defined(GL_VERSION_4_2) || defined(_SOKOL_USE_WIN32_GL_LOADER) + #define _SOKOL_GL_HAS_TEXSTORAGE (1) + #define _SOKOL_GL_HAS_BASEINSTANCE (1) + #endif + #if defined(GL_VERSION_3_2) || defined(_SOKOL_USE_WIN32_GL_LOADER) + #define _SOKOL_GL_HAS_BASEVERTEX (1) + #endif + #elif defined(__APPLE__) + #if defined(TARGET_OS_IPHONE) && !TARGET_OS_IPHONE + #define _SOKOL_GL_HAS_BASEVERTEX (1) + #else + #define _SOKOL_GL_HAS_TEXSTORAGE (1) + #endif + #elif defined(__EMSCRIPTEN__) + #define _SOKOL_GL_HAS_TEXSTORAGE (1) + #elif defined(__ANDROID__) + #define _SOKOL_GL_HAS_COMPUTE (1) + #define _SOKOL_GL_HAS_TEXSTORAGE (1) + #elif defined(__linux__) || defined(__unix__) + #if defined(SOKOL_GLCORE) + #if defined(GL_VERSION_4_3) + #define _SOKOL_GL_HAS_COMPUTE (1) + #define _SOKOL_GL_HAS_TEXVIEWS (1) + #endif + #if defined(GL_VERSION_4_2) + #define _SOKOL_GL_HAS_TEXSTORAGE (1) + #define _SOKOL_GL_HAS_BASEINSTANCE (1) + #endif + #if defined(GL_VERSION_3_2) + #define _SOKOL_GL_HAS_BASEVERTEX (1) + #endif + #else + #define _SOKOL_GL_HAS_COMPUTE (1) + #define _SOKOL_GL_HAS_TEXSTORAGE (1) + #define _SOKOL_GL_HAS_BASEVERTEX (1) + #endif + #endif + + // optional GL loader definitions (only on Win32) + #if defined(_SOKOL_USE_WIN32_GL_LOADER) + #define __gl_h_ 1 + #define __gl32_h_ 1 + #define __gl31_h_ 1 + #define __GL_H__ 1 + #define __glext_h_ 1 + #define __GLEXT_H_ 1 + #define __gltypes_h_ 1 + #define __glcorearb_h_ 1 + #define __gl_glcorearb_h_ 1 + #define GL_APIENTRY APIENTRY + + typedef unsigned int GLenum; + typedef unsigned int GLuint; + typedef int GLsizei; + typedef char GLchar; + typedef ptrdiff_t GLintptr; + typedef ptrdiff_t GLsizeiptr; + typedef double GLclampd; + typedef unsigned short GLushort; + typedef unsigned char GLubyte; + typedef unsigned char GLboolean; + typedef uint64_t GLuint64; + typedef double GLdouble; + typedef unsigned short GLhalf; + typedef float GLclampf; + typedef unsigned int GLbitfield; + typedef signed char GLbyte; + typedef short GLshort; + typedef void GLvoid; + typedef int64_t GLint64; + typedef float GLfloat; + typedef int GLint; + #define GL_INT_2_10_10_10_REV 0x8D9F + #define GL_R32F 0x822E + #define GL_PROGRAM_POINT_SIZE 0x8642 + #define GL_DEPTH_ATTACHMENT 0x8D00 + #define GL_DEPTH_STENCIL_ATTACHMENT 0x821A + #define GL_COLOR_ATTACHMENT2 0x8CE2 + #define GL_COLOR_ATTACHMENT0 0x8CE0 + #define GL_R16F 0x822D + #define GL_COLOR_ATTACHMENT22 0x8CF6 + #define GL_DRAW_FRAMEBUFFER 0x8CA9 + #define GL_FRAMEBUFFER_COMPLETE 0x8CD5 + #define GL_NUM_EXTENSIONS 0x821D + #define GL_INFO_LOG_LENGTH 0x8B84 + #define GL_VERTEX_SHADER 0x8B31 + #define GL_INCR 0x1E02 + #define GL_DYNAMIC_DRAW 0x88E8 + #define GL_STATIC_DRAW 0x88E4 + #define GL_TEXTURE_CUBE_MAP_POSITIVE_Z 0x8519 + #define GL_TEXTURE_CUBE_MAP 0x8513 + #define GL_FUNC_SUBTRACT 0x800A + #define GL_FUNC_REVERSE_SUBTRACT 0x800B + #define GL_CONSTANT_COLOR 0x8001 + #define GL_DECR_WRAP 0x8508 + #define GL_R8 0x8229 + #define GL_LINEAR_MIPMAP_LINEAR 0x2703 + #define GL_ELEMENT_ARRAY_BUFFER 0x8893 + #define GL_SHORT 0x1402 + #define GL_DEPTH_TEST 0x0B71 + #define GL_TEXTURE_CUBE_MAP_NEGATIVE_Y 0x8518 + #define GL_LINK_STATUS 0x8B82 + #define GL_TEXTURE_CUBE_MAP_POSITIVE_Y 0x8517 + #define GL_SAMPLE_ALPHA_TO_COVERAGE 0x809E + #define GL_RGBA16F 0x881A + #define GL_CONSTANT_ALPHA 0x8003 + #define GL_READ_FRAMEBUFFER 0x8CA8 + #define GL_TEXTURE0 0x84C0 + #define GL_TEXTURE_MIN_LOD 0x813A + #define GL_CLAMP_TO_EDGE 0x812F + #define GL_UNSIGNED_SHORT_5_6_5 0x8363 + #define GL_TEXTURE_WRAP_R 0x8072 + #define GL_UNSIGNED_SHORT_5_5_5_1 0x8034 + #define GL_NEAREST_MIPMAP_NEAREST 0x2700 + #define GL_UNSIGNED_SHORT_4_4_4_4 0x8033 + #define GL_SRC_ALPHA_SATURATE 0x0308 + #define GL_STREAM_DRAW 0x88E0 + #define GL_ONE 1 + #define GL_NEAREST_MIPMAP_LINEAR 0x2702 + #define GL_RGB10_A2 0x8059 + #define GL_RGBA8 0x8058 + #define GL_SRGB8_ALPHA8 0x8C43 + #define GL_COLOR_ATTACHMENT1 0x8CE1 + #define GL_RGBA4 0x8056 + #define GL_RGB8 0x8051 + #define GL_ARRAY_BUFFER 0x8892 + #define GL_STENCIL 0x1802 + #define GL_TEXTURE_2D 0x0DE1 + #define GL_DEPTH 0x1801 + #define GL_FRONT 0x0404 + #define GL_STENCIL_BUFFER_BIT 0x00000400 + #define GL_REPEAT 0x2901 + #define GL_RGBA 0x1908 + #define GL_TEXTURE_CUBE_MAP_POSITIVE_X 0x8515 + #define GL_DECR 0x1E03 + #define GL_FRAGMENT_SHADER 0x8B30 + #define GL_COMPUTE_SHADER 0x91B9 + #define GL_FLOAT 0x1406 + #define GL_TEXTURE_MAX_LOD 0x813B + #define GL_DEPTH_COMPONENT 0x1902 + #define GL_ONE_MINUS_DST_ALPHA 0x0305 + #define GL_COLOR 0x1800 + #define GL_TEXTURE_2D_ARRAY 0x8C1A + #define GL_TRIANGLES 0x0004 + #define GL_UNSIGNED_BYTE 0x1401 + #define GL_TEXTURE_MAG_FILTER 0x2800 + #define GL_ONE_MINUS_CONSTANT_ALPHA 0x8004 + #define GL_NONE 0 + #define GL_SRC_COLOR 0x0300 + #define GL_BYTE 0x1400 + #define GL_TEXTURE_CUBE_MAP_NEGATIVE_Z 0x851A + #define GL_LINE_STRIP 0x0003 + #define GL_TEXTURE_3D 0x806F + #define GL_CW 0x0900 + #define GL_LINEAR 0x2601 + #define GL_RENDERBUFFER 0x8D41 + #define GL_GEQUAL 0x0206 + #define GL_COLOR_BUFFER_BIT 0x00004000 + #define GL_RGBA32F 0x8814 + #define GL_BLEND 0x0BE2 + #define GL_ONE_MINUS_SRC_ALPHA 0x0303 + #define GL_ONE_MINUS_CONSTANT_COLOR 0x8002 + #define GL_TEXTURE_WRAP_T 0x2803 + #define GL_TEXTURE_WRAP_S 0x2802 + #define GL_TEXTURE_MIN_FILTER 0x2801 + #define GL_LINEAR_MIPMAP_NEAREST 0x2701 + #define GL_EXTENSIONS 0x1F03 + #define GL_NO_ERROR 0 + #define GL_REPLACE 0x1E01 + #define GL_KEEP 0x1E00 + #define GL_CCW 0x0901 + #define GL_TEXTURE_CUBE_MAP_NEGATIVE_X 0x8516 + #define GL_RGB 0x1907 + #define GL_TRIANGLE_STRIP 0x0005 + #define GL_FALSE 0 + #define GL_ZERO 0 + #define GL_CULL_FACE 0x0B44 + #define GL_INVERT 0x150A + #define GL_INT 0x1404 + #define GL_UNSIGNED_INT 0x1405 + #define GL_UNSIGNED_SHORT 0x1403 + #define GL_NEAREST 0x2600 + #define GL_SCISSOR_TEST 0x0C11 + #define GL_LEQUAL 0x0203 + #define GL_STENCIL_TEST 0x0B90 + #define GL_DITHER 0x0BD0 + #define GL_DEPTH_COMPONENT32F 0x8CAC + #define GL_EQUAL 0x0202 + #define GL_FRAMEBUFFER 0x8D40 + #define GL_RGB5 0x8050 + #define GL_LINES 0x0001 + #define GL_DEPTH_BUFFER_BIT 0x00000100 + #define GL_SRC_ALPHA 0x0302 + #define GL_INCR_WRAP 0x8507 + #define GL_LESS 0x0201 + #define GL_MULTISAMPLE 0x809D + #define GL_FRAMEBUFFER_BINDING 0x8CA6 + #define GL_BACK 0x0405 + #define GL_ALWAYS 0x0207 + #define GL_FUNC_ADD 0x8006 + #define GL_ONE_MINUS_DST_COLOR 0x0307 + #define GL_NOTEQUAL 0x0205 + #define GL_DST_COLOR 0x0306 + #define GL_COMPILE_STATUS 0x8B81 + #define GL_RED 0x1903 + #define GL_COLOR_ATTACHMENT3 0x8CE3 + #define GL_DST_ALPHA 0x0304 + #define GL_RGB5_A1 0x8057 + #define GL_GREATER 0x0204 + #define GL_POLYGON_OFFSET_FILL 0x8037 + #define GL_TRUE 1 + #define GL_NEVER 0x0200 + #define GL_POINTS 0x0000 + #define GL_ONE_MINUS_SRC_COLOR 0x0301 + #define GL_MIRRORED_REPEAT 0x8370 + #define GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS 0x8B4D + #define GL_R11F_G11F_B10F 0x8C3A + #define GL_UNSIGNED_INT_10F_11F_11F_REV 0x8C3B + #define GL_RGB9_E5 0x8C3D + #define GL_UNSIGNED_INT_5_9_9_9_REV 0x8C3E + #define GL_RGBA32UI 0x8D70 + #define GL_RGB32UI 0x8D71 + #define GL_RGBA16UI 0x8D76 + #define GL_RGB16UI 0x8D77 + #define GL_RGBA8UI 0x8D7C + #define GL_RGB8UI 0x8D7D + #define GL_RGBA32I 0x8D82 + #define GL_RGB32I 0x8D83 + #define GL_RGBA16I 0x8D88 + #define GL_RGB16I 0x8D89 + #define GL_RGBA8I 0x8D8E + #define GL_RGB8I 0x8D8F + #define GL_RED_INTEGER 0x8D94 + #define GL_RG 0x8227 + #define GL_RG_INTEGER 0x8228 + #define GL_R8 0x8229 + #define GL_R16 0x822A + #define GL_RG8 0x822B + #define GL_RG16 0x822C + #define GL_R16F 0x822D + #define GL_R32F 0x822E + #define GL_RG16F 0x822F + #define GL_RG32F 0x8230 + #define GL_R8I 0x8231 + #define GL_R8UI 0x8232 + #define GL_R16I 0x8233 + #define GL_R16UI 0x8234 + #define GL_R32I 0x8235 + #define GL_R32UI 0x8236 + #define GL_RG8I 0x8237 + #define GL_RG8UI 0x8238 + #define GL_RG16I 0x8239 + #define GL_RG16UI 0x823A + #define GL_RG32I 0x823B + #define GL_RG32UI 0x823C + #define GL_RGBA_INTEGER 0x8D99 + #define GL_R8_SNORM 0x8F94 + #define GL_RG8_SNORM 0x8F95 + #define GL_RGB8_SNORM 0x8F96 + #define GL_RGBA8_SNORM 0x8F97 + #define GL_R16_SNORM 0x8F98 + #define GL_RG16_SNORM 0x8F99 + #define GL_RGB16_SNORM 0x8F9A + #define GL_RGBA16_SNORM 0x8F9B + #define GL_RGBA16 0x805B + #define GL_MAX_TEXTURE_SIZE 0x0D33 + #define GL_MAX_CUBE_MAP_TEXTURE_SIZE 0x851C + #define GL_MAX_3D_TEXTURE_SIZE 0x8073 + #define GL_MAX_ARRAY_TEXTURE_LAYERS 0x88FF + #define GL_MAX_VERTEX_ATTRIBS 0x8869 + #define GL_CLAMP_TO_BORDER 0x812D + #define GL_TEXTURE_BORDER_COLOR 0x1004 + #define GL_CURRENT_PROGRAM 0x8B8D + #define GL_MAX_VERTEX_UNIFORM_COMPONENTS 0x8B4A + #define GL_UNPACK_ALIGNMENT 0x0CF5 + #define GL_FRAMEBUFFER_SRGB 0x8DB9 + #define GL_TEXTURE_COMPARE_MODE 0x884C + #define GL_TEXTURE_COMPARE_FUNC 0x884D + #define GL_COMPARE_REF_TO_TEXTURE 0x884E + #define GL_TEXTURE_CUBE_MAP_SEAMLESS 0x884F + #define GL_TEXTURE_MAX_LEVEL 0x813D + #define GL_FRAMEBUFFER_UNDEFINED 0x8219 + #define GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT 0x8CD6 + #define GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT 0x8CD7 + #define GL_FRAMEBUFFER_UNSUPPORTED 0x8CDD + #define GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE 0x8D56 + #define GL_MAJOR_VERSION 0x821B + #define GL_MINOR_VERSION 0x821C + #define GL_TEXTURE_2D_MULTISAMPLE 0x9100 + #define GL_TEXTURE_2D_MULTISAMPLE_ARRAY 0x9102 + #define GL_SHADER_STORAGE_BARRIER_BIT 0x2000 + #define GL_VERTEX_ATTRIB_ARRAY_BARRIER_BIT 0x00000001 + #define GL_ELEMENT_ARRAY_BARRIER_BIT 0x00000002 + #define GL_TEXTURE_FETCH_BARRIER_BIT 0x00000008 + #define GL_SHADER_IMAGE_ACCESS_BARRIER_BIT 0x00000020 + #define GL_FRAMEBUFFER_BARRIER_BIT 0x00000400 + #define GL_MIN 0x8007 + #define GL_MAX 0x8008 + #define GL_WRITE_ONLY 0x88B9 + #define GL_READ_WRITE 0x88BA + #define GL_MAX_DRAW_BUFFERS 0x8824 + #define GL_MAX_TEXTURE_IMAGE_UNITS 0x8872 + #define GL_MAX_SHADER_STORAGE_BUFFER_BINDINGS 0x90DD + #define GL_MAX_IMAGE_UNITS 0x8F38 + #endif + + #ifndef GL_UNSIGNED_INT_2_10_10_10_REV + #define GL_UNSIGNED_INT_2_10_10_10_REV 0x8368 + #endif + #ifndef GL_UNSIGNED_INT_24_8 + #define GL_UNSIGNED_INT_24_8 0x84FA + #endif + #ifndef GL_TEXTURE_MAX_ANISOTROPY_EXT + #define GL_TEXTURE_MAX_ANISOTROPY_EXT 0x84FE + #endif + #ifndef GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT + #define GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT 0x84FF + #endif + #ifndef GL_COMPRESSED_RGBA_S3TC_DXT1_EXT + #define GL_COMPRESSED_RGBA_S3TC_DXT1_EXT 0x83F1 + #endif + #ifndef GL_COMPRESSED_RGBA_S3TC_DXT3_EXT + #define GL_COMPRESSED_RGBA_S3TC_DXT3_EXT 0x83F2 + #endif + #ifndef GL_COMPRESSED_RGBA_S3TC_DXT5_EXT + #define GL_COMPRESSED_RGBA_S3TC_DXT5_EXT 0x83F3 + #endif + #ifndef GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT + #define GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT 0x8C4F + #endif + #ifndef GL_COMPRESSED_RED_RGTC1 + #define GL_COMPRESSED_RED_RGTC1 0x8DBB + #endif + #ifndef GL_COMPRESSED_SIGNED_RED_RGTC1 + #define GL_COMPRESSED_SIGNED_RED_RGTC1 0x8DBC + #endif + #ifndef GL_COMPRESSED_RED_GREEN_RGTC2 + #define GL_COMPRESSED_RED_GREEN_RGTC2 0x8DBD + #endif + #ifndef GL_COMPRESSED_SIGNED_RED_GREEN_RGTC2 + #define GL_COMPRESSED_SIGNED_RED_GREEN_RGTC2 0x8DBE + #endif + #ifndef GL_COMPRESSED_RGBA_BPTC_UNORM_ARB + #define GL_COMPRESSED_RGBA_BPTC_UNORM_ARB 0x8E8C + #endif + #ifndef GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM_ARB + #define GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM_ARB 0x8E8D + #endif + #ifndef GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT_ARB + #define GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT_ARB 0x8E8E + #endif + #ifndef GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_ARB + #define GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_ARB 0x8E8F + #endif + #ifndef GL_COMPRESSED_RGB8_ETC2 + #define GL_COMPRESSED_RGB8_ETC2 0x9274 + #endif + #ifndef GL_COMPRESSED_SRGB8_ETC2 + #define GL_COMPRESSED_SRGB8_ETC2 0x9275 + #endif + #ifndef GL_COMPRESSED_RGBA8_ETC2_EAC + #define GL_COMPRESSED_RGBA8_ETC2_EAC 0x9278 + #endif + #ifndef GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC + #define GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC 0x9279 + #endif + #ifndef GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2 + #define GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2 0x9276 + #endif + #ifndef GL_COMPRESSED_R11_EAC + #define GL_COMPRESSED_R11_EAC 0x9270 + #endif + #ifndef GL_COMPRESSED_SIGNED_R11_EAC + #define GL_COMPRESSED_SIGNED_R11_EAC 0x9271 + #endif + #ifndef GL_COMPRESSED_RG11_EAC + #define GL_COMPRESSED_RG11_EAC 0x9272 + #endif + #ifndef GL_COMPRESSED_SIGNED_RG11_EAC + #define GL_COMPRESSED_SIGNED_RG11_EAC 0x9273 + #endif + #ifndef GL_COMPRESSED_RGBA_ASTC_4x4_KHR + #define GL_COMPRESSED_RGBA_ASTC_4x4_KHR 0x93B0 + #endif + #ifndef GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR + #define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR 0x93D0 + #endif + #ifndef GL_DEPTH24_STENCIL8 + #define GL_DEPTH24_STENCIL8 0x88F0 + #endif + #ifndef GL_HALF_FLOAT + #define GL_HALF_FLOAT 0x140B + #endif + #ifndef GL_DEPTH_STENCIL + #define GL_DEPTH_STENCIL 0x84F9 + #endif + #ifndef GL_LUMINANCE + #define GL_LUMINANCE 0x1909 + #endif + #ifndef GL_COMPUTE_SHADER + #define GL_COMPUTE_SHADER 0x91B9 + #endif + #ifndef _SG_GL_CHECK_ERROR + #if defined(__EMSCRIPTEN__) + // generally turn off glGetError() on WASM, it's a too big performance hit + // and WebGL provides much better diagnostics anyway + #define _SG_GL_CHECK_ERROR() + #elif defined(SOKOL_DEBUG) + // make sure that glGetError() is only called in debug mode + #define _SG_GL_CHECK_ERROR() { SOKOL_ASSERT(glGetError() == GL_NO_ERROR); } + #else + #define _SG_GL_CHECK_ERROR() + #endif + #endif + // make some GL constants generally available to simplify compilation, + // use of those constants will be filtered by runtime flags + #ifndef GL_SHADER_STORAGE_BUFFER + #define GL_SHADER_STORAGE_BUFFER 0x90D2 + #endif +#endif + +#if defined(SOKOL_GLES3) + // on WebGL2, GL_FRAMEBUFFER_UNDEFINED technically doesn't exist (it is defined + // in the Emscripten headers, but may not exist in other WebGL2 shims) + // see: https://github.com/floooh/sokol/pull/933 + #ifndef GL_FRAMEBUFFER_UNDEFINED + #define GL_FRAMEBUFFER_UNDEFINED 0x8219 + #endif +#endif + +// ███████ ████████ ██████ ██ ██ ██████ ████████ ███████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ███████ ██ ██████ ██ ██ ██ ██ ███████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ███████ ██ ██ ██ ██████ ██████ ██ ███████ +// +// >>structs + +typedef struct { int x, y, w, h; } _sg_recti_t; +typedef struct { int width, height; } _sg_dimi_t; + +// resource pool slots +typedef struct { + uint32_t id; + uint32_t uninit_count; + sg_resource_state state; +} _sg_slot_t; + +// resource pool housekeeping struct +typedef struct { + int size; + int queue_top; + uint32_t* gen_ctrs; + int* free_queue; +} _sg_pool_t; + +// resource func forward decls +struct _sg_buffer_s; +struct _sg_image_s; +struct _sg_sampler_s; +struct _sg_shader_s; +struct _sg_pipeline_s; +struct _sg_view_s; + +// a general resource slot reference useful for caches +typedef struct _sg_sref_s { + uint32_t id; + uint32_t uninit_count; +} _sg_sref_t; + +// safe (in debug mode) internal resource references +typedef struct _sg_buffer_ref_s { + struct _sg_buffer_s* ptr; + _sg_sref_t sref; +} _sg_buffer_ref_t; + +typedef struct _sg_image_ref_s { + struct _sg_image_s* ptr; + _sg_sref_t sref; +} _sg_image_ref_t; + +typedef struct _sg_sampler_ref_t { + struct _sg_sampler_s* ptr; + _sg_sref_t sref; +} _sg_sampler_ref_t; + +typedef struct _sg_shader_ref_s { + struct _sg_shader_s* ptr; + _sg_sref_t sref; +} _sg_shader_ref_t; + +typedef struct _sg_pipeline_ref_s { + struct _sg_pipeline_s* ptr; + _sg_sref_t sref; +} _sg_pipeline_ref_t; + +typedef struct _sg_view_ref_s { + struct _sg_view_s* ptr; + _sg_sref_t sref; +} _sg_view_ref_t; + +// constants +enum { + _SG_STRING_SIZE = 32, + _SG_SLOT_SHIFT = 16, + _SG_SLOT_MASK = (1<<_SG_SLOT_SHIFT)-1, + _SG_MAX_POOL_SIZE = (1<<_SG_SLOT_SHIFT), + _SG_DEFAULT_BUFFER_POOL_SIZE = 128, + _SG_DEFAULT_IMAGE_POOL_SIZE = 128, + _SG_DEFAULT_SAMPLER_POOL_SIZE = 64, + _SG_DEFAULT_SHADER_POOL_SIZE = 32, + _SG_DEFAULT_PIPELINE_POOL_SIZE = 64, + _SG_DEFAULT_VIEW_POOL_SIZE = 256, + _SG_DEFAULT_UB_SIZE = 4 * 1024 * 1024, + _SG_DEFAULT_MAX_COMMIT_LISTENERS = 1024, + _SG_DEFAULT_WGPU_BINDGROUP_CACHE_SIZE = 1024, + _SG_MAX_STORAGEBUFFER_BINDINGS_PER_STAGE = SG_MAX_VIEW_BINDSLOTS, + _SG_MAX_STORAGEIMAGE_BINDINGS_PER_STAGE = SG_MAX_VIEW_BINDSLOTS, + _SG_MAX_TEXTURE_BINDINGS_PER_STAGE = SG_MAX_VIEW_BINDSLOTS, + _SG_MAX_UNIFORMBLOCK_BINDINGS_PER_STAGE = 8, +}; + +// fixed-size string +typedef struct { + char buf[_SG_STRING_SIZE]; +} _sg_str_t; + +typedef struct { + int size; + int append_pos; + bool append_overflow; + uint32_t update_frame_index; + uint32_t append_frame_index; + int num_slots; + int active_slot; + sg_buffer_usage usage; +} _sg_buffer_common_t; + +typedef struct { + uint32_t upd_frame_index; + int num_slots; + int active_slot; + sg_image_type type; + int width; + int height; + int num_slices; + int num_mipmaps; + sg_image_usage usage; + sg_pixel_format pixel_format; + int sample_count; +} _sg_image_common_t; + +typedef struct { + sg_filter min_filter; + sg_filter mag_filter; + sg_filter mipmap_filter; + sg_wrap wrap_u; + sg_wrap wrap_v; + sg_wrap wrap_w; + float min_lod; + float max_lod; + sg_border_color border_color; + sg_compare_func compare; + uint32_t max_anisotropy; +} _sg_sampler_common_t; + +typedef struct { + sg_shader_attr_base_type base_type; +} _sg_shader_attr_t; + +typedef struct { + sg_shader_stage stage; + uint32_t size; +} _sg_shader_uniform_block_t; + +typedef struct { + sg_shader_stage stage; + sg_view_type view_type; + sg_image_type image_type; + sg_pixel_format access_format; + sg_image_sample_type sample_type; + bool sbuf_readonly; + bool simg_writeonly; + bool multisampled; +} _sg_shader_view_t; + +typedef struct { + sg_shader_stage stage; + sg_sampler_type sampler_type; +} _sg_shader_sampler_t; + +typedef struct { + sg_shader_stage stage; + uint8_t view_slot; + uint8_t sampler_slot; +} _sg_shader_texture_sampler_t; + +typedef struct { + uint32_t required_bindings_and_uniforms; + bool is_compute; + _sg_shader_attr_t attrs[SG_MAX_VERTEX_ATTRIBUTES]; + _sg_shader_uniform_block_t uniform_blocks[SG_MAX_UNIFORMBLOCK_BINDSLOTS]; + _sg_shader_view_t views[SG_MAX_VIEW_BINDSLOTS]; + _sg_shader_sampler_t samplers[SG_MAX_SAMPLER_BINDSLOTS]; + _sg_shader_texture_sampler_t texture_samplers[SG_MAX_TEXTURE_SAMPLER_PAIRS]; +} _sg_shader_common_t; + +typedef struct { + bool vertex_buffer_layout_active[SG_MAX_VERTEXBUFFER_BINDSLOTS]; + bool use_instanced_draw; + bool is_compute; + uint32_t required_bindings_and_uniforms; + _sg_shader_ref_t shader; + sg_vertex_layout_state layout; + sg_depth_state depth; + sg_stencil_state stencil; + int color_count; + sg_color_target_state colors[SG_MAX_COLOR_ATTACHMENTS]; + sg_primitive_type primitive_type; + sg_index_type index_type; + sg_cull_mode cull_mode; + sg_face_winding face_winding; + int sample_count; + sg_color blend_color; + bool alpha_to_coverage_enabled; +} _sg_pipeline_common_t; + +typedef struct { + _sg_buffer_ref_t ref; + int offset; +} _sg_buffer_view_common_t; + +typedef struct { + _sg_image_ref_t ref; + int mip_level; + int slice; + int mip_level_count; + int slice_count; +} _sg_image_view_common_t; + +typedef struct { + sg_view_type type; + _sg_buffer_view_common_t buf; + _sg_image_view_common_t img; +} _sg_view_common_t; + +#if defined(SOKOL_DUMMY_BACKEND) +typedef struct _sg_buffer_s { + _sg_slot_t slot; + _sg_buffer_common_t cmn; +} _sg_dummy_buffer_t; +typedef _sg_dummy_buffer_t _sg_buffer_t; + +typedef struct _sg_image_s { + _sg_slot_t slot; + _sg_image_common_t cmn; +} _sg_dummy_image_t; +typedef _sg_dummy_image_t _sg_image_t; + +typedef struct _sg_sampler_s { + _sg_slot_t slot; + _sg_sampler_common_t cmn; +} _sg_dummy_sampler_t; +typedef _sg_dummy_sampler_t _sg_sampler_t; + +typedef struct _sg_shader_s { + _sg_slot_t slot; + _sg_shader_common_t cmn; +} _sg_dummy_shader_t; +typedef _sg_dummy_shader_t _sg_shader_t; + +typedef struct _sg_pipeline_s { + _sg_slot_t slot; + _sg_pipeline_common_t cmn; +} _sg_dummy_pipeline_t; +typedef _sg_dummy_pipeline_t _sg_pipeline_t; + +typedef struct _sg_view_s { + _sg_slot_t slot; + _sg_view_common_t cmn; +} _sg_dummy_view_t; +typedef _sg_dummy_view_t _sg_view_t; + +#elif defined(_SOKOL_ANY_GL) + +typedef enum { + _SG_GL_GPUDIRTY_VERTEXBUFFER = (1<<0), + _SG_GL_GPUDIRTY_INDEXBUFFER = (1<<1), + _SG_GL_GPUDIRTY_STORAGEBUFFER = (1<<2), + _SG_GL_GPUDIRTY_TEXTURE = (1<<3), + _SG_GL_GPUDIRTY_STORAGEIMAGE = (1<<4), + _SG_GL_GPUDIRTY_ATTACHMENT = (1<<5), + _SG_GL_GPUDIRTY_BUFFER_ALL = _SG_GL_GPUDIRTY_VERTEXBUFFER | _SG_GL_GPUDIRTY_INDEXBUFFER | _SG_GL_GPUDIRTY_STORAGEBUFFER, + _SG_GL_GPUDIRTY_IMAGE_ALL = _SG_GL_GPUDIRTY_TEXTURE | _SG_GL_GPUDIRTY_STORAGEIMAGE | _SG_GL_GPUDIRTY_ATTACHMENT, +} _sg_gl_gpudirty_t; + +typedef struct _sg_buffer_s { + _sg_slot_t slot; + _sg_buffer_common_t cmn; + struct { + GLuint buf[SG_NUM_INFLIGHT_FRAMES]; + uint8_t gpu_dirty_flags; // combination of _sg_gl_gpudirty_t flags + bool injected; // if true, external buffers were injected with sg_buffer_desc.gl_buffers + } gl; +} _sg_gl_buffer_t; +typedef _sg_gl_buffer_t _sg_buffer_t; + +typedef struct _sg_image_s { + _sg_slot_t slot; + _sg_image_common_t cmn; + struct { + GLenum target; + GLuint tex[SG_NUM_INFLIGHT_FRAMES]; + uint8_t gpu_dirty_flags; // combination of _sg_gl_gpudirty_flags + bool injected; // if true, external textures were injected with sg_image_desc.gl_textures + } gl; +} _sg_gl_image_t; +typedef _sg_gl_image_t _sg_image_t; + +typedef struct _sg_sampler_s { + _sg_slot_t slot; + _sg_sampler_common_t cmn; + struct { + GLuint smp; + bool injected; // true if external sampler was injects in sg_sampler_desc.gl_sampler + } gl; +} _sg_gl_sampler_t; +typedef _sg_gl_sampler_t _sg_sampler_t; + +typedef struct { + GLint gl_loc; + sg_uniform_type type; + uint16_t count; + uint16_t offset; +} _sg_gl_uniform_t; + +typedef struct { + int num_uniforms; + _sg_gl_uniform_t uniforms[SG_MAX_UNIFORMBLOCK_MEMBERS]; +} _sg_gl_uniform_block_t; + +typedef struct { + _sg_str_t name; +} _sg_gl_shader_attr_t; + +typedef struct _sg_shader_s { + _sg_slot_t slot; + _sg_shader_common_t cmn; + struct { + GLuint prog; + _sg_gl_shader_attr_t attrs[SG_MAX_VERTEX_ATTRIBUTES]; + _sg_gl_uniform_block_t uniform_blocks[SG_MAX_UNIFORMBLOCK_BINDSLOTS]; + uint8_t sbuf_binding[SG_MAX_VIEW_BINDSLOTS]; + uint8_t simg_binding[SG_MAX_VIEW_BINDSLOTS]; + int8_t tex_slot[SG_MAX_TEXTURE_SAMPLER_PAIRS]; // GL texture unit index + } gl; +} _sg_gl_shader_t; +typedef _sg_gl_shader_t _sg_shader_t; + +typedef struct { + int8_t vb_index; // -1 if attr is not enabled + int8_t divisor; // -1 if not initialized + uint8_t stride; + uint8_t size; + uint8_t normalized; + int offset; + GLenum type; + sg_shader_attr_base_type base_type; +} _sg_gl_attr_t; + +typedef struct _sg_pipeline_s { + _sg_slot_t slot; + _sg_pipeline_common_t cmn; + struct { + _sg_gl_attr_t attrs[SG_MAX_VERTEX_ATTRIBUTES]; + sg_depth_state depth; + sg_stencil_state stencil; + sg_primitive_type primitive_type; + sg_blend_state blend; + sg_color_mask color_write_mask[SG_MAX_COLOR_ATTACHMENTS]; + sg_cull_mode cull_mode; + sg_face_winding face_winding; + int sample_count; + bool alpha_to_coverage_enabled; + } gl; +} _sg_gl_pipeline_t; +typedef _sg_gl_pipeline_t _sg_pipeline_t; + +typedef struct _sg_view_s { + _sg_slot_t slot; + _sg_view_common_t cmn; + struct { + GLuint tex_view[SG_NUM_INFLIGHT_FRAMES]; // only if sg_features.gl_texture_views + GLuint msaa_render_buffer; // only if !msaa_texture_bindings + GLuint msaa_resolve_frame_buffer; + } gl; +} _sg_gl_view_t; +typedef _sg_gl_view_t _sg_view_t; + +typedef struct { + _sg_gl_attr_t gl_attr; + GLuint gl_vbuf; +} _sg_gl_cache_attr_t; + +typedef struct { + GLenum target; + GLuint texture; + GLuint sampler; +} _sg_gl_cache_texture_sampler_bind_slot; + +#define _SG_GL_MAX_SBUF_BINDINGS (_SG_MAX_STORAGEBUFFER_BINDINGS_PER_STAGE) +#define _SG_GL_MAX_SIMG_BINDINGS (_SG_MAX_STORAGEIMAGE_BINDINGS_PER_STAGE) +#define _SG_GL_MAX_TEX_SMP_BINDINGS (SG_MAX_TEXTURE_SAMPLER_PAIRS) +typedef struct { + sg_depth_state depth; + sg_stencil_state stencil; + sg_blend_state blend; + sg_color_mask color_write_mask[SG_MAX_COLOR_ATTACHMENTS]; + sg_cull_mode cull_mode; + sg_face_winding face_winding; + bool polygon_offset_enabled; + int sample_count; + sg_color blend_color; + bool alpha_to_coverage_enabled; + _sg_gl_cache_attr_t attrs[SG_MAX_VERTEX_ATTRIBUTES]; + GLuint vertex_buffer; + GLuint index_buffer; + GLuint storage_buffer; // general bind point + GLuint storage_buffers[_SG_GL_MAX_SBUF_BINDINGS]; + int storage_buffer_offsets[_SG_GL_MAX_SBUF_BINDINGS]; + GLuint stored_vertex_buffer; + GLuint stored_index_buffer; + GLuint stored_storage_buffer; + GLuint prog; + _sg_gl_cache_texture_sampler_bind_slot texture_samplers[_SG_GL_MAX_TEX_SMP_BINDINGS]; + _sg_gl_cache_texture_sampler_bind_slot stored_texture_sampler; + int cur_ib_offset; + GLenum cur_primitive_type; + GLenum cur_index_type; + GLenum cur_active_texture; + _sg_sref_t cur_pip; +} _sg_gl_cache_t; + +typedef struct { + bool valid; + GLuint vao; // global mutated vertex-array-object + GLuint fb; // global mutated framebuffer + _sg_gl_cache_t cache; + bool ext_anisotropic; + GLint max_anisotropy; + sg_store_action color_store_actions[SG_MAX_COLOR_ATTACHMENTS]; + sg_store_action depth_store_action; + sg_store_action stencil_store_action; + #if _SOKOL_USE_WIN32_GL_LOADER + HINSTANCE opengl32_dll; + #endif +} _sg_gl_backend_t; + +#elif defined(SOKOL_D3D11) + +typedef struct _sg_buffer_s { + _sg_slot_t slot; + _sg_buffer_common_t cmn; + struct { + ID3D11Buffer* buf; + } d3d11; +} _sg_d3d11_buffer_t; +typedef _sg_d3d11_buffer_t _sg_buffer_t; + +typedef struct _sg_image_s { + _sg_slot_t slot; + _sg_image_common_t cmn; + struct { + DXGI_FORMAT format; + ID3D11Texture2D* tex2d; + ID3D11Texture3D* tex3d; + ID3D11Resource* res; // either tex2d or tex3d + } d3d11; +} _sg_d3d11_image_t; +typedef _sg_d3d11_image_t _sg_image_t; + +typedef struct _sg_sampler_s { + _sg_slot_t slot; + _sg_sampler_common_t cmn; + struct { + ID3D11SamplerState* smp; + } d3d11; +} _sg_d3d11_sampler_t; +typedef _sg_d3d11_sampler_t _sg_sampler_t; + +typedef struct { + _sg_str_t sem_name; + int sem_index; +} _sg_d3d11_shader_attr_t; + +#define _SG_D3D11_MAX_TEXTUREARRAY_LAYERS (2048) +#define _SG_D3D11_MAX_TEXTURE_SUBRESOURCES (SG_MAX_MIPMAPS * _SG_D3D11_MAX_TEXTUREARRAY_LAYERS) +#define _SG_D3D11_MAX_STAGE_UB_BINDINGS (_SG_MAX_UNIFORMBLOCK_BINDINGS_PER_STAGE) +#define _SG_D3D11_MAX_STAGE_SRV_BINDINGS (SG_MAX_VIEW_BINDSLOTS) +#define _SG_D3D11_MAX_STAGE_UAV_BINDINGS (SG_MAX_VIEW_BINDSLOTS) +#define _SG_D3D11_MAX_STAGE_SMP_BINDINGS (SG_MAX_SAMPLER_BINDSLOTS) + +typedef struct _sg_shader_s { + _sg_slot_t slot; + _sg_shader_common_t cmn; + struct { + _sg_d3d11_shader_attr_t attrs[SG_MAX_VERTEX_ATTRIBUTES]; + ID3D11VertexShader* vs; + ID3D11PixelShader* fs; + ID3D11ComputeShader* cs; + void* vs_blob; + size_t vs_blob_length; + uint8_t ub_register_b_n[SG_MAX_UNIFORMBLOCK_BINDSLOTS]; + uint8_t view_register_t_n[SG_MAX_VIEW_BINDSLOTS]; + uint8_t view_register_u_n[SG_MAX_VIEW_BINDSLOTS]; + uint8_t smp_register_s_n[SG_MAX_SAMPLER_BINDSLOTS]; + ID3D11Buffer* all_cbufs[SG_MAX_UNIFORMBLOCK_BINDSLOTS]; + ID3D11Buffer* vs_cbufs[_SG_D3D11_MAX_STAGE_UB_BINDINGS]; + ID3D11Buffer* fs_cbufs[_SG_D3D11_MAX_STAGE_UB_BINDINGS]; + ID3D11Buffer* cs_cbufs[_SG_D3D11_MAX_STAGE_UB_BINDINGS]; + } d3d11; +} _sg_d3d11_shader_t; +typedef _sg_d3d11_shader_t _sg_shader_t; + +typedef struct _sg_pipeline_s { + _sg_slot_t slot; + _sg_pipeline_common_t cmn; + struct { + UINT stencil_ref; + UINT vb_strides[SG_MAX_VERTEXBUFFER_BINDSLOTS]; + D3D_PRIMITIVE_TOPOLOGY topology; + DXGI_FORMAT index_format; + ID3D11InputLayout* il; + ID3D11RasterizerState* rs; + ID3D11DepthStencilState* dss; + ID3D11BlendState* bs; + } d3d11; +} _sg_d3d11_pipeline_t; +typedef _sg_d3d11_pipeline_t _sg_pipeline_t; + +typedef struct _sg_view_s { + _sg_slot_t slot; + _sg_view_common_t cmn; + struct { + ID3D11ShaderResourceView* srv; + ID3D11UnorderedAccessView* uav; + ID3D11RenderTargetView* rtv; + ID3D11DepthStencilView* dsv; + } d3d11; +} _sg_d3d11_view_t; +typedef _sg_d3d11_view_t _sg_view_t; + +typedef struct { + bool valid; + ID3D11Device* dev; + ID3D11DeviceContext* ctx; + struct { + ID3D11RenderTargetView* render_view; + ID3D11RenderTargetView* resolve_view; + } cur_swapchain; + // on-demand loaded d3dcompiler_47.dll handles + HINSTANCE d3dcompiler_dll; + bool d3dcompiler_dll_load_failed; + pD3DCompile D3DCompile_func; + // static bindings arrays + struct { + ID3D11Buffer* vbs[SG_MAX_VERTEXBUFFER_BINDSLOTS]; + UINT vb_offsets[SG_MAX_VERTEXBUFFER_BINDSLOTS]; + ID3D11ShaderResourceView* vs_srvs[_SG_D3D11_MAX_STAGE_SRV_BINDINGS]; + ID3D11ShaderResourceView* fs_srvs[_SG_D3D11_MAX_STAGE_SRV_BINDINGS]; + ID3D11ShaderResourceView* cs_srvs[_SG_D3D11_MAX_STAGE_SRV_BINDINGS]; + ID3D11UnorderedAccessView* cs_uavs[_SG_D3D11_MAX_STAGE_UAV_BINDINGS]; + ID3D11SamplerState* vs_smps[_SG_D3D11_MAX_STAGE_SMP_BINDINGS]; + ID3D11SamplerState* fs_smps[_SG_D3D11_MAX_STAGE_SMP_BINDINGS]; + ID3D11SamplerState* cs_smps[_SG_D3D11_MAX_STAGE_SMP_BINDINGS]; + } bnd; + // global subresourcedata array for texture updates + D3D11_SUBRESOURCE_DATA subres_data[_SG_D3D11_MAX_TEXTURE_SUBRESOURCES]; +} _sg_d3d11_backend_t; + +#elif defined(SOKOL_METAL) + +#if defined(_SG_TARGET_MACOS) || defined(_SG_TARGET_IOS_SIMULATOR) +#define _SG_MTL_UB_ALIGN (256) +#else +#define _SG_MTL_UB_ALIGN (16) +#endif +#define _SG_MTL_INVALID_SLOT_INDEX (0) + +typedef struct { + uint32_t frame_index; // frame index at which it is safe to release this resource + int slot_index; +} _sg_mtl_release_item_t; + +typedef struct { + NSMutableArray* pool; + int num_slots; + int free_queue_top; + int* free_queue; + int release_queue_front; + int release_queue_back; + _sg_mtl_release_item_t* release_queue; +} _sg_mtl_idpool_t; + +typedef struct _sg_buffer_s { + _sg_slot_t slot; + _sg_buffer_common_t cmn; + struct { + int buf[SG_NUM_INFLIGHT_FRAMES]; // index into _sg_mtl_pool + } mtl; +} _sg_mtl_buffer_t; +typedef _sg_mtl_buffer_t _sg_buffer_t; + +typedef struct _sg_image_s { + _sg_slot_t slot; + _sg_image_common_t cmn; + struct { + int tex[SG_NUM_INFLIGHT_FRAMES]; + } mtl; +} _sg_mtl_image_t; +typedef _sg_mtl_image_t _sg_image_t; + +typedef struct _sg_sampler_s { + _sg_slot_t slot; + _sg_sampler_common_t cmn; + struct { + int sampler_state; + } mtl; +} _sg_mtl_sampler_t; +typedef _sg_mtl_sampler_t _sg_sampler_t; + +typedef struct { + int mtl_lib; + int mtl_func; +} _sg_mtl_shader_func_t; + +typedef struct _sg_shader_s { + _sg_slot_t slot; + _sg_shader_common_t cmn; + struct { + _sg_mtl_shader_func_t vertex_func; + _sg_mtl_shader_func_t fragment_func; + _sg_mtl_shader_func_t compute_func; + MTLSize threads_per_threadgroup; + uint8_t ub_buffer_n[SG_MAX_UNIFORMBLOCK_BINDSLOTS]; + uint8_t view_buffer_texture_n[SG_MAX_VIEW_BINDSLOTS]; + uint8_t smp_sampler_n[SG_MAX_SAMPLER_BINDSLOTS]; + } mtl; +} _sg_mtl_shader_t; +typedef _sg_mtl_shader_t _sg_shader_t; + +typedef struct _sg_pipeline_s { + _sg_slot_t slot; + _sg_pipeline_common_t cmn; + struct { + MTLPrimitiveType prim_type; + int index_size; + MTLIndexType index_type; + MTLCullMode cull_mode; + MTLWinding winding; + uint32_t stencil_ref; + MTLSize threads_per_threadgroup; + int cps; // MTLComputePipelineState + int rps; // MTLRenderPipelineState + int dss; // MTLDepthStencilState + } mtl; +} _sg_mtl_pipeline_t; +typedef _sg_mtl_pipeline_t _sg_pipeline_t; + +typedef struct _sg_view_s { + _sg_slot_t slot; + _sg_view_common_t cmn; + struct { + int tex_view[SG_NUM_INFLIGHT_FRAMES]; + } mtl; +} _sg_mtl_view_t; +typedef _sg_mtl_view_t _sg_view_t; + +// resource binding state cache +// +// NOTE: reserved buffer bindslot ranges: +// - 0..<=7: uniform buffer bindings +// - 8..<=22: storage buffer bindings +// - 23..<=30: vertex buffer bindings +// +#define _SG_MTL_MAX_STAGE_BUFFER_BINDINGS (31) // see: https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf +#define _SG_MTL_MAX_STAGE_UB_BINDINGS (_SG_MAX_UNIFORMBLOCK_BINDINGS_PER_STAGE) +#define _SG_MTL_MAX_STAGE_UB_SBUF_BINDINGS (_SG_MTL_MAX_STAGE_BUFFER_BINDINGS - SG_MAX_VERTEXBUFFER_BINDSLOTS) +#define _SG_MTL_MAX_STAGE_TEXTURE_BINDINGS (SG_MAX_VIEW_BINDSLOTS) +#define _SG_MTL_MAX_STAGE_SAMPLER_BINDINGS (SG_MAX_SAMPLER_BINDSLOTS) + +typedef struct { + _sg_sref_t sref; + int active_slot; + int offset; +} _sg_mtl_cache_buf_t; + +typedef struct { + _sg_sref_t sref; + int active_slot; +} _sg_mtl_cache_tex_t; + +typedef enum { + _SG_MTL_CACHE_CMP_EQUAL = 0, + _SG_MTL_CACHE_CMP_SREF = (1<<1), + _SG_MTL_CACHE_CMP_OFFSET = (1<<2), + _SG_MTL_CACHE_CMP_ACTIVESLOT = (1<<3), +} _sg_mtl_cache_cmp_result_t; + +typedef struct { + _sg_sref_t cur_pip; + _sg_buffer_ref_t cur_ibuf; + int cur_ibuf_offset; + _sg_mtl_cache_buf_t cur_vsbufs[_SG_MTL_MAX_STAGE_BUFFER_BINDINGS]; + _sg_mtl_cache_buf_t cur_fsbufs[_SG_MTL_MAX_STAGE_BUFFER_BINDINGS]; + _sg_mtl_cache_buf_t cur_csbufs[_SG_MTL_MAX_STAGE_BUFFER_BINDINGS]; + _sg_mtl_cache_tex_t cur_vstexs[_SG_MTL_MAX_STAGE_TEXTURE_BINDINGS]; + _sg_mtl_cache_tex_t cur_fstexs[_SG_MTL_MAX_STAGE_TEXTURE_BINDINGS]; + _sg_mtl_cache_tex_t cur_cstexs[_SG_MTL_MAX_STAGE_TEXTURE_BINDINGS]; + _sg_sref_t cur_vssmps[_SG_MTL_MAX_STAGE_SAMPLER_BINDINGS]; + _sg_sref_t cur_fssmps[_SG_MTL_MAX_STAGE_SAMPLER_BINDINGS]; + _sg_sref_t cur_cssmps[_SG_MTL_MAX_STAGE_SAMPLER_BINDINGS]; +} _sg_mtl_cache_t; + +typedef struct { + bool valid; + bool use_shared_storage_mode; + uint32_t cur_frame_rotate_index; + int ub_size; + int cur_ub_offset; + uint8_t* cur_ub_base_ptr; + _sg_mtl_cache_t cache; + _sg_mtl_idpool_t idpool; + dispatch_semaphore_t sem; + id device; + id cmd_queue; + id cmd_buffer; + id render_cmd_encoder; + id compute_cmd_encoder; + id cur_drawable; + id uniform_buffers[SG_NUM_INFLIGHT_FRAMES]; +} _sg_mtl_backend_t; + +#elif defined(SOKOL_WGPU) + +#define _SG_WGPU_ROWPITCH_ALIGN (256) +#define _SG_WGPU_MAX_UNIFORM_UPDATE_SIZE (1<<16) // also see WGPULimits.maxUniformBufferBindingSize +#define _SG_WGPU_MAX_BINDGROUPS (2) // 0: uniforms, 1: images, samplers, storage buffers, storage images +#define _SG_WGPU_UB_BINDGROUP_INDEX (0) +#define _SG_WGPU_VIEW_SMP_BINDGROUP_INDEX (1) +#define _SG_WGPU_MAX_UB_BINDGROUP_ENTRIES (SG_MAX_UNIFORMBLOCK_BINDSLOTS) +#define _SG_WGPU_MAX_UB_BINDGROUP_WGSL_SLOTS (2 * SG_MAX_UNIFORMBLOCK_BINDSLOTS) +#define _SG_WGPU_MAX_VIEW_SMP_BINDGROUP_ENTRIES (SG_MAX_VIEW_BINDSLOTS + SG_MAX_SAMPLER_BINDSLOTS) +#define _SG_WGPU_MAX_VIEW_SMP_BINDGROUP_WGSL_SLOTS (128) + +typedef struct _sg_buffer_s { + _sg_slot_t slot; + _sg_buffer_common_t cmn; + struct { + WGPUBuffer buf; + } wgpu; +} _sg_wgpu_buffer_t; +typedef _sg_wgpu_buffer_t _sg_buffer_t; + +typedef struct _sg_image_s { + _sg_slot_t slot; + _sg_image_common_t cmn; + struct { + WGPUTexture tex; + } wgpu; +} _sg_wgpu_image_t; +typedef _sg_wgpu_image_t _sg_image_t; + +typedef struct _sg_sampler_s { + _sg_slot_t slot; + _sg_sampler_common_t cmn; + struct { + WGPUSampler smp; + } wgpu; +} _sg_wgpu_sampler_t; +typedef _sg_wgpu_sampler_t _sg_sampler_t; + +typedef struct { + WGPUShaderModule module; + _sg_str_t entry; +} _sg_wgpu_shader_func_t; + +typedef struct _sg_shader_s { + _sg_slot_t slot; + _sg_shader_common_t cmn; + struct { + _sg_wgpu_shader_func_t vertex_func; + _sg_wgpu_shader_func_t fragment_func; + _sg_wgpu_shader_func_t compute_func; + WGPUBindGroupLayout bgl_ub; + WGPUBindGroup bg_ub; + WGPUBindGroupLayout bgl_view_smp; + // a mapping of sokol-gfx bind slots to setBindGroup dynamic-offset-array indices + uint8_t ub_num_dynoffsets; + uint8_t ub_dynoffsets[SG_MAX_UNIFORMBLOCK_BINDSLOTS]; + // indexed by sokol-gfx bind slot: + uint8_t ub_grp0_bnd_n[SG_MAX_UNIFORMBLOCK_BINDSLOTS]; + uint8_t view_grp1_bnd_n[SG_MAX_VIEW_BINDSLOTS]; + uint8_t smp_grp1_bnd_n[SG_MAX_SAMPLER_BINDSLOTS]; + } wgpu; +} _sg_wgpu_shader_t; +typedef _sg_wgpu_shader_t _sg_shader_t; + +typedef struct _sg_pipeline_s { + _sg_slot_t slot; + _sg_pipeline_common_t cmn; + struct { + WGPURenderPipeline rpip; + WGPUComputePipeline cpip; + WGPUColor blend_color; + } wgpu; +} _sg_wgpu_pipeline_t; +typedef _sg_wgpu_pipeline_t _sg_pipeline_t; + +typedef struct _sg_view_s { + _sg_slot_t slot; + _sg_view_common_t cmn; + struct { + WGPUTextureView view; + } wgpu; +} _sg_wgpu_view_t; +typedef _sg_wgpu_view_t _sg_view_t; + +// a pool of per-frame uniform buffers +typedef struct { + uint32_t num_bytes; + uint32_t offset; // current offset into buf + uint8_t* staging; // intermediate buffer for uniform data updates + WGPUBuffer buf; // the GPU-side uniform buffer + uint32_t bind_offsets[SG_MAX_UNIFORMBLOCK_BINDSLOTS]; // NOTE: index is sokol-gfx ub slot index! +} _sg_wgpu_uniform_buffer_t; + +typedef struct { + uint32_t id; +} _sg_wgpu_bindgroup_handle_t; + +typedef enum { + _SG_WGPU_BINDGROUPSCACHEITEMTYPE_NONE = 0, + _SG_WGPU_BINDGROUPSCACHEITEMTYPE_VIEW = 1, + _SG_WGPU_BINDGROUPSCACHEITEMTYPE_SAMPLER = 2, + _SG_WGPU_BINDGROUPSCACHEITEMTYPE_PIPELINE = 3, +} _sg_wgpu_bindgroups_cache_item_type_t; + +#define _SG_WGPU_BINDGROUPSCACHEKEY_NUM_ITEMS (1 + _SG_WGPU_MAX_VIEW_SMP_BINDGROUP_ENTRIES) +typedef struct { + uint64_t hash; + // the format of cache key items is BBTCCCCCIIIIIIII + // where + // - BB: 8 bits WGPU binding + // - T: 2 bits _sg_wgpu_bindgroups_cache_item_type_t + // - CCCCC: 22 bits slot.uninit_count + // - IIIIIIII: 32 bits slot.id + // + // where the item type is a per-resource-type bit pattern + uint64_t items[_SG_WGPU_BINDGROUPSCACHEKEY_NUM_ITEMS]; +} _sg_wgpu_bindgroups_cache_key_t; + +typedef struct { + uint32_t num; // must be 2^n + uint32_t index_mask; // mask to turn hash into valid index + _sg_wgpu_bindgroup_handle_t* items; +} _sg_wgpu_bindgroups_cache_t; + +typedef struct { + _sg_slot_t slot; + WGPUBindGroup bindgroup; + _sg_wgpu_bindgroups_cache_key_t key; +} _sg_wgpu_bindgroup_t; + +typedef struct { + _sg_pool_t pool; + _sg_wgpu_bindgroup_t* bindgroups; +} _sg_wgpu_bindgroups_pool_t; + +typedef struct { + struct { + sg_buffer buffer; + uint64_t offset; + } vbs[SG_MAX_VERTEXBUFFER_BINDSLOTS]; + struct { + sg_buffer buffer; + uint64_t offset; + } ib; + _sg_wgpu_bindgroup_handle_t bg; +} _sg_wgpu_bindings_cache_t; + +// the WGPU backend state +typedef struct { + bool valid; + WGPUDevice dev; + WGPULimits limits; + WGPUQueue queue; + WGPUCommandEncoder cmd_enc; + WGPURenderPassEncoder rpass_enc; + WGPUComputePassEncoder cpass_enc; + WGPUBindGroup empty_bind_group; + _sg_wgpu_uniform_buffer_t uniform; + _sg_wgpu_bindings_cache_t bindings_cache; + _sg_wgpu_bindgroups_cache_t bindgroups_cache; + _sg_wgpu_bindgroups_pool_t bindgroups_pool; +} _sg_wgpu_backend_t; +#endif // SOKOL_WGPU + +// this *MUST* remain 0 +#define _SG_INVALID_SLOT_INDEX (0) + +typedef struct _sg_pools_s { + _sg_pool_t buffer_pool; + _sg_pool_t image_pool; + _sg_pool_t sampler_pool; + _sg_pool_t shader_pool; + _sg_pool_t pipeline_pool; + _sg_pool_t view_pool; + _sg_buffer_t* buffers; + _sg_image_t* images; + _sg_sampler_t* samplers; + _sg_shader_t* shaders; + _sg_pipeline_t* pipelines; + _sg_view_t* views; +} _sg_pools_t; + +typedef struct { + int num; // number of allocated commit listener items + int upper; // the current upper index (no valid items past this point) + sg_commit_listener* items; +} _sg_commit_listeners_t; + +// resolved pass attachments struct +typedef struct { + bool empty; + int num_color_views; + _sg_view_t* color_views[SG_MAX_COLOR_ATTACHMENTS]; + _sg_view_t* resolve_views[SG_MAX_COLOR_ATTACHMENTS]; + _sg_view_t* ds_view; +} _sg_attachments_ptrs_t; + +// resolved resource bindings struct +typedef struct { + _sg_pipeline_t* pip; + int vb_offsets[SG_MAX_VERTEXBUFFER_BINDSLOTS]; + int ib_offset; + _sg_buffer_t* vbs[SG_MAX_VERTEXBUFFER_BINDSLOTS]; + _sg_buffer_t* ib; + _sg_view_t* views[SG_MAX_VIEW_BINDSLOTS]; + _sg_sampler_t* smps[SG_MAX_SAMPLER_BINDSLOTS]; +} _sg_bindings_ptrs_t; + +typedef struct { + bool sample; + bool filter; + bool render; + bool blend; + bool msaa; + bool depth; + bool read; + bool write; +} _sg_pixelformat_info_t; + +typedef struct { + bool valid; + sg_desc desc; // original desc with default values patched in + uint32_t frame_index; + struct { + bool valid; + bool in_pass; + bool is_compute; + _sg_dimi_t dim; + sg_attachments atts; + struct { + sg_pixel_format color_fmt; + sg_pixel_format depth_fmt; + int sample_count; + } swapchain; + } cur_pass; + _sg_pipeline_ref_t cur_pip; + bool next_draw_valid; + bool use_indexed_draw; + bool use_instanced_draw; + uint32_t required_bindings_and_uniforms; // used to check that bindings and uniforms are applied after applying pipeline + uint32_t applied_bindings_and_uniforms; // bits 0..7: uniform blocks, bit 8: bindings + #if defined(SOKOL_DEBUG) + sg_log_item validate_error; + #endif + _sg_pools_t pools; + sg_backend backend; + sg_features features; + sg_limits limits; + _sg_pixelformat_info_t formats[_SG_PIXELFORMAT_NUM]; + bool stats_enabled; + sg_frame_stats stats; + sg_frame_stats prev_stats; + #if defined(_SOKOL_ANY_GL) + _sg_gl_backend_t gl; + #elif defined(SOKOL_METAL) + _sg_mtl_backend_t mtl; + #elif defined(SOKOL_D3D11) + _sg_d3d11_backend_t d3d11; + #elif defined(SOKOL_WGPU) + _sg_wgpu_backend_t wgpu; + #endif + #if defined(SOKOL_TRACE_HOOKS) + sg_trace_hooks hooks; + #endif + _sg_commit_listeners_t commit_listeners; +} _sg_state_t; +static _sg_state_t _sg; + +// ██ ██████ ██████ ██████ ██ ███ ██ ██████ +// ██ ██ ██ ██ ██ ██ ████ ██ ██ +// ██ ██ ██ ██ ███ ██ ███ ██ ██ ██ ██ ██ ███ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ███████ ██████ ██████ ██████ ██ ██ ████ ██████ +// +// >>logging +#if defined(SOKOL_DEBUG) +#define _SG_LOGITEM_XMACRO(item,msg) #item ": " msg, +static const char* _sg_log_messages[] = { + _SG_LOG_ITEMS +}; +#undef _SG_LOGITEM_XMACRO +#endif // SOKOL_DEBUG + +#define _SG_PANIC(code) _sg_log(SG_LOGITEM_ ##code, 0, 0, __LINE__) +#define _SG_ERROR(code) _sg_log(SG_LOGITEM_ ##code, 1, 0, __LINE__) +#define _SG_WARN(code) _sg_log(SG_LOGITEM_ ##code, 2, 0, __LINE__) +#define _SG_INFO(code) _sg_log(SG_LOGITEM_ ##code, 3, 0, __LINE__) +#define _SG_LOGMSG(code,msg) _sg_log(SG_LOGITEM_ ##code, 3, msg, __LINE__) +#define _SG_VALIDATE(cond,code) if (!(cond)){ _sg.validate_error = SG_LOGITEM_ ##code; _sg_log(SG_LOGITEM_ ##code, 1, 0, __LINE__); } + +static void _sg_log(sg_log_item log_item, uint32_t log_level, const char* msg, uint32_t line_nr) { + if (_sg.desc.logger.func) { + const char* filename = 0; + #if defined(SOKOL_DEBUG) + filename = __FILE__; + if (0 == msg) { + msg = _sg_log_messages[log_item]; + } + #endif + _sg.desc.logger.func("sg", log_level, (uint32_t)log_item, msg, line_nr, filename, _sg.desc.logger.user_data); + } else { + // for log level PANIC it would be 'undefined behaviour' to continue + if (log_level == 0) { + abort(); + } + } +} + +// ███ ███ ███████ ███ ███ ██████ ██████ ██ ██ +// ████ ████ ██ ████ ████ ██ ██ ██ ██ ██ ██ +// ██ ████ ██ █████ ██ ████ ██ ██ ██ ██████ ████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██ ██ ███████ ██ ██ ██████ ██ ██ ██ +// +// >>memory + +// a helper macro to clear a struct with potentially ARC'ed ObjC references +#if defined(SOKOL_METAL) + #if defined(__cplusplus) + #define _SG_CLEAR_ARC_STRUCT(type, item) { item = type(); } + #else + #define _SG_CLEAR_ARC_STRUCT(type, item) { item = (type) { 0 }; } + #endif +#else + #define _SG_CLEAR_ARC_STRUCT(type, item) { _sg_clear(&item, sizeof(item)); } +#endif + +_SOKOL_PRIVATE void _sg_clear(void* ptr, size_t size) { + SOKOL_ASSERT(ptr && (size > 0)); + memset(ptr, 0, size); +} + +_SOKOL_PRIVATE void* _sg_malloc(size_t size) { + SOKOL_ASSERT(size > 0); + void* ptr; + if (_sg.desc.allocator.alloc_fn) { + ptr = _sg.desc.allocator.alloc_fn(size, _sg.desc.allocator.user_data); + } else { + ptr = malloc(size); + } + if (0 == ptr) { + _SG_PANIC(MALLOC_FAILED); + } + return ptr; +} + +_SOKOL_PRIVATE void* _sg_malloc_clear(size_t size) { + void* ptr = _sg_malloc(size); + _sg_clear(ptr, size); + return ptr; +} + +_SOKOL_PRIVATE void _sg_free(void* ptr) { + if (_sg.desc.allocator.free_fn) { + _sg.desc.allocator.free_fn(ptr, _sg.desc.allocator.user_data); + } else { + free(ptr); + } +} + +_SOKOL_PRIVATE bool _sg_strempty(const _sg_str_t* str) { + return 0 == str->buf[0]; +} + +_SOKOL_PRIVATE const char* _sg_strptr(const _sg_str_t* str) { + return &str->buf[0]; +} + +_SOKOL_PRIVATE void _sg_strcpy(_sg_str_t* dst, const char* src) { + SOKOL_ASSERT(dst); + if (src) { + #if defined(_MSC_VER) + strncpy_s(dst->buf, _SG_STRING_SIZE, src, (_SG_STRING_SIZE-1)); + #else + strncpy(dst->buf, src, _SG_STRING_SIZE); + #endif + dst->buf[_SG_STRING_SIZE-1] = 0; + } else { + _sg_clear(dst->buf, _SG_STRING_SIZE); + } +} + +// ██████ ██████ ██████ ██ +// ██ ██ ██ ██ ██ ██ ██ +// ██████ ██ ██ ██ ██ ██ +// ██ ██ ██ ██ ██ ██ +// ██ ██████ ██████ ███████ +// +// >>pool +_SOKOL_PRIVATE void _sg_pool_init(_sg_pool_t* pool, int num) { + SOKOL_ASSERT(pool && (num >= 1)); + // slot 0 is reserved for the 'invalid id', so bump the pool size by 1 + pool->size = num + 1; + pool->queue_top = 0; + // generation counters indexable by pool slot index, slot 0 is reserved + size_t gen_ctrs_size = sizeof(uint32_t) * (size_t)pool->size; + pool->gen_ctrs = (uint32_t*)_sg_malloc_clear(gen_ctrs_size); + // it's not a bug to only reserve 'num' here + pool->free_queue = (int*) _sg_malloc_clear(sizeof(int) * (size_t)num); + // never allocate the zero-th pool item since the invalid id is 0 + for (int i = pool->size-1; i >= 1; i--) { + pool->free_queue[pool->queue_top++] = i; + } +} + +_SOKOL_PRIVATE void _sg_pool_discard(_sg_pool_t* pool) { + SOKOL_ASSERT(pool); + SOKOL_ASSERT(pool->free_queue); + _sg_free(pool->free_queue); + pool->free_queue = 0; + SOKOL_ASSERT(pool->gen_ctrs); + _sg_free(pool->gen_ctrs); + pool->gen_ctrs = 0; + pool->size = 0; + pool->queue_top = 0; +} + +_SOKOL_PRIVATE int _sg_pool_alloc_index(_sg_pool_t* pool) { + SOKOL_ASSERT(pool); + SOKOL_ASSERT(pool->free_queue); + if (pool->queue_top > 0) { + int slot_index = pool->free_queue[--pool->queue_top]; + SOKOL_ASSERT((slot_index > 0) && (slot_index < pool->size)); + return slot_index; + } else { + // pool exhausted + return _SG_INVALID_SLOT_INDEX; + } +} + +_SOKOL_PRIVATE void _sg_pool_free_index(_sg_pool_t* pool, int slot_index) { + SOKOL_ASSERT((slot_index > _SG_INVALID_SLOT_INDEX) && (slot_index < pool->size)); + SOKOL_ASSERT(pool); + SOKOL_ASSERT(pool->free_queue); + SOKOL_ASSERT(pool->queue_top < pool->size); + #ifdef SOKOL_DEBUG + // debug check against double-free + for (int i = 0; i < pool->queue_top; i++) { + SOKOL_ASSERT(pool->free_queue[i] != slot_index); + } + #endif + pool->free_queue[pool->queue_top++] = slot_index; + SOKOL_ASSERT(pool->queue_top <= (pool->size-1)); +} + +_SOKOL_PRIVATE void _sg_slot_reset(_sg_slot_t* slot) { + SOKOL_ASSERT(slot); + _sg_clear(slot, sizeof(_sg_slot_t)); +} + +_SOKOL_PRIVATE void _sg_reset_buffer_to_alloc_state(_sg_buffer_t* buf) { + SOKOL_ASSERT(buf); + _sg_slot_t slot = buf->slot; + _sg_clear(buf, sizeof(*buf)); + buf->slot = slot; + buf->slot.uninit_count += 1; + buf->slot.state = SG_RESOURCESTATE_ALLOC; +} + +_SOKOL_PRIVATE void _sg_reset_image_to_alloc_state(_sg_image_t* img) { + SOKOL_ASSERT(img); + _sg_slot_t slot = img->slot; + _sg_clear(img, sizeof(*img)); + img->slot = slot; + img->slot.uninit_count += 1; + img->slot.state = SG_RESOURCESTATE_ALLOC; +} + +_SOKOL_PRIVATE void _sg_reset_sampler_to_alloc_state(_sg_sampler_t* smp) { + SOKOL_ASSERT(smp); + _sg_slot_t slot = smp->slot; + _sg_clear(smp, sizeof(*smp)); + smp->slot = slot; + smp->slot.uninit_count += 1; + smp->slot.state = SG_RESOURCESTATE_ALLOC; +} + +_SOKOL_PRIVATE void _sg_reset_shader_to_alloc_state(_sg_shader_t* shd) { + SOKOL_ASSERT(shd); + _sg_slot_t slot = shd->slot; + _sg_clear(shd, sizeof(*shd)); + shd->slot = slot; + shd->slot.uninit_count += 1; + shd->slot.state = SG_RESOURCESTATE_ALLOC; +} + +_SOKOL_PRIVATE void _sg_reset_pipeline_to_alloc_state(_sg_pipeline_t* pip) { + SOKOL_ASSERT(pip); + _sg_slot_t slot = pip->slot; + _sg_clear(pip, sizeof(*pip)); + pip->slot = slot; + pip->slot.uninit_count += 1; + pip->slot.state = SG_RESOURCESTATE_ALLOC; +} + +_SOKOL_PRIVATE void _sg_reset_view_to_alloc_state(_sg_view_t* view) { + SOKOL_ASSERT(view); + _sg_slot_t slot = view->slot; + _sg_clear(view, sizeof(*view)); + view->slot = slot; + view->slot.uninit_count += 1; + view->slot.state = SG_RESOURCESTATE_ALLOC; +} + +_SOKOL_PRIVATE void _sg_setup_pools(_sg_pools_t* p, const sg_desc* desc) { + SOKOL_ASSERT(p); + SOKOL_ASSERT(desc); + // note: the pools here will have an additional item, since slot 0 is reserved + SOKOL_ASSERT((desc->buffer_pool_size > 0) && (desc->buffer_pool_size < _SG_MAX_POOL_SIZE)); + _sg_pool_init(&p->buffer_pool, desc->buffer_pool_size); + size_t buffer_pool_byte_size = sizeof(_sg_buffer_t) * (size_t)p->buffer_pool.size; + p->buffers = (_sg_buffer_t*) _sg_malloc_clear(buffer_pool_byte_size); + + SOKOL_ASSERT((desc->image_pool_size > 0) && (desc->image_pool_size < _SG_MAX_POOL_SIZE)); + _sg_pool_init(&p->image_pool, desc->image_pool_size); + size_t image_pool_byte_size = sizeof(_sg_image_t) * (size_t)p->image_pool.size; + p->images = (_sg_image_t*) _sg_malloc_clear(image_pool_byte_size); + + SOKOL_ASSERT((desc->sampler_pool_size > 0) && (desc->sampler_pool_size < _SG_MAX_POOL_SIZE)); + _sg_pool_init(&p->sampler_pool, desc->sampler_pool_size); + size_t sampler_pool_byte_size = sizeof(_sg_sampler_t) * (size_t)p->sampler_pool.size; + p->samplers = (_sg_sampler_t*) _sg_malloc_clear(sampler_pool_byte_size); + + SOKOL_ASSERT((desc->shader_pool_size > 0) && (desc->shader_pool_size < _SG_MAX_POOL_SIZE)); + _sg_pool_init(&p->shader_pool, desc->shader_pool_size); + size_t shader_pool_byte_size = sizeof(_sg_shader_t) * (size_t)p->shader_pool.size; + p->shaders = (_sg_shader_t*) _sg_malloc_clear(shader_pool_byte_size); + + SOKOL_ASSERT((desc->pipeline_pool_size > 0) && (desc->pipeline_pool_size < _SG_MAX_POOL_SIZE)); + _sg_pool_init(&p->pipeline_pool, desc->pipeline_pool_size); + size_t pipeline_pool_byte_size = sizeof(_sg_pipeline_t) * (size_t)p->pipeline_pool.size; + p->pipelines = (_sg_pipeline_t*) _sg_malloc_clear(pipeline_pool_byte_size); + + SOKOL_ASSERT((desc->view_pool_size > 0) && (desc->view_pool_size < _SG_MAX_POOL_SIZE)); + _sg_pool_init(&p->view_pool, desc->view_pool_size); + size_t view_pool_byte_size = sizeof(_sg_view_t) * (size_t)p->view_pool.size; + p->views = (_sg_view_t*) _sg_malloc_clear(view_pool_byte_size); +} + +_SOKOL_PRIVATE void _sg_discard_pools(_sg_pools_t* p) { + SOKOL_ASSERT(p); + _sg_free(p->views); p->views = 0; + _sg_free(p->pipelines); p->pipelines = 0; + _sg_free(p->shaders); p->shaders = 0; + _sg_free(p->samplers); p->samplers = 0; + _sg_free(p->images); p->images = 0; + _sg_free(p->buffers); p->buffers = 0; + _sg_pool_discard(&p->view_pool); + _sg_pool_discard(&p->pipeline_pool); + _sg_pool_discard(&p->shader_pool); + _sg_pool_discard(&p->sampler_pool); + _sg_pool_discard(&p->image_pool); + _sg_pool_discard(&p->buffer_pool); +} + +/* allocate the slot at slot_index: + - bump the slot's generation counter + - create a resource id from the generation counter and slot index + - set the slot's id to this id + - set the slot's state to ALLOC + - return the resource id +*/ +_SOKOL_PRIVATE uint32_t _sg_slot_alloc(_sg_pool_t* pool, _sg_slot_t* slot, int slot_index) { + /* FIXME: add handling for an overflowing generation counter, + for now, just overflow (another option is to disable + the slot) + */ + SOKOL_ASSERT(pool && pool->gen_ctrs); + SOKOL_ASSERT((slot_index > _SG_INVALID_SLOT_INDEX) && (slot_index < pool->size)); + SOKOL_ASSERT(slot->id == SG_INVALID_ID); + SOKOL_ASSERT(slot->state == SG_RESOURCESTATE_INITIAL); + uint32_t ctr = ++pool->gen_ctrs[slot_index]; + slot->id = (ctr<<_SG_SLOT_SHIFT)|(slot_index & _SG_SLOT_MASK); + slot->state = SG_RESOURCESTATE_ALLOC; + return slot->id; +} + +// extract slot index from id +_SOKOL_PRIVATE int _sg_slot_index(uint32_t id) { + int slot_index = (int) (id & _SG_SLOT_MASK); + SOKOL_ASSERT(_SG_INVALID_SLOT_INDEX != slot_index); + return slot_index; +} + +// returns pointer to resource by id without matching id check +_SOKOL_PRIVATE _sg_buffer_t* _sg_buffer_at(uint32_t buf_id) { + SOKOL_ASSERT(SG_INVALID_ID != buf_id); + int slot_index = _sg_slot_index(buf_id); + SOKOL_ASSERT((slot_index > _SG_INVALID_SLOT_INDEX) && (slot_index < _sg.pools.buffer_pool.size)); + return &_sg.pools.buffers[slot_index]; +} + +_SOKOL_PRIVATE _sg_image_t* _sg_image_at(uint32_t img_id) { + SOKOL_ASSERT(SG_INVALID_ID != img_id); + int slot_index = _sg_slot_index(img_id); + SOKOL_ASSERT((slot_index > _SG_INVALID_SLOT_INDEX) && (slot_index < _sg.pools.image_pool.size)); + return &_sg.pools.images[slot_index]; +} + +_SOKOL_PRIVATE _sg_sampler_t* _sg_sampler_at(uint32_t smp_id) { + SOKOL_ASSERT(SG_INVALID_ID != smp_id); + int slot_index = _sg_slot_index(smp_id); + SOKOL_ASSERT((slot_index > _SG_INVALID_SLOT_INDEX) && (slot_index < _sg.pools.sampler_pool.size)); + return &_sg.pools.samplers[slot_index]; +} + +_SOKOL_PRIVATE _sg_shader_t* _sg_shader_at(uint32_t shd_id) { + SOKOL_ASSERT(SG_INVALID_ID != shd_id); + int slot_index = _sg_slot_index(shd_id); + SOKOL_ASSERT((slot_index > _SG_INVALID_SLOT_INDEX) && (slot_index < _sg.pools.shader_pool.size)); + return &_sg.pools.shaders[slot_index]; +} + +_SOKOL_PRIVATE _sg_pipeline_t* _sg_pipeline_at(uint32_t pip_id) { + SOKOL_ASSERT(SG_INVALID_ID != pip_id); + int slot_index = _sg_slot_index(pip_id); + SOKOL_ASSERT((slot_index > _SG_INVALID_SLOT_INDEX) && (slot_index < _sg.pools.pipeline_pool.size)); + return &_sg.pools.pipelines[slot_index]; +} + +_SOKOL_PRIVATE _sg_view_t* _sg_view_at(uint32_t view_id) { + SOKOL_ASSERT(SG_INVALID_ID != view_id); + int slot_index = _sg_slot_index(view_id); + SOKOL_ASSERT((slot_index > _SG_INVALID_SLOT_INDEX) && (slot_index < _sg.pools.view_pool.size)); + return &_sg.pools.views[slot_index]; +} + +// returns pointer to resource with matching id check, may return 0 +_SOKOL_PRIVATE _sg_buffer_t* _sg_lookup_buffer(uint32_t buf_id) { + if (SG_INVALID_ID != buf_id) { + _sg_buffer_t* buf = _sg_buffer_at(buf_id); + if (buf->slot.id == buf_id) { + return buf; + } + } + return 0; +} + +_SOKOL_PRIVATE _sg_image_t* _sg_lookup_image(uint32_t img_id) { + if (SG_INVALID_ID != img_id) { + _sg_image_t* img = _sg_image_at(img_id); + if (img->slot.id == img_id) { + return img; + } + } + return 0; +} + +_SOKOL_PRIVATE _sg_sampler_t* _sg_lookup_sampler(uint32_t smp_id) { + if (SG_INVALID_ID != smp_id) { + _sg_sampler_t* smp = _sg_sampler_at(smp_id); + if (smp->slot.id == smp_id) { + return smp; + } + } + return 0; +} + +_SOKOL_PRIVATE _sg_shader_t* _sg_lookup_shader(uint32_t shd_id) { + if (SG_INVALID_ID != shd_id) { + _sg_shader_t* shd = _sg_shader_at(shd_id); + if (shd->slot.id == shd_id) { + return shd; + } + } + return 0; +} + +_SOKOL_PRIVATE _sg_pipeline_t* _sg_lookup_pipeline(uint32_t pip_id) { + if (SG_INVALID_ID != pip_id) { + _sg_pipeline_t* pip = _sg_pipeline_at(pip_id); + if (pip->slot.id == pip_id) { + return pip; + } + } + return 0; +} + +_SOKOL_PRIVATE _sg_view_t* _sg_lookup_view(uint32_t view_id) { + if (SG_INVALID_ID != view_id) { + _sg_view_t* view = _sg_view_at(view_id); + if (view->slot.id == view_id) { + return view; + } + } + return 0; +} + +// ██████ ███████ ███████ ███████ +// ██ ██ ██ ██ ██ +// ██████ █████ █████ ███████ +// ██ ██ ██ ██ ██ +// ██ ██ ███████ ██ ███████ +// +// >>refs +_SOKOL_PRIVATE _sg_sref_t _sg_sref(const _sg_slot_t* slot) { + _sg_sref_t sref; _sg_clear(&sref, sizeof(sref)); + if (slot) { + sref.id = slot->id; + sref.uninit_count = slot->uninit_count; + } + return sref; +} + +_SOKOL_PRIVATE bool _sg_sref_slot_eql(const _sg_sref_t* sref, const _sg_slot_t* slot) { + SOKOL_ASSERT(sref && slot); + return (sref->id == slot->id) && (sref->uninit_count == slot->uninit_count); +} + +_SOKOL_PRIVATE bool _sg_sref_sref_eql(const _sg_sref_t* sref0, const _sg_sref_t* sref1) { + SOKOL_ASSERT(sref0 && sref1); + return (sref0->id == sref1->id) && (sref0->uninit_count == sref1->uninit_count); +} + +_SOKOL_PRIVATE _sg_buffer_ref_t _sg_buffer_ref(_sg_buffer_t* buf_or_null) { + _sg_buffer_ref_t ref; _sg_clear(&ref, sizeof(ref)); + if (buf_or_null) { + _sg_buffer_t* buf = buf_or_null; + SOKOL_ASSERT(buf->slot.id != SG_INVALID_ID); + ref.ptr = buf; + ref.sref = _sg_sref(&buf->slot); + } + return ref; +} + +_SOKOL_PRIVATE _sg_image_ref_t _sg_image_ref(_sg_image_t* img_or_null) { + _sg_image_ref_t ref; _sg_clear(&ref, sizeof(ref)); + if (img_or_null) { + _sg_image_t* img = img_or_null; + SOKOL_ASSERT(img->slot.id != SG_INVALID_ID); + ref.ptr = img; + ref.sref = _sg_sref(&img->slot); + } + return ref; +} + +_SOKOL_PRIVATE _sg_sampler_ref_t _sg_sampler_ref(_sg_sampler_t* smp_or_null) { + _sg_sampler_ref_t ref; _sg_clear(&ref, sizeof(ref)); + if (smp_or_null) { + _sg_sampler_t* smp = smp_or_null; + SOKOL_ASSERT(smp->slot.id != SG_INVALID_ID); + ref.ptr = smp; + ref.sref = _sg_sref(&smp->slot); + } + return ref; +} + +_SOKOL_PRIVATE _sg_shader_ref_t _sg_shader_ref(_sg_shader_t* shd_or_null) { + _sg_shader_ref_t ref; _sg_clear(&ref, sizeof(ref)); + if (shd_or_null) { + _sg_shader_t* shd = shd_or_null; + SOKOL_ASSERT(shd->slot.id != SG_INVALID_ID); + ref.ptr = shd; + ref.sref = _sg_sref(&shd->slot); + } + return ref; +} + +_SOKOL_PRIVATE _sg_pipeline_ref_t _sg_pipeline_ref(_sg_pipeline_t* pip_or_null) { + _sg_pipeline_ref_t ref; _sg_clear(&ref, sizeof(ref)); + if (pip_or_null) { + _sg_pipeline_t* pip = pip_or_null; + SOKOL_ASSERT(pip->slot.id != SG_INVALID_ID); + ref.ptr = pip; + ref.sref = _sg_sref(&pip->slot); + } + return ref; +} + +_SOKOL_PRIVATE _sg_view_ref_t _sg_view_ref(_sg_view_t* view_or_null) { + _sg_view_ref_t ref; _sg_clear(&ref, sizeof(ref)); + if (view_or_null) { + _sg_view_t* view = view_or_null; + SOKOL_ASSERT(view->slot.id != SG_INVALID_ID); + ref.ptr = view; + ref.sref = _sg_sref(&view->slot); + } + return ref; +} + +#define _SG_IMPL_RES_EQL(NAME,REF,RES) _SOKOL_PRIVATE bool NAME(const REF* ref, const RES* res) { SOKOL_ASSERT(ref && res); return _sg_sref_slot_eql(&ref->sref, &res->slot); } +_SG_IMPL_RES_EQL(_sg_buffer_ref_eql, _sg_buffer_ref_t, _sg_buffer_t) +_SG_IMPL_RES_EQL(_sg_image_ref_eql, _sg_image_ref_t, _sg_image_t) +_SG_IMPL_RES_EQL(_sg_sampler_ref_eql, _sg_sampler_ref_t, _sg_sampler_t) +_SG_IMPL_RES_EQL(_sg_shader_ref_eql, _sg_shader_ref_t, _sg_shader_t) +_SG_IMPL_RES_EQL(_sg_pipeline_ref_eql, _sg_pipeline_ref_t, _sg_pipeline_t) +_SG_IMPL_RES_EQL(_sg_view_ref_eql, _sg_view_ref_t, _sg_view_t) + +#define _SG_IMPL_RES_NULL(NAME,REF) _SOKOL_PRIVATE bool NAME(const REF* ref) { SOKOL_ASSERT(ref); return SG_INVALID_ID == ref->sref.id; } +_SG_IMPL_RES_NULL(_sg_buffer_ref_null, _sg_buffer_ref_t) +_SG_IMPL_RES_NULL(_sg_image_ref_null, _sg_image_ref_t) +_SG_IMPL_RES_NULL(_sg_sampler_ref_null, _sg_sampler_ref_t) +_SG_IMPL_RES_NULL(_sg_shader_ref_null, _sg_shader_ref_t) +_SG_IMPL_RES_NULL(_sg_pipeline_ref_null, _sg_pipeline_ref_t) +_SG_IMPL_RES_NULL(_sg_view_ref_null, _sg_view_ref_t) + +#define _SG_IMPL_RES_ALIVE(NAME,REF) _SOKOL_PRIVATE bool NAME(const REF* ref) { SOKOL_ASSERT(ref); return ref->ptr && _sg_sref_slot_eql(&ref->sref, &ref->ptr->slot); } +_SG_IMPL_RES_ALIVE(_sg_buffer_ref_alive, _sg_buffer_ref_t) +_SG_IMPL_RES_ALIVE(_sg_image_ref_alive, _sg_image_ref_t) +_SG_IMPL_RES_ALIVE(_sg_sampler_ref_alive, _sg_sampler_ref_t) +_SG_IMPL_RES_ALIVE(_sg_shader_ref_alive, _sg_shader_ref_t) +_SG_IMPL_RES_ALIVE(_sg_pipeline_ref_alive, _sg_pipeline_ref_t) +_SG_IMPL_RES_ALIVE(_sg_view_ref_alive, _sg_view_ref_t) + +#define _SG_IMPL_RES_VALID(NAME,REF) _SOKOL_PRIVATE bool NAME(const REF* ref) { SOKOL_ASSERT(ref); return ref->ptr && _sg_sref_slot_eql(&ref->sref, &ref->ptr->slot) && (ref->ptr->slot.state == SG_RESOURCESTATE_VALID); } +_SG_IMPL_RES_VALID(_sg_buffer_ref_valid, _sg_buffer_ref_t) +_SG_IMPL_RES_VALID(_sg_image_ref_valid, _sg_image_ref_t) +_SG_IMPL_RES_VALID(_sg_sampler_ref_valid, _sg_sampler_ref_t) +_SG_IMPL_RES_VALID(_sg_shader_ref_valid, _sg_shader_ref_t) +_SG_IMPL_RES_VALID(_sg_pipeline_ref_valid, _sg_pipeline_ref_t) +_SG_IMPL_RES_VALID(_sg_view_ref_valid, _sg_view_ref_t) + +#define _SG_IMPL_RES_PTR(NAME,REF,RES) _SOKOL_PRIVATE RES* NAME(const REF* ref) { SOKOL_ASSERT(ref && ref->ptr && _sg_sref_slot_eql(&ref->sref, &ref->ptr->slot)); return ref->ptr; } +_SG_IMPL_RES_PTR(_sg_buffer_ref_ptr, _sg_buffer_ref_t, _sg_buffer_t) +_SG_IMPL_RES_PTR(_sg_image_ref_ptr, _sg_image_ref_t, _sg_image_t) +_SG_IMPL_RES_PTR(_sg_sampler_ref_ptr, _sg_sampler_ref_t, _sg_sampler_t) +_SG_IMPL_RES_PTR(_sg_shader_ref_ptr, _sg_shader_ref_t, _sg_shader_t) +_SG_IMPL_RES_PTR(_sg_pipeline_ref_ptr, _sg_pipeline_ref_t, _sg_pipeline_t) +_SG_IMPL_RES_PTR(_sg_view_ref_ptr, _sg_view_ref_t, _sg_view_t) + +#define _SG_IMPL_RES_PTR_OR_NULL(NAME,REF,RES) _SOKOL_PRIVATE RES* NAME(const REF* ref) { SOKOL_ASSERT(ref); if ((SG_INVALID_ID != ref->sref.id) && _sg_sref_slot_eql(&ref->sref, &ref->ptr->slot)) { return ref->ptr; } else { return 0; } } +_SG_IMPL_RES_PTR_OR_NULL(_sg_buffer_ref_ptr_or_null, _sg_buffer_ref_t, _sg_buffer_t) +_SG_IMPL_RES_PTR_OR_NULL(_sg_image_ref_ptr_or_null, _sg_image_ref_t, _sg_image_t) +_SG_IMPL_RES_PTR_OR_NULL(_sg_sampler_ref_ptr_or_null, _sg_sampler_ref_t, _sg_sampler_t) +_SG_IMPL_RES_PTR_OR_NULL(_sg_shader_ref_ptr_or_null, _sg_shader_ref_t, _sg_shader_t) +_SG_IMPL_RES_PTR_OR_NULL(_sg_pipeline_ref_ptr_or_null, _sg_pipeline_ref_t, _sg_pipeline_t) +_SG_IMPL_RES_PTR_OR_NULL(_sg_view_ref_ptr_or_null, _sg_view_ref_t, _sg_view_t) + +// ██ ██ ███████ ██ ██████ ███████ ██████ ███████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ███████ █████ ██ ██████ █████ ██████ ███████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██ ██ ███████ ███████ ██ ███████ ██ ██ ███████ +// +// >>helpers + +// helper macros +#define _sg_def(val, def) (((val) == 0) ? (def) : (val)) +#define _sg_def_flt(val, def) (((val) == 0.0f) ? (def) : (val)) +#define _sg_min(a,b) (((a)<(b))?(a):(b)) +#define _sg_max(a,b) (((a)>(b))?(a):(b)) +#define _sg_clamp(v,v0,v1) (((v)<(v0))?(v0):(((v)>(v1))?(v1):(v))) +#define _sg_fequal(val,cmp,delta) ((((val)-(cmp))> -(delta))&&(((val)-(cmp))<(delta))) +#define _sg_ispow2(val) ((val&(val-1))==0) +#define _sg_stats_add(key,val) {if(_sg.stats_enabled){ _sg.stats.key+=val;}} + +_SOKOL_PRIVATE void _sg_update_resource_stats(sg_resource_stats* stats, const _sg_pool_t* pool) { + SOKOL_ASSERT(stats && pool); + stats->total_alive = (uint32_t) ((pool->size - 1) - pool->queue_top); + stats->total_free = (uint32_t) pool->queue_top; +} + +_SOKOL_PRIVATE void _sg_update_frame_stats(void) { + _sg.stats.frame_index = _sg.frame_index; + _sg_update_resource_stats(&_sg.stats.buffers, &_sg.pools.buffer_pool); + _sg_update_resource_stats(&_sg.stats.images, &_sg.pools.image_pool); + _sg_update_resource_stats(&_sg.stats.views, &_sg.pools.view_pool); + _sg_update_resource_stats(&_sg.stats.samplers, &_sg.pools.sampler_pool); + _sg_update_resource_stats(&_sg.stats.shaders, &_sg.pools.shader_pool); + _sg_update_resource_stats(&_sg.stats.pipelines, &_sg.pools.pipeline_pool); + _sg.prev_stats = _sg.stats; + _sg_clear(&_sg.stats, sizeof(_sg.stats)); +} + +_SOKOL_PRIVATE uint32_t _sg_align_u32(uint32_t val, uint32_t align) { + SOKOL_ASSERT((align > 0) && ((align & (align - 1)) == 0)); + return (val + (align - 1)) & ~(align - 1); +} + +_SOKOL_PRIVATE _sg_recti_t _sg_clipi(int x, int y, int w, int h, int clip_width, int clip_height) { + x = _sg_min(_sg_max(0, x), clip_width-1); + y = _sg_min(_sg_max(0, y), clip_height-1); + if ((x + w) > clip_width) { + w = clip_width - x; + } + if ((y + h) > clip_height) { + h = clip_height - y; + } + w = _sg_max(w, 1); + h = _sg_max(h, 1); + const _sg_recti_t res = { x, y, w, h }; + return res; +} + +// return size of a mipmap level +_SOKOL_PRIVATE int _sg_miplevel_dim(int base_dim, int mip_level) { + return _sg_max(base_dim >> mip_level, 1); +} + +_SOKOL_PRIVATE bool _sg_image_view_alive(const _sg_view_t* view) { + return view && _sg_image_ref_alive(&view->cmn.img.ref); +} + +_SOKOL_PRIVATE _sg_dimi_t _sg_image_view_dim(const _sg_view_t* view) { + SOKOL_ASSERT(view); + const _sg_image_t* img = _sg_image_ref_ptr(&view->cmn.img.ref); + SOKOL_ASSERT((img->cmn.width > 0) && (img->cmn.height > 0)); + _sg_dimi_t res; _sg_clear(&res, sizeof(res)); + res.width = _sg_miplevel_dim(img->cmn.width, view->cmn.img.mip_level); + res.height = _sg_miplevel_dim(img->cmn.height, view->cmn.img.mip_level); + return res; +} + +_SOKOL_PRIVATE bool _sg_attachments_empty(const sg_attachments* atts) { + SOKOL_ASSERT(atts); + for (size_t i = 0; i < SG_MAX_COLOR_ATTACHMENTS; i++) { + if (atts->colors[i].id != SG_INVALID_ID) { + return false; + } + if (atts->resolves[i].id != SG_INVALID_ID) { + return false; + } + } + if (atts->depth_stencil.id != SG_INVALID_ID) { + return false; + } + return true; +} + +_SOKOL_PRIVATE _sg_attachments_ptrs_t _sg_attachments_ptrs(const sg_attachments* atts) { + SOKOL_ASSERT(atts); + _sg_attachments_ptrs_t res; + _sg_clear(&res, sizeof(res)); + res.empty = true; + for (int i = 0; i < SG_MAX_COLOR_ATTACHMENTS; i++) { + if (atts->colors[i].id != SG_INVALID_ID) { + res.empty = false; + res.num_color_views += 1; + res.color_views[i] = _sg_lookup_view(atts->colors[i].id); + } + if (atts->resolves[i].id != SG_INVALID_ID) { + SOKOL_ASSERT(atts->colors[i].id != SG_INVALID_ID); + res.empty = false; + res.resolve_views[i] = _sg_lookup_view(atts->resolves[i].id); + } + } + if (atts->depth_stencil.id != SG_INVALID_ID) { + res.empty = false; + res.ds_view = _sg_lookup_view(atts->depth_stencil.id); + } + return res; +} + +_SOKOL_PRIVATE _sg_dimi_t _sg_attachments_dim(const _sg_attachments_ptrs_t* atts_ptrs) { + if (atts_ptrs->ds_view) { + return _sg_image_view_dim(atts_ptrs->ds_view); + } else { + SOKOL_ASSERT(atts_ptrs->color_views[0]); + return _sg_image_view_dim(atts_ptrs->color_views[0]); + } +} + +_SOKOL_PRIVATE bool _sg_attachments_alive(const _sg_attachments_ptrs_t* atts_ptrs) { + for (int i = 0; i < atts_ptrs->num_color_views; i++) { + if (!_sg_image_view_alive(atts_ptrs->color_views[i])) { + return false; + } + if (atts_ptrs->resolve_views[i] && !_sg_image_view_alive(atts_ptrs->resolve_views[i])) { + return false; + } + } + if (atts_ptrs->ds_view && !_sg_image_view_alive(atts_ptrs->ds_view)) { + return false; + } + return true; +} + +_SOKOL_PRIVATE void _sg_buffer_common_init(_sg_buffer_common_t* cmn, const sg_buffer_desc* desc) { + cmn->size = (int)desc->size; + cmn->append_pos = 0; + cmn->append_overflow = false; + cmn->update_frame_index = 0; + cmn->append_frame_index = 0; + cmn->num_slots = desc->usage.immutable ? 1 : SG_NUM_INFLIGHT_FRAMES; + cmn->active_slot = 0; + cmn->usage = desc->usage; +} + +_SOKOL_PRIVATE void _sg_image_common_init(_sg_image_common_t* cmn, const sg_image_desc* desc) { + cmn->upd_frame_index = 0; + cmn->num_slots = desc->usage.immutable ? 1 : SG_NUM_INFLIGHT_FRAMES; + cmn->active_slot = 0; + cmn->type = desc->type; + cmn->width = desc->width; + cmn->height = desc->height; + cmn->num_slices = desc->num_slices; + cmn->num_mipmaps = desc->num_mipmaps; + cmn->usage = desc->usage; + cmn->pixel_format = desc->pixel_format; + cmn->sample_count = desc->sample_count; +} + +_SOKOL_PRIVATE void _sg_sampler_common_init(_sg_sampler_common_t* cmn, const sg_sampler_desc* desc) { + cmn->min_filter = desc->min_filter; + cmn->mag_filter = desc->mag_filter; + cmn->mipmap_filter = desc->mipmap_filter; + cmn->wrap_u = desc->wrap_u; + cmn->wrap_v = desc->wrap_v; + cmn->wrap_w = desc->wrap_w; + cmn->min_lod = desc->min_lod; + cmn->max_lod = desc->max_lod; + cmn->border_color = desc->border_color; + cmn->compare = desc->compare; + cmn->max_anisotropy = desc->max_anisotropy; +} + +_SOKOL_PRIVATE void _sg_shader_common_init(_sg_shader_common_t* cmn, const sg_shader_desc* desc) { + cmn->is_compute = desc->compute_func.source || desc->compute_func.bytecode.ptr; + for (size_t i = 0; i < SG_MAX_VERTEX_ATTRIBUTES; i++) { + cmn->attrs[i].base_type = desc->attrs[i].base_type; + } + for (size_t i = 0; i < SG_MAX_UNIFORMBLOCK_BINDSLOTS; i++) { + const sg_shader_uniform_block* src = &desc->uniform_blocks[i]; + _sg_shader_uniform_block_t* dst = &cmn->uniform_blocks[i]; + if (src->stage != SG_SHADERSTAGE_NONE) { + cmn->required_bindings_and_uniforms |= (1 << i); + dst->stage = src->stage; + dst->size = src->size; + } + } + const uint32_t required_bindings_flag = (1 << SG_MAX_UNIFORMBLOCK_BINDSLOTS); + for (size_t i = 0; i < SG_MAX_VIEW_BINDSLOTS; i++) { + _sg_shader_view_t* dst = &cmn->views[i]; + if (desc->views[i].texture.stage != SG_SHADERSTAGE_NONE) { + const sg_shader_texture_view* src = &desc->views[i].texture; + dst->stage = src->stage; + dst->view_type = SG_VIEWTYPE_TEXTURE; + dst->image_type = src->image_type; + dst->sample_type = src->sample_type; + dst->multisampled = src->multisampled; + } else if (desc->views[i].storage_buffer.stage != SG_SHADERSTAGE_NONE) { + const sg_shader_storage_buffer_view* src = &desc->views[i].storage_buffer; + cmn->required_bindings_and_uniforms |= required_bindings_flag; + dst->stage = src->stage; + dst->view_type = SG_VIEWTYPE_STORAGEBUFFER; + dst->sbuf_readonly = src->readonly; + } else if (desc->views[i].storage_image.stage != SG_SHADERSTAGE_NONE) { + const sg_shader_storage_image_view* src = &desc->views[i].storage_image; + cmn->required_bindings_and_uniforms |= required_bindings_flag; + dst->stage = src->stage; + dst->view_type = SG_VIEWTYPE_STORAGEIMAGE; + dst->image_type = src->image_type; + dst->access_format = src->access_format; + dst->simg_writeonly = src->writeonly; + } + } + for (size_t i = 0; i < SG_MAX_SAMPLER_BINDSLOTS; i++) { + const sg_shader_sampler* src = &desc->samplers[i]; + _sg_shader_sampler_t* dst = &cmn->samplers[i]; + if (src->stage != SG_SHADERSTAGE_NONE) { + cmn->required_bindings_and_uniforms |= required_bindings_flag; + dst->stage = src->stage; + dst->sampler_type = src->sampler_type; + } + } + for (size_t i = 0; i < SG_MAX_TEXTURE_SAMPLER_PAIRS; i++) { + const sg_shader_texture_sampler_pair* src = &desc->texture_sampler_pairs[i]; + _sg_shader_texture_sampler_t* dst = &cmn->texture_samplers[i]; + if (src->stage != SG_SHADERSTAGE_NONE) { + dst->stage = src->stage; + SOKOL_ASSERT((src->view_slot >= 0) && (src->view_slot < SG_MAX_VIEW_BINDSLOTS)); + SOKOL_ASSERT(cmn->views[src->view_slot].view_type == SG_VIEWTYPE_TEXTURE); + SOKOL_ASSERT(cmn->views[src->view_slot].stage == src->stage); + dst->view_slot = src->view_slot; + SOKOL_ASSERT((src->sampler_slot >= 0) && (src->sampler_slot < SG_MAX_SAMPLER_BINDSLOTS)); + SOKOL_ASSERT(desc->samplers[src->sampler_slot].stage == src->stage); + dst->sampler_slot = src->sampler_slot; + } + } +} + +_SOKOL_PRIVATE void _sg_pipeline_common_init(_sg_pipeline_common_t* cmn, const sg_pipeline_desc* desc, _sg_shader_t* shd) { + SOKOL_ASSERT((desc->color_count >= 0) && (desc->color_count <= SG_MAX_COLOR_ATTACHMENTS)); + + // FIXME: most of this isn't needed for compute pipelines + + const uint32_t required_bindings_flag = (1 << SG_MAX_UNIFORMBLOCK_BINDSLOTS); + for (size_t attr_idx = 0; attr_idx < SG_MAX_VERTEX_ATTRIBUTES; attr_idx++) { + const sg_vertex_attr_state* attr_state = &desc->layout.attrs[attr_idx]; + if (attr_state->format != SG_VERTEXFORMAT_INVALID) { + SOKOL_ASSERT(attr_state->buffer_index < SG_MAX_VERTEXBUFFER_BINDSLOTS); + cmn->vertex_buffer_layout_active[attr_state->buffer_index] = true; + cmn->required_bindings_and_uniforms |= required_bindings_flag; + } + } + cmn->use_instanced_draw = false; + for (size_t vbuf_idx = 0; vbuf_idx < SG_MAX_VERTEXBUFFER_BINDSLOTS; vbuf_idx++) { + const sg_vertex_buffer_layout_state* vbuf_state = &desc->layout.buffers[vbuf_idx]; + if (vbuf_state->step_func == SG_VERTEXSTEP_PER_INSTANCE) { + cmn->use_instanced_draw = true; + } + } + cmn->is_compute = desc->compute; + cmn->shader = _sg_shader_ref(shd); + cmn->layout = desc->layout; + cmn->depth = desc->depth; + cmn->stencil = desc->stencil; + cmn->color_count = desc->color_count; + for (int i = 0; i < desc->color_count; i++) { + cmn->colors[i] = desc->colors[i]; + } + cmn->primitive_type = desc->primitive_type; + cmn->index_type = desc->index_type; + if (cmn->index_type != SG_INDEXTYPE_NONE) { + cmn->required_bindings_and_uniforms |= required_bindings_flag; + } + cmn->cull_mode = desc->cull_mode; + cmn->face_winding = desc->face_winding; + cmn->sample_count = desc->sample_count; + cmn->blend_color = desc->blend_color; + cmn->alpha_to_coverage_enabled = desc->alpha_to_coverage_enabled; +} + +_SOKOL_PRIVATE void _sg_buffer_view_common_init(_sg_buffer_view_common_t* cmn, const sg_buffer_view_desc* desc, _sg_buffer_t* buf) { + SOKOL_ASSERT(SG_RESOURCESTATE_VALID == buf->slot.state); + cmn->ref = _sg_buffer_ref(buf); + cmn->offset = desc->offset; +} + +_SOKOL_PRIVATE void _sg_texture_view_common_init(_sg_image_view_common_t* cmn, const sg_texture_view_desc* desc, _sg_image_t* img) { + SOKOL_ASSERT(SG_RESOURCESTATE_VALID == img->slot.state); + cmn->ref = _sg_image_ref(img); + cmn->mip_level = desc->mip_levels.base; + cmn->mip_level_count = _sg_def(desc->mip_levels.count, img->cmn.num_mipmaps - cmn->mip_level); + cmn->slice = desc->slices.base; + switch (img->cmn.type) { + case SG_IMAGETYPE_2D: + cmn->slice_count = 1; + break; + case SG_IMAGETYPE_CUBE: + cmn->slice_count = 6; + break; + case SG_IMAGETYPE_3D: + cmn->slice_count = 1; + break; + case SG_IMAGETYPE_ARRAY: + cmn->slice_count = _sg_def(desc->slices.count, img->cmn.num_slices - cmn->slice); + break; + default: + SOKOL_UNREACHABLE; + } +} + +_SOKOL_PRIVATE void _sg_image_view_common_init(_sg_image_view_common_t* cmn, const sg_image_view_desc* desc, _sg_image_t* img) { + SOKOL_ASSERT(SG_RESOURCESTATE_VALID == img->slot.state); + cmn->ref = _sg_image_ref(img); + cmn->mip_level = desc->mip_level; + cmn->mip_level_count = 1; + cmn->slice = desc->slice; + cmn->slice_count = 1; +} + +_SOKOL_PRIVATE void _sg_view_common_init(_sg_view_common_t* cmn, const sg_view_desc* desc, _sg_buffer_t* buf, _sg_image_t* img) { + if (desc->texture.image.id != SG_INVALID_ID) { + SOKOL_ASSERT(img); + cmn->type = SG_VIEWTYPE_TEXTURE; + _sg_texture_view_common_init(&cmn->img, &desc->texture, img); + } else if (desc->storage_buffer.buffer.id != SG_INVALID_ID) { + SOKOL_ASSERT(buf); + cmn->type = SG_VIEWTYPE_STORAGEBUFFER; + _sg_buffer_view_common_init(&cmn->buf, &desc->storage_buffer, buf); + } else if (desc->storage_image.image.id != SG_INVALID_ID) { + SOKOL_ASSERT(img); + cmn->type = SG_VIEWTYPE_STORAGEIMAGE; + _sg_image_view_common_init(&cmn->img, &desc->storage_image, img); + } else if (desc->color_attachment.image.id != SG_INVALID_ID) { + SOKOL_ASSERT(img); + cmn->type = SG_VIEWTYPE_COLORATTACHMENT; + _sg_image_view_common_init(&cmn->img, &desc->color_attachment, img); + } else if (desc->resolve_attachment.image.id != SG_INVALID_ID) { + SOKOL_ASSERT(img); + cmn->type = SG_VIEWTYPE_RESOLVEATTACHMENT; + _sg_image_view_common_init(&cmn->img, &desc->resolve_attachment, img); + } else if (desc->depth_stencil_attachment.image.id != SG_INVALID_ID) { + SOKOL_ASSERT(img); + cmn->type = SG_VIEWTYPE_DEPTHSTENCILATTACHMENT; + _sg_image_view_common_init(&cmn->img, &desc->depth_stencil_attachment, img); + } else { + SOKOL_UNREACHABLE; + } +} + +_SOKOL_PRIVATE int _sg_vertexformat_bytesize(sg_vertex_format fmt) { + switch (fmt) { + case SG_VERTEXFORMAT_FLOAT: return 4; + case SG_VERTEXFORMAT_FLOAT2: return 8; + case SG_VERTEXFORMAT_FLOAT3: return 12; + case SG_VERTEXFORMAT_FLOAT4: return 16; + case SG_VERTEXFORMAT_INT: return 4; + case SG_VERTEXFORMAT_INT2: return 8; + case SG_VERTEXFORMAT_INT3: return 12; + case SG_VERTEXFORMAT_INT4: return 16; + case SG_VERTEXFORMAT_UINT: return 4; + case SG_VERTEXFORMAT_UINT2: return 8; + case SG_VERTEXFORMAT_UINT3: return 12; + case SG_VERTEXFORMAT_UINT4: return 16; + case SG_VERTEXFORMAT_BYTE4: return 4; + case SG_VERTEXFORMAT_BYTE4N: return 4; + case SG_VERTEXFORMAT_UBYTE4: return 4; + case SG_VERTEXFORMAT_UBYTE4N: return 4; + case SG_VERTEXFORMAT_SHORT2: return 4; + case SG_VERTEXFORMAT_SHORT2N: return 4; + case SG_VERTEXFORMAT_USHORT2: return 4; + case SG_VERTEXFORMAT_USHORT2N: return 4; + case SG_VERTEXFORMAT_SHORT4: return 8; + case SG_VERTEXFORMAT_SHORT4N: return 8; + case SG_VERTEXFORMAT_USHORT4: return 8; + case SG_VERTEXFORMAT_USHORT4N: return 8; + case SG_VERTEXFORMAT_UINT10_N2: return 4; + case SG_VERTEXFORMAT_HALF2: return 4; + case SG_VERTEXFORMAT_HALF4: return 8; + case SG_VERTEXFORMAT_INVALID: return 0; + default: + SOKOL_UNREACHABLE; + return -1; + } +} + +_SOKOL_PRIVATE const char* _sg_vertexformat_to_string(sg_vertex_format fmt) { + switch (fmt) { + case SG_VERTEXFORMAT_FLOAT: return "FLOAT"; + case SG_VERTEXFORMAT_FLOAT2: return "FLOAT2"; + case SG_VERTEXFORMAT_FLOAT3: return "FLOAT3"; + case SG_VERTEXFORMAT_FLOAT4: return "FLOAT4"; + case SG_VERTEXFORMAT_INT: return "INT"; + case SG_VERTEXFORMAT_INT2: return "INT2"; + case SG_VERTEXFORMAT_INT3: return "INT3"; + case SG_VERTEXFORMAT_INT4: return "INT4"; + case SG_VERTEXFORMAT_UINT: return "UINT"; + case SG_VERTEXFORMAT_UINT2: return "UINT2"; + case SG_VERTEXFORMAT_UINT3: return "UINT3"; + case SG_VERTEXFORMAT_UINT4: return "UINT4"; + case SG_VERTEXFORMAT_BYTE4: return "BYTE4"; + case SG_VERTEXFORMAT_BYTE4N: return "BYTE4N"; + case SG_VERTEXFORMAT_UBYTE4: return "UBYTE4"; + case SG_VERTEXFORMAT_UBYTE4N: return "UBYTE4N"; + case SG_VERTEXFORMAT_SHORT2: return "SHORT2"; + case SG_VERTEXFORMAT_SHORT2N: return "SHORT2N"; + case SG_VERTEXFORMAT_USHORT2: return "USHORT2"; + case SG_VERTEXFORMAT_USHORT2N: return "USHORT2N"; + case SG_VERTEXFORMAT_SHORT4: return "SHORT4"; + case SG_VERTEXFORMAT_SHORT4N: return "SHORT4N"; + case SG_VERTEXFORMAT_USHORT4: return "USHORT4"; + case SG_VERTEXFORMAT_USHORT4N: return "USHORT4N"; + case SG_VERTEXFORMAT_UINT10_N2: return "UINT10_N2"; + case SG_VERTEXFORMAT_HALF2: return "HALF2"; + case SG_VERTEXFORMAT_HALF4: return "HALF4"; + default: + SOKOL_UNREACHABLE; + return "INVALID"; + } +} + +_SOKOL_PRIVATE const char* _sg_shaderattrbasetype_to_string(sg_shader_attr_base_type b) { + switch (b) { + case SG_SHADERATTRBASETYPE_UNDEFINED: return "UNDEFINED"; + case SG_SHADERATTRBASETYPE_FLOAT: return "FLOAT"; + case SG_SHADERATTRBASETYPE_SINT: return "SINT"; + case SG_SHADERATTRBASETYPE_UINT: return "UINT"; + default: + SOKOL_UNREACHABLE; + return "INVALID"; + } +} + +_SOKOL_PRIVATE sg_shader_attr_base_type _sg_vertexformat_basetype(sg_vertex_format fmt) { + switch (fmt) { + case SG_VERTEXFORMAT_FLOAT: + case SG_VERTEXFORMAT_FLOAT2: + case SG_VERTEXFORMAT_FLOAT3: + case SG_VERTEXFORMAT_FLOAT4: + case SG_VERTEXFORMAT_HALF2: + case SG_VERTEXFORMAT_HALF4: + case SG_VERTEXFORMAT_BYTE4N: + case SG_VERTEXFORMAT_UBYTE4N: + case SG_VERTEXFORMAT_SHORT2N: + case SG_VERTEXFORMAT_USHORT2N: + case SG_VERTEXFORMAT_SHORT4N: + case SG_VERTEXFORMAT_USHORT4N: + case SG_VERTEXFORMAT_UINT10_N2: + return SG_SHADERATTRBASETYPE_FLOAT; + case SG_VERTEXFORMAT_INT: + case SG_VERTEXFORMAT_INT2: + case SG_VERTEXFORMAT_INT3: + case SG_VERTEXFORMAT_INT4: + case SG_VERTEXFORMAT_BYTE4: + case SG_VERTEXFORMAT_SHORT2: + case SG_VERTEXFORMAT_SHORT4: + return SG_SHADERATTRBASETYPE_SINT; + case SG_VERTEXFORMAT_UINT: + case SG_VERTEXFORMAT_UINT2: + case SG_VERTEXFORMAT_UINT3: + case SG_VERTEXFORMAT_UINT4: + case SG_VERTEXFORMAT_UBYTE4: + case SG_VERTEXFORMAT_USHORT2: + case SG_VERTEXFORMAT_USHORT4: + return SG_SHADERATTRBASETYPE_UINT; + default: + SOKOL_UNREACHABLE; + return SG_SHADERATTRBASETYPE_UNDEFINED; + } +} + +_SOKOL_PRIVATE uint32_t _sg_uniform_alignment(sg_uniform_type type, int array_count, sg_uniform_layout ub_layout) { + if (ub_layout == SG_UNIFORMLAYOUT_NATIVE) { + return 1; + } else { + SOKOL_ASSERT(array_count > 0); + if (array_count == 1) { + switch (type) { + case SG_UNIFORMTYPE_FLOAT: + case SG_UNIFORMTYPE_INT: + return 4; + case SG_UNIFORMTYPE_FLOAT2: + case SG_UNIFORMTYPE_INT2: + return 8; + case SG_UNIFORMTYPE_FLOAT3: + case SG_UNIFORMTYPE_FLOAT4: + case SG_UNIFORMTYPE_INT3: + case SG_UNIFORMTYPE_INT4: + return 16; + case SG_UNIFORMTYPE_MAT4: + return 16; + default: + SOKOL_UNREACHABLE; + return 1; + } + } else { + return 16; + } + } +} + +_SOKOL_PRIVATE uint32_t _sg_uniform_size(sg_uniform_type type, int array_count, sg_uniform_layout ub_layout) { + SOKOL_ASSERT(array_count > 0); + if (array_count == 1) { + switch (type) { + case SG_UNIFORMTYPE_FLOAT: + case SG_UNIFORMTYPE_INT: + return 4; + case SG_UNIFORMTYPE_FLOAT2: + case SG_UNIFORMTYPE_INT2: + return 8; + case SG_UNIFORMTYPE_FLOAT3: + case SG_UNIFORMTYPE_INT3: + return 12; + case SG_UNIFORMTYPE_FLOAT4: + case SG_UNIFORMTYPE_INT4: + return 16; + case SG_UNIFORMTYPE_MAT4: + return 64; + default: + SOKOL_UNREACHABLE; + return 0; + } + } else { + if (ub_layout == SG_UNIFORMLAYOUT_NATIVE) { + switch (type) { + case SG_UNIFORMTYPE_FLOAT: + case SG_UNIFORMTYPE_INT: + return 4 * (uint32_t)array_count; + case SG_UNIFORMTYPE_FLOAT2: + case SG_UNIFORMTYPE_INT2: + return 8 * (uint32_t)array_count; + case SG_UNIFORMTYPE_FLOAT3: + case SG_UNIFORMTYPE_INT3: + return 12 * (uint32_t)array_count; + case SG_UNIFORMTYPE_FLOAT4: + case SG_UNIFORMTYPE_INT4: + return 16 * (uint32_t)array_count; + case SG_UNIFORMTYPE_MAT4: + return 64 * (uint32_t)array_count; + default: + SOKOL_UNREACHABLE; + return 0; + } + } else { + switch (type) { + case SG_UNIFORMTYPE_FLOAT: + case SG_UNIFORMTYPE_FLOAT2: + case SG_UNIFORMTYPE_FLOAT3: + case SG_UNIFORMTYPE_FLOAT4: + case SG_UNIFORMTYPE_INT: + case SG_UNIFORMTYPE_INT2: + case SG_UNIFORMTYPE_INT3: + case SG_UNIFORMTYPE_INT4: + return 16 * (uint32_t)array_count; + case SG_UNIFORMTYPE_MAT4: + return 64 * (uint32_t)array_count; + default: + SOKOL_UNREACHABLE; + return 0; + } + } + } +} + +_SOKOL_PRIVATE bool _sg_is_compressed_pixel_format(sg_pixel_format fmt) { + switch (fmt) { + case SG_PIXELFORMAT_BC1_RGBA: + case SG_PIXELFORMAT_BC2_RGBA: + case SG_PIXELFORMAT_BC3_RGBA: + case SG_PIXELFORMAT_BC3_SRGBA: + case SG_PIXELFORMAT_BC4_R: + case SG_PIXELFORMAT_BC4_RSN: + case SG_PIXELFORMAT_BC5_RG: + case SG_PIXELFORMAT_BC5_RGSN: + case SG_PIXELFORMAT_BC6H_RGBF: + case SG_PIXELFORMAT_BC6H_RGBUF: + case SG_PIXELFORMAT_BC7_RGBA: + case SG_PIXELFORMAT_BC7_SRGBA: + case SG_PIXELFORMAT_ETC2_RGB8: + case SG_PIXELFORMAT_ETC2_SRGB8: + case SG_PIXELFORMAT_ETC2_RGB8A1: + case SG_PIXELFORMAT_ETC2_RGBA8: + case SG_PIXELFORMAT_ETC2_SRGB8A8: + case SG_PIXELFORMAT_EAC_R11: + case SG_PIXELFORMAT_EAC_R11SN: + case SG_PIXELFORMAT_EAC_RG11: + case SG_PIXELFORMAT_EAC_RG11SN: + case SG_PIXELFORMAT_ASTC_4x4_RGBA: + case SG_PIXELFORMAT_ASTC_4x4_SRGBA: + return true; + default: + return false; + } +} + +_SOKOL_PRIVATE bool _sg_is_valid_attachment_color_format(sg_pixel_format fmt) { + const int fmt_index = (int) fmt; + SOKOL_ASSERT((fmt_index >= 0) && (fmt_index < _SG_PIXELFORMAT_NUM)); + return _sg.formats[fmt_index].render && !_sg.formats[fmt_index].depth; +} + +_SOKOL_PRIVATE bool _sg_is_valid_attachment_depth_format(sg_pixel_format fmt) { + const int fmt_index = (int) fmt; + SOKOL_ASSERT((fmt_index >= 0) && (fmt_index < _SG_PIXELFORMAT_NUM)); + return _sg.formats[fmt_index].render && _sg.formats[fmt_index].depth; +} + +_SOKOL_PRIVATE bool _sg_is_valid_storage_image_format(sg_pixel_format fmt) { + const int fmt_index = (int) fmt; + SOKOL_ASSERT((fmt_index >= 0) && (fmt_index < _SG_PIXELFORMAT_NUM)); + return _sg.formats[fmt_index].read || _sg.formats[fmt_index].write; +} + +_SOKOL_PRIVATE bool _sg_is_depth_or_depth_stencil_format(sg_pixel_format fmt) { + return (SG_PIXELFORMAT_DEPTH == fmt) || (SG_PIXELFORMAT_DEPTH_STENCIL == fmt); +} + +_SOKOL_PRIVATE bool _sg_is_depth_stencil_format(sg_pixel_format fmt) { + return (SG_PIXELFORMAT_DEPTH_STENCIL == fmt); +} + +_SOKOL_PRIVATE int _sg_pixelformat_bytesize(sg_pixel_format fmt) { + switch (fmt) { + case SG_PIXELFORMAT_R8: + case SG_PIXELFORMAT_R8SN: + case SG_PIXELFORMAT_R8UI: + case SG_PIXELFORMAT_R8SI: + return 1; + case SG_PIXELFORMAT_R16: + case SG_PIXELFORMAT_R16SN: + case SG_PIXELFORMAT_R16UI: + case SG_PIXELFORMAT_R16SI: + case SG_PIXELFORMAT_R16F: + case SG_PIXELFORMAT_RG8: + case SG_PIXELFORMAT_RG8SN: + case SG_PIXELFORMAT_RG8UI: + case SG_PIXELFORMAT_RG8SI: + return 2; + case SG_PIXELFORMAT_R32UI: + case SG_PIXELFORMAT_R32SI: + case SG_PIXELFORMAT_R32F: + case SG_PIXELFORMAT_RG16: + case SG_PIXELFORMAT_RG16SN: + case SG_PIXELFORMAT_RG16UI: + case SG_PIXELFORMAT_RG16SI: + case SG_PIXELFORMAT_RG16F: + case SG_PIXELFORMAT_RGBA8: + case SG_PIXELFORMAT_SRGB8A8: + case SG_PIXELFORMAT_RGBA8SN: + case SG_PIXELFORMAT_RGBA8UI: + case SG_PIXELFORMAT_RGBA8SI: + case SG_PIXELFORMAT_BGRA8: + case SG_PIXELFORMAT_RGB10A2: + case SG_PIXELFORMAT_RG11B10F: + case SG_PIXELFORMAT_RGB9E5: + return 4; + case SG_PIXELFORMAT_RG32UI: + case SG_PIXELFORMAT_RG32SI: + case SG_PIXELFORMAT_RG32F: + case SG_PIXELFORMAT_RGBA16: + case SG_PIXELFORMAT_RGBA16SN: + case SG_PIXELFORMAT_RGBA16UI: + case SG_PIXELFORMAT_RGBA16SI: + case SG_PIXELFORMAT_RGBA16F: + return 8; + case SG_PIXELFORMAT_RGBA32UI: + case SG_PIXELFORMAT_RGBA32SI: + case SG_PIXELFORMAT_RGBA32F: + return 16; + case SG_PIXELFORMAT_DEPTH: + case SG_PIXELFORMAT_DEPTH_STENCIL: + return 4; + default: + SOKOL_UNREACHABLE; + return 0; + } +} + +_SOKOL_PRIVATE int _sg_roundup(int val, int round_to) { + return (val+(round_to-1)) & ~(round_to-1); +} + +_SOKOL_PRIVATE uint32_t _sg_roundup_u32(uint32_t val, uint32_t round_to) { + return (val+(round_to-1)) & ~(round_to-1); +} + +_SOKOL_PRIVATE uint64_t _sg_roundup_u64(uint64_t val, uint64_t round_to) { + return (val+(round_to-1)) & ~(round_to-1); +} + +_SOKOL_PRIVATE bool _sg_multiple_u64(uint64_t val, uint64_t of) { + return (val & (of-1)) == 0; +} + +/* return row pitch for an image + + see ComputePitch in https://github.com/microsoft/DirectXTex/blob/master/DirectXTex/DirectXTexUtil.cpp +*/ +_SOKOL_PRIVATE int _sg_row_pitch(sg_pixel_format fmt, int width, int row_align) { + int pitch; + switch (fmt) { + case SG_PIXELFORMAT_BC1_RGBA: + case SG_PIXELFORMAT_BC4_R: + case SG_PIXELFORMAT_BC4_RSN: + case SG_PIXELFORMAT_ETC2_RGB8: + case SG_PIXELFORMAT_ETC2_SRGB8: + case SG_PIXELFORMAT_ETC2_RGB8A1: + case SG_PIXELFORMAT_EAC_R11: + case SG_PIXELFORMAT_EAC_R11SN: + pitch = ((width + 3) / 4) * 8; + pitch = pitch < 8 ? 8 : pitch; + break; + case SG_PIXELFORMAT_BC2_RGBA: + case SG_PIXELFORMAT_BC3_RGBA: + case SG_PIXELFORMAT_BC3_SRGBA: + case SG_PIXELFORMAT_BC5_RG: + case SG_PIXELFORMAT_BC5_RGSN: + case SG_PIXELFORMAT_BC6H_RGBF: + case SG_PIXELFORMAT_BC6H_RGBUF: + case SG_PIXELFORMAT_BC7_RGBA: + case SG_PIXELFORMAT_BC7_SRGBA: + case SG_PIXELFORMAT_ETC2_RGBA8: + case SG_PIXELFORMAT_ETC2_SRGB8A8: + case SG_PIXELFORMAT_EAC_RG11: + case SG_PIXELFORMAT_EAC_RG11SN: + case SG_PIXELFORMAT_ASTC_4x4_RGBA: + case SG_PIXELFORMAT_ASTC_4x4_SRGBA: + pitch = ((width + 3) / 4) * 16; + pitch = pitch < 16 ? 16 : pitch; + break; + default: + pitch = width * _sg_pixelformat_bytesize(fmt); + break; + } + pitch = _sg_roundup(pitch, row_align); + return pitch; +} + +// compute the number of rows in a surface depending on pixel format +_SOKOL_PRIVATE int _sg_num_rows(sg_pixel_format fmt, int height) { + int num_rows; + switch (fmt) { + case SG_PIXELFORMAT_BC1_RGBA: + case SG_PIXELFORMAT_BC4_R: + case SG_PIXELFORMAT_BC4_RSN: + case SG_PIXELFORMAT_ETC2_RGB8: + case SG_PIXELFORMAT_ETC2_SRGB8: + case SG_PIXELFORMAT_ETC2_RGB8A1: + case SG_PIXELFORMAT_ETC2_RGBA8: + case SG_PIXELFORMAT_ETC2_SRGB8A8: + case SG_PIXELFORMAT_EAC_R11: + case SG_PIXELFORMAT_EAC_R11SN: + case SG_PIXELFORMAT_EAC_RG11: + case SG_PIXELFORMAT_EAC_RG11SN: + case SG_PIXELFORMAT_BC2_RGBA: + case SG_PIXELFORMAT_BC3_RGBA: + case SG_PIXELFORMAT_BC3_SRGBA: + case SG_PIXELFORMAT_BC5_RG: + case SG_PIXELFORMAT_BC5_RGSN: + case SG_PIXELFORMAT_BC6H_RGBF: + case SG_PIXELFORMAT_BC6H_RGBUF: + case SG_PIXELFORMAT_BC7_RGBA: + case SG_PIXELFORMAT_BC7_SRGBA: + case SG_PIXELFORMAT_ASTC_4x4_RGBA: + case SG_PIXELFORMAT_ASTC_4x4_SRGBA: + num_rows = ((height + 3) / 4); + break; + default: + num_rows = height; + break; + } + if (num_rows < 1) { + num_rows = 1; + } + return num_rows; +} + +/* return pitch of a 2D subimage / texture slice + see ComputePitch in https://github.com/microsoft/DirectXTex/blob/master/DirectXTex/DirectXTexUtil.cpp +*/ +_SOKOL_PRIVATE int _sg_surface_pitch(sg_pixel_format fmt, int width, int height, int row_align) { + int num_rows = _sg_num_rows(fmt, height); + return num_rows * _sg_row_pitch(fmt, width, row_align); +} + +// capability table pixel format helper functions +_SOKOL_PRIVATE void _sg_pixelformat_all(_sg_pixelformat_info_t* pfi) { + pfi->sample = true; + pfi->filter = true; + pfi->blend = true; + pfi->render = true; + pfi->msaa = true; +} + +_SOKOL_PRIVATE void _sg_pixelformat_s(_sg_pixelformat_info_t* pfi) { + pfi->sample = true; +} + +_SOKOL_PRIVATE void _sg_pixelformat_sf(_sg_pixelformat_info_t* pfi) { + pfi->sample = true; + pfi->filter = true; +} + +_SOKOL_PRIVATE void _sg_pixelformat_sr(_sg_pixelformat_info_t* pfi) { + pfi->sample = true; + pfi->render = true; +} + +_SOKOL_PRIVATE void _sg_pixelformat_sfr(_sg_pixelformat_info_t* pfi) { + pfi->sample = true; + pfi->filter = true; + pfi->render = true; +} + +_SOKOL_PRIVATE void _sg_pixelformat_srmd(_sg_pixelformat_info_t* pfi) { + pfi->sample = true; + pfi->render = true; + pfi->msaa = true; + pfi->depth = true; +} + +_SOKOL_PRIVATE void _sg_pixelformat_srm(_sg_pixelformat_info_t* pfi) { + pfi->sample = true; + pfi->render = true; + pfi->msaa = true; +} + +_SOKOL_PRIVATE void _sg_pixelformat_sfrm(_sg_pixelformat_info_t* pfi) { + pfi->sample = true; + pfi->filter = true; + pfi->render = true; + pfi->msaa = true; +} +_SOKOL_PRIVATE void _sg_pixelformat_sbrm(_sg_pixelformat_info_t* pfi) { + pfi->sample = true; + pfi->blend = true; + pfi->render = true; + pfi->msaa = true; +} + +_SOKOL_PRIVATE void _sg_pixelformat_sbr(_sg_pixelformat_info_t* pfi) { + pfi->sample = true; + pfi->blend = true; + pfi->render = true; +} + +_SOKOL_PRIVATE void _sg_pixelformat_sfbr(_sg_pixelformat_info_t* pfi) { + pfi->sample = true; + pfi->filter = true; + pfi->blend = true; + pfi->render = true; +} + +_SOKOL_PRIVATE void _sg_pixelformat_compute_all(_sg_pixelformat_info_t* pfi) { + pfi->read = true; + pfi->write = true; +} + +_SOKOL_PRIVATE void _sg_pixelformat_compute_writeonly(_sg_pixelformat_info_t* pfi) { + pfi->read = false; + pfi->write = true; +} + +_SOKOL_PRIVATE sg_pass_action _sg_pass_action_defaults(const sg_pass_action* action) { + SOKOL_ASSERT(action); + sg_pass_action res = *action; + for (int i = 0; i < SG_MAX_COLOR_ATTACHMENTS; i++) { + if (res.colors[i].load_action == _SG_LOADACTION_DEFAULT) { + res.colors[i].load_action = SG_LOADACTION_CLEAR; + res.colors[i].clear_value.r = SG_DEFAULT_CLEAR_RED; + res.colors[i].clear_value.g = SG_DEFAULT_CLEAR_GREEN; + res.colors[i].clear_value.b = SG_DEFAULT_CLEAR_BLUE; + res.colors[i].clear_value.a = SG_DEFAULT_CLEAR_ALPHA; + } + if (res.colors[i].store_action == _SG_STOREACTION_DEFAULT) { + res.colors[i].store_action = SG_STOREACTION_STORE; + } + } + if (res.depth.load_action == _SG_LOADACTION_DEFAULT) { + res.depth.load_action = SG_LOADACTION_CLEAR; + res.depth.clear_value = SG_DEFAULT_CLEAR_DEPTH; + } + if (res.depth.store_action == _SG_STOREACTION_DEFAULT) { + res.depth.store_action = SG_STOREACTION_DONTCARE; + } + if (res.stencil.load_action == _SG_LOADACTION_DEFAULT) { + res.stencil.load_action = SG_LOADACTION_CLEAR; + res.stencil.clear_value = SG_DEFAULT_CLEAR_STENCIL; + } + if (res.stencil.store_action == _SG_STOREACTION_DEFAULT) { + res.stencil.store_action = SG_STOREACTION_DONTCARE; + } + return res; +} + +// ██████ ██ ██ ███ ███ ███ ███ ██ ██ ██████ █████ ██████ ██ ██ ███████ ███ ██ ██████ +// ██ ██ ██ ██ ████ ████ ████ ████ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██ ██ ██ +// ██ ██ ██ ██ ██ ████ ██ ██ ████ ██ ████ ██████ ███████ ██ █████ █████ ██ ██ ██ ██ ██ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██████ ██████ ██ ██ ██ ██ ██ ██████ ██ ██ ██████ ██ ██ ███████ ██ ████ ██████ +// +// >>dummy backend +#if defined(SOKOL_DUMMY_BACKEND) + +_SOKOL_PRIVATE void _sg_dummy_setup_backend(const sg_desc* desc) { + SOKOL_ASSERT(desc); + _SOKOL_UNUSED(desc); + _sg.backend = SG_BACKEND_DUMMY; + for (int i = SG_PIXELFORMAT_R8; i < SG_PIXELFORMAT_BC1_RGBA; i++) { + _sg.formats[i].sample = true; + _sg.formats[i].filter = true; + _sg.formats[i].render = true; + _sg.formats[i].blend = true; + _sg.formats[i].msaa = true; + } + _sg.formats[SG_PIXELFORMAT_DEPTH].depth = true; + _sg.formats[SG_PIXELFORMAT_DEPTH_STENCIL].depth = true; + _sg.limits.max_image_size_2d = 1024; + _sg.limits.max_image_size_cube = 1024; + _sg.limits.max_image_size_3d = 1024; + _sg.limits.max_image_size_array = 1024; + _sg.limits.max_image_array_layers = 1024; + _sg.limits.max_vertex_attrs = 16; + _sg.limits.max_color_attachments = SG_MAX_PORTABLE_COLOR_ATTACHMENTS; + _sg.limits.max_texture_bindings_per_stage = SG_MAX_PORTABLE_TEXTURE_BINDINGS_PER_STAGE; + _sg.limits.max_storage_buffer_bindings_per_stage = SG_MAX_PORTABLE_STORAGEBUFFER_BINDINGS_PER_STAGE; + _sg.limits.max_storage_image_bindings_per_stage = SG_MAX_PORTABLE_STORAGEIMAGE_BINDINGS_PER_STAGE; +} + +_SOKOL_PRIVATE void _sg_dummy_discard_backend(void) { + // empty +} + +_SOKOL_PRIVATE void _sg_dummy_reset_state_cache(void) { + // empty +} + +_SOKOL_PRIVATE sg_resource_state _sg_dummy_create_buffer(_sg_buffer_t* buf, const sg_buffer_desc* desc) { + SOKOL_ASSERT(buf && desc); + _SOKOL_UNUSED(buf); + _SOKOL_UNUSED(desc); + return SG_RESOURCESTATE_VALID; +} + +_SOKOL_PRIVATE void _sg_dummy_discard_buffer(_sg_buffer_t* buf) { + SOKOL_ASSERT(buf); + _SOKOL_UNUSED(buf); +} + +_SOKOL_PRIVATE sg_resource_state _sg_dummy_create_image(_sg_image_t* img, const sg_image_desc* desc) { + SOKOL_ASSERT(img && desc); + _SOKOL_UNUSED(img); + _SOKOL_UNUSED(desc); + return SG_RESOURCESTATE_VALID; +} + +_SOKOL_PRIVATE void _sg_dummy_discard_image(_sg_image_t* img) { + SOKOL_ASSERT(img); + _SOKOL_UNUSED(img); +} + +_SOKOL_PRIVATE sg_resource_state _sg_dummy_create_sampler(_sg_sampler_t* smp, const sg_sampler_desc* desc) { + SOKOL_ASSERT(smp && desc); + _SOKOL_UNUSED(smp); + _SOKOL_UNUSED(desc); + return SG_RESOURCESTATE_VALID; +} + +_SOKOL_PRIVATE void _sg_dummy_discard_sampler(_sg_sampler_t* smp) { + SOKOL_ASSERT(smp); + _SOKOL_UNUSED(smp); +} + +_SOKOL_PRIVATE sg_resource_state _sg_dummy_create_shader(_sg_shader_t* shd, const sg_shader_desc* desc) { + SOKOL_ASSERT(shd && desc); + _SOKOL_UNUSED(shd); + _SOKOL_UNUSED(desc); + return SG_RESOURCESTATE_VALID; +} + +_SOKOL_PRIVATE void _sg_dummy_discard_shader(_sg_shader_t* shd) { + SOKOL_ASSERT(shd); + _SOKOL_UNUSED(shd); +} + +_SOKOL_PRIVATE sg_resource_state _sg_dummy_create_pipeline(_sg_pipeline_t* pip, const sg_pipeline_desc* desc) { + SOKOL_ASSERT(pip && desc); + _SOKOL_UNUSED(pip); + _SOKOL_UNUSED(desc); + return SG_RESOURCESTATE_VALID; +} + +_SOKOL_PRIVATE void _sg_dummy_discard_pipeline(_sg_pipeline_t* pip) { + SOKOL_ASSERT(pip); + _SOKOL_UNUSED(pip); +} + +_SOKOL_PRIVATE sg_resource_state _sg_dummy_create_view(_sg_view_t* view, const sg_view_desc* desc) { + SOKOL_ASSERT(view && desc); + _SOKOL_UNUSED(view); + _SOKOL_UNUSED(desc); + return SG_RESOURCESTATE_VALID; +} + +_SOKOL_PRIVATE void _sg_dummy_discard_view(_sg_view_t* view) { + SOKOL_ASSERT(view); + _SOKOL_UNUSED(view); +} + +_SOKOL_PRIVATE void _sg_dummy_begin_pass(const sg_pass* pass, const _sg_attachments_ptrs_t* atts) { + SOKOL_ASSERT(pass && atts); + _SOKOL_UNUSED(pass); + _SOKOL_UNUSED(atts); +} + +_SOKOL_PRIVATE void _sg_dummy_end_pass(const _sg_attachments_ptrs_t* atts) { + SOKOL_ASSERT(atts); + _SOKOL_UNUSED(atts); +} + +_SOKOL_PRIVATE void _sg_dummy_commit(void) { + // empty +} + +_SOKOL_PRIVATE void _sg_dummy_apply_viewport(int x, int y, int w, int h, bool origin_top_left) { + _SOKOL_UNUSED(x); + _SOKOL_UNUSED(y); + _SOKOL_UNUSED(w); + _SOKOL_UNUSED(h); + _SOKOL_UNUSED(origin_top_left); +} + +_SOKOL_PRIVATE void _sg_dummy_apply_scissor_rect(int x, int y, int w, int h, bool origin_top_left) { + _SOKOL_UNUSED(x); + _SOKOL_UNUSED(y); + _SOKOL_UNUSED(w); + _SOKOL_UNUSED(h); + _SOKOL_UNUSED(origin_top_left); +} + +_SOKOL_PRIVATE void _sg_dummy_apply_pipeline(_sg_pipeline_t* pip) { + SOKOL_ASSERT(pip); + _SOKOL_UNUSED(pip); +} + +_SOKOL_PRIVATE bool _sg_dummy_apply_bindings(_sg_bindings_ptrs_t* bnd) { + SOKOL_ASSERT(bnd); + SOKOL_ASSERT(bnd->pip); + _SOKOL_UNUSED(bnd); + return true; +} + +_SOKOL_PRIVATE void _sg_dummy_apply_uniforms(int ub_slot, const sg_range* data) { + _SOKOL_UNUSED(ub_slot); + _SOKOL_UNUSED(data); +} + +_SOKOL_PRIVATE void _sg_dummy_draw(int base_element, int num_elements, int num_instances, int base_vertex, int base_instance) { + _SOKOL_UNUSED(base_element); + _SOKOL_UNUSED(num_elements); + _SOKOL_UNUSED(num_instances); + _SOKOL_UNUSED(base_vertex); + _SOKOL_UNUSED(base_instance); +} + +_SOKOL_PRIVATE void _sg_dummy_dispatch(int num_groups_x, int num_groups_y, int num_groups_z) { + _SOKOL_UNUSED(num_groups_x); + _SOKOL_UNUSED(num_groups_y); + _SOKOL_UNUSED(num_groups_z); +} + +_SOKOL_PRIVATE void _sg_dummy_update_buffer(_sg_buffer_t* buf, const sg_range* data) { + SOKOL_ASSERT(buf && data && data->ptr && (data->size > 0)); + _SOKOL_UNUSED(data); + if (++buf->cmn.active_slot >= buf->cmn.num_slots) { + buf->cmn.active_slot = 0; + } +} + +_SOKOL_PRIVATE bool _sg_dummy_append_buffer(_sg_buffer_t* buf, const sg_range* data, bool new_frame) { + SOKOL_ASSERT(buf && data && data->ptr && (data->size > 0)); + _SOKOL_UNUSED(data); + if (new_frame) { + if (++buf->cmn.active_slot >= buf->cmn.num_slots) { + buf->cmn.active_slot = 0; + } + } + return true; +} + +_SOKOL_PRIVATE void _sg_dummy_update_image(_sg_image_t* img, const sg_image_data* data) { + SOKOL_ASSERT(img && data); + _SOKOL_UNUSED(data); + if (++img->cmn.active_slot >= img->cmn.num_slots) { + img->cmn.active_slot = 0; + } +} + +// ██████ ██████ ███████ ███ ██ ██████ ██ ██████ █████ ██████ ██ ██ ███████ ███ ██ ██████ +// ██ ██ ██ ██ ██ ████ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██ ██ ██ +// ██ ██ ██████ █████ ██ ██ ██ ██ ███ ██ ██████ ███████ ██ █████ █████ ██ ██ ██ ██ ██ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██████ ██ ███████ ██ ████ ██████ ███████ ██████ ██ ██ ██████ ██ ██ ███████ ██ ████ ██████ +// +// >>opengl backend +#elif defined(_SOKOL_ANY_GL) + +// optional GL loader for win32 +#if defined(_SOKOL_USE_WIN32_GL_LOADER) + +#ifndef SG_GL_FUNCS_EXT +#define SG_GL_FUNCS_EXT +#endif + +// X Macro list of GL function names and signatures +#define _SG_GL_FUNCS \ + SG_GL_FUNCS_EXT \ + _SG_XMACRO(glBindVertexArray, void, (GLuint array)) \ + _SG_XMACRO(glFramebufferTextureLayer, void, (GLenum target, GLenum attachment, GLuint texture, GLint level, GLint layer)) \ + _SG_XMACRO(glGenFramebuffers, void, (GLsizei n, GLuint * framebuffers)) \ + _SG_XMACRO(glBindFramebuffer, void, (GLenum target, GLuint framebuffer)) \ + _SG_XMACRO(glBindRenderbuffer, void, (GLenum target, GLuint renderbuffer)) \ + _SG_XMACRO(glGetStringi, const GLubyte *, (GLenum name, GLuint index)) \ + _SG_XMACRO(glClearBufferfi, void, (GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil)) \ + _SG_XMACRO(glClearBufferfv, void, (GLenum buffer, GLint drawbuffer, const GLfloat * value)) \ + _SG_XMACRO(glClearBufferuiv, void, (GLenum buffer, GLint drawbuffer, const GLuint * value)) \ + _SG_XMACRO(glClearBufferiv, void, (GLenum buffer, GLint drawbuffer, const GLint * value)) \ + _SG_XMACRO(glDeleteRenderbuffers, void, (GLsizei n, const GLuint * renderbuffers)) \ + _SG_XMACRO(glUniform1fv, void, (GLint location, GLsizei count, const GLfloat * value)) \ + _SG_XMACRO(glUniform2fv, void, (GLint location, GLsizei count, const GLfloat * value)) \ + _SG_XMACRO(glUniform3fv, void, (GLint location, GLsizei count, const GLfloat * value)) \ + _SG_XMACRO(glUniform4fv, void, (GLint location, GLsizei count, const GLfloat * value)) \ + _SG_XMACRO(glUniform1iv, void, (GLint location, GLsizei count, const GLint * value)) \ + _SG_XMACRO(glUniform2iv, void, (GLint location, GLsizei count, const GLint * value)) \ + _SG_XMACRO(glUniform3iv, void, (GLint location, GLsizei count, const GLint * value)) \ + _SG_XMACRO(glUniform4iv, void, (GLint location, GLsizei count, const GLint * value)) \ + _SG_XMACRO(glUniformMatrix4fv, void, (GLint location, GLsizei count, GLboolean transpose, const GLfloat * value)) \ + _SG_XMACRO(glUseProgram, void, (GLuint program)) \ + _SG_XMACRO(glShaderSource, void, (GLuint shader, GLsizei count, const GLchar *const* string, const GLint * length)) \ + _SG_XMACRO(glLinkProgram, void, (GLuint program)) \ + _SG_XMACRO(glGetUniformLocation, GLint, (GLuint program, const GLchar * name)) \ + _SG_XMACRO(glGetShaderiv, void, (GLuint shader, GLenum pname, GLint * params)) \ + _SG_XMACRO(glGetProgramInfoLog, void, (GLuint program, GLsizei bufSize, GLsizei * length, GLchar * infoLog)) \ + _SG_XMACRO(glGetAttribLocation, GLint, (GLuint program, const GLchar * name)) \ + _SG_XMACRO(glDisableVertexAttribArray, void, (GLuint index)) \ + _SG_XMACRO(glDeleteShader, void, (GLuint shader)) \ + _SG_XMACRO(glDeleteProgram, void, (GLuint program)) \ + _SG_XMACRO(glCompileShader, void, (GLuint shader)) \ + _SG_XMACRO(glStencilFuncSeparate, void, (GLenum face, GLenum func, GLint ref, GLuint mask)) \ + _SG_XMACRO(glStencilOpSeparate, void, (GLenum face, GLenum sfail, GLenum dpfail, GLenum dppass)) \ + _SG_XMACRO(glRenderbufferStorageMultisample, void, (GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height)) \ + _SG_XMACRO(glDrawBuffers, void, (GLsizei n, const GLenum * bufs)) \ + _SG_XMACRO(glVertexAttribDivisor, void, (GLuint index, GLuint divisor)) \ + _SG_XMACRO(glBufferSubData, void, (GLenum target, GLintptr offset, GLsizeiptr size, const void * data)) \ + _SG_XMACRO(glGenBuffers, void, (GLsizei n, GLuint * buffers)) \ + _SG_XMACRO(glCheckFramebufferStatus, GLenum, (GLenum target)) \ + _SG_XMACRO(glFramebufferRenderbuffer, void, (GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer)) \ + _SG_XMACRO(glCompressedTexImage2D, void, (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const void * data)) \ + _SG_XMACRO(glCompressedTexImage3D, void, (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const void * data)) \ + _SG_XMACRO(glActiveTexture, void, (GLenum texture)) \ + _SG_XMACRO(glTexSubImage3D, void, (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const void * pixels)) \ + _SG_XMACRO(glRenderbufferStorage, void, (GLenum target, GLenum internalformat, GLsizei width, GLsizei height)) \ + _SG_XMACRO(glGenTextures, void, (GLsizei n, GLuint * textures)) \ + _SG_XMACRO(glPolygonOffset, void, (GLfloat factor, GLfloat units)) \ + _SG_XMACRO(glDrawElements, void, (GLenum mode, GLsizei count, GLenum type, const void * indices)) \ + _SG_XMACRO(glDeleteFramebuffers, void, (GLsizei n, const GLuint * framebuffers)) \ + _SG_XMACRO(glBlendEquationSeparate, void, (GLenum modeRGB, GLenum modeAlpha)) \ + _SG_XMACRO(glDeleteTextures, void, (GLsizei n, const GLuint * textures)) \ + _SG_XMACRO(glGetProgramiv, void, (GLuint program, GLenum pname, GLint * params)) \ + _SG_XMACRO(glBindTexture, void, (GLenum target, GLuint texture)) \ + _SG_XMACRO(glTexImage3D, void, (GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const void * pixels)) \ + _SG_XMACRO(glCreateShader, GLuint, (GLenum type)) \ + _SG_XMACRO(glTexSubImage2D, void, (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const void * pixels)) \ + _SG_XMACRO(glFramebufferTexture2D, void, (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level)) \ + _SG_XMACRO(glCreateProgram, GLuint, (void)) \ + _SG_XMACRO(glViewport, void, (GLint x, GLint y, GLsizei width, GLsizei height)) \ + _SG_XMACRO(glDeleteBuffers, void, (GLsizei n, const GLuint * buffers)) \ + _SG_XMACRO(glDrawArrays, void, (GLenum mode, GLint first, GLsizei count)) \ + _SG_XMACRO(glDrawElementsInstanced, void, (GLenum mode, GLsizei count, GLenum type, const void * indices, GLsizei instancecount)) \ + _SG_XMACRO(glVertexAttribPointer, void, (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void * pointer)) \ + _SG_XMACRO(glVertexAttribIPointer, void, (GLuint index, GLint size, GLenum type, GLsizei stride, const void * pointer)) \ + _SG_XMACRO(glUniform1i, void, (GLint location, GLint v0)) \ + _SG_XMACRO(glDisable, void, (GLenum cap)) \ + _SG_XMACRO(glColorMask, void, (GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha)) \ + _SG_XMACRO(glColorMaski, void, (GLuint buf, GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha)) \ + _SG_XMACRO(glBindBuffer, void, (GLenum target, GLuint buffer)) \ + _SG_XMACRO(glDeleteVertexArrays, void, (GLsizei n, const GLuint * arrays)) \ + _SG_XMACRO(glDepthMask, void, (GLboolean flag)) \ + _SG_XMACRO(glDrawArraysInstanced, void, (GLenum mode, GLint first, GLsizei count, GLsizei instancecount)) \ + _SG_XMACRO(glScissor, void, (GLint x, GLint y, GLsizei width, GLsizei height)) \ + _SG_XMACRO(glGenRenderbuffers, void, (GLsizei n, GLuint * renderbuffers)) \ + _SG_XMACRO(glBufferData, void, (GLenum target, GLsizeiptr size, const void * data, GLenum usage)) \ + _SG_XMACRO(glBlendFuncSeparate, void, (GLenum sfactorRGB, GLenum dfactorRGB, GLenum sfactorAlpha, GLenum dfactorAlpha)) \ + _SG_XMACRO(glTexParameteri, void, (GLenum target, GLenum pname, GLint param)) \ + _SG_XMACRO(glGetIntegerv, void, (GLenum pname, GLint * data)) \ + _SG_XMACRO(glEnable, void, (GLenum cap)) \ + _SG_XMACRO(glBlitFramebuffer, void, (GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter)) \ + _SG_XMACRO(glStencilMask, void, (GLuint mask)) \ + _SG_XMACRO(glAttachShader, void, (GLuint program, GLuint shader)) \ + _SG_XMACRO(glGetError, GLenum, (void)) \ + _SG_XMACRO(glBlendColor, void, (GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha)) \ + _SG_XMACRO(glTexParameterf, void, (GLenum target, GLenum pname, GLfloat param)) \ + _SG_XMACRO(glTexParameterfv, void, (GLenum target, GLenum pname, const GLfloat* params)) \ + _SG_XMACRO(glGetShaderInfoLog, void, (GLuint shader, GLsizei bufSize, GLsizei * length, GLchar * infoLog)) \ + _SG_XMACRO(glDepthFunc, void, (GLenum func)) \ + _SG_XMACRO(glStencilOp , void, (GLenum fail, GLenum zfail, GLenum zpass)) \ + _SG_XMACRO(glStencilFunc, void, (GLenum func, GLint ref, GLuint mask)) \ + _SG_XMACRO(glEnableVertexAttribArray, void, (GLuint index)) \ + _SG_XMACRO(glBlendFunc, void, (GLenum sfactor, GLenum dfactor)) \ + _SG_XMACRO(glReadBuffer, void, (GLenum src)) \ + _SG_XMACRO(glTexImage2D, void, (GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const void * pixels)) \ + _SG_XMACRO(glGenVertexArrays, void, (GLsizei n, GLuint * arrays)) \ + _SG_XMACRO(glFrontFace, void, (GLenum mode)) \ + _SG_XMACRO(glCullFace, void, (GLenum mode)) \ + _SG_XMACRO(glPixelStorei, void, (GLenum pname, GLint param)) \ + _SG_XMACRO(glBindSampler, void, (GLuint unit, GLuint sampler)) \ + _SG_XMACRO(glGenSamplers, void, (GLsizei n, GLuint* samplers)) \ + _SG_XMACRO(glSamplerParameteri, void, (GLuint sampler, GLenum pname, GLint param)) \ + _SG_XMACRO(glSamplerParameterf, void, (GLuint sampler, GLenum pname, GLfloat param)) \ + _SG_XMACRO(glSamplerParameterfv, void, (GLuint sampler, GLenum pname, const GLfloat* params)) \ + _SG_XMACRO(glDeleteSamplers, void, (GLsizei n, const GLuint* samplers)) \ + _SG_XMACRO(glBindBufferBase, void, (GLenum target, GLuint index, GLuint buffer)) \ + _SG_XMACRO(glBindBufferRange, void, (GLenum target, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size)) \ + _SG_XMACRO(glTexImage2DMultisample, void, (GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLboolean fixedsamplelocations)) \ + _SG_XMACRO(glTexImage3DMultisample, void, (GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLboolean fixedsamplelocations)) \ + _SG_XMACRO(glDispatchCompute, void, (GLuint num_groups_x, GLuint num_groups_y, GLuint num_groups_z)) \ + _SG_XMACRO(glMemoryBarrier, void, (GLbitfield barriers)) \ + _SG_XMACRO(glBindImageTexture, void, (GLuint unit, GLuint texture, GLint level, GLboolean layered, GLint layer, GLenum access, GLenum format)) \ + _SG_XMACRO(glTexStorage2DMultisample, void, (GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLboolean fixedsamplelocations)) \ + _SG_XMACRO(glTexStorage2D, void, (GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height)) \ + _SG_XMACRO(glTexStorage3DMultisample, void, (GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLboolean fixedsamplelocations)) \ + _SG_XMACRO(glTexStorage3D, void, (GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth)) \ + _SG_XMACRO(glCompressedTexSubImage2D, void, (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const void *data)) \ + _SG_XMACRO(glCompressedTexSubImage3D, void, (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const void *data)) \ + _SG_XMACRO(glTextureView, void, (GLuint texture, GLenum target, GLuint origtexture, GLenum internalformat, GLuint minlevel, GLuint numlevels, GLuint minlayer, GLuint numlayers)) \ + _SG_XMACRO(glDrawElementsBaseVertex, void, (GLenum mode, GLsizei count, GLenum type, const void* indices, GLint basevertex)) \ + _SG_XMACRO(glDrawElementsInstancedBaseVertex, void, (GLenum mode, GLsizei count, GLenum type, const void* indices, GLsizei instancecount, GLint basevertex)) \ + _SG_XMACRO(glDrawElementsInstancedBaseVertexBaseInstance, void, (GLenum mode, GLsizei count, GLenum type, const void* indices, GLsizei instancecount, GLint basevertex, GLuint baseinstance)) \ + _SG_XMACRO(glDrawArraysInstancedBaseInstance, void, (GLenum mode, GLint first, GLsizei count, GLsizei instancecount, GLuint baseinstance)) + +// generate GL function pointer typedefs +#define _SG_XMACRO(name, ret, args) typedef ret (GL_APIENTRY* PFN_ ## name) args; +_SG_GL_FUNCS +#undef _SG_XMACRO + +// generate GL function pointers +#define _SG_XMACRO(name, ret, args) static PFN_ ## name name; +_SG_GL_FUNCS +#undef _SG_XMACRO + +// helper function to lookup GL functions in GL DLL +typedef PROC (WINAPI * _sg_wglGetProcAddress)(LPCSTR); +_SOKOL_PRIVATE void* _sg_gl_getprocaddr(const char* name, _sg_wglGetProcAddress wgl_getprocaddress) { + void* proc_addr = (void*) wgl_getprocaddress(name); + if (0 == proc_addr) { + proc_addr = (void*) GetProcAddress(_sg.gl.opengl32_dll, name); + } + SOKOL_ASSERT(proc_addr); + return proc_addr; +} + +// populate GL function pointers +_SOKOL_PRIVATE void _sg_gl_load_opengl(void) { + SOKOL_ASSERT(0 == _sg.gl.opengl32_dll); + _sg.gl.opengl32_dll = LoadLibraryA("opengl32.dll"); + SOKOL_ASSERT(_sg.gl.opengl32_dll); + _sg_wglGetProcAddress wgl_getprocaddress = (_sg_wglGetProcAddress) GetProcAddress(_sg.gl.opengl32_dll, "wglGetProcAddress"); + SOKOL_ASSERT(wgl_getprocaddress); + #define _SG_XMACRO(name, ret, args) name = (PFN_ ## name) _sg_gl_getprocaddr(#name, wgl_getprocaddress); + _SG_GL_FUNCS + #undef _SG_XMACRO +} + +_SOKOL_PRIVATE void _sg_gl_unload_opengl(void) { + SOKOL_ASSERT(_sg.gl.opengl32_dll); + FreeLibrary(_sg.gl.opengl32_dll); + _sg.gl.opengl32_dll = 0; +} +#endif // _SOKOL_USE_WIN32_GL_LOADER + +//-- type translation ---------------------------------------------------------- +_SOKOL_PRIVATE GLenum _sg_gl_buffer_target(const sg_buffer_usage* usg) { + // NOTE: the buffer target returned here is only used for the bind point + // to copy data into the buffer, expect for WebGL2, the bind point doesn't + // need to match the later usage of the buffer (but because of the WebGL2 + // restriction we cannot simply select a random bind point, because in WebGL2 + // a buffer cannot 'switch' bind points later. + if (usg->vertex_buffer) { + return GL_ARRAY_BUFFER; + } else if (usg->index_buffer) { + return GL_ELEMENT_ARRAY_BUFFER; + } else if (usg->storage_buffer) { + return GL_SHADER_STORAGE_BUFFER; + } else { + SOKOL_UNREACHABLE; return 0; + } +} + +_SOKOL_PRIVATE GLenum _sg_gl_texture_target(sg_image_type t, int sample_count) { + #if defined(SOKOL_GLCORE) + const bool msaa = sample_count > 1; + if (msaa) { + switch (t) { + case SG_IMAGETYPE_2D: return GL_TEXTURE_2D_MULTISAMPLE; + case SG_IMAGETYPE_ARRAY: return GL_TEXTURE_2D_MULTISAMPLE_ARRAY; + default: SOKOL_UNREACHABLE; return 0; + } + } else { + switch (t) { + case SG_IMAGETYPE_2D: return GL_TEXTURE_2D; + case SG_IMAGETYPE_CUBE: return GL_TEXTURE_CUBE_MAP; + case SG_IMAGETYPE_3D: return GL_TEXTURE_3D; + case SG_IMAGETYPE_ARRAY: return GL_TEXTURE_2D_ARRAY; + default: SOKOL_UNREACHABLE; return 0; + } + } + #else + SOKOL_ASSERT(sample_count == 1); _SOKOL_UNUSED(sample_count); + switch (t) { + case SG_IMAGETYPE_2D: return GL_TEXTURE_2D; + case SG_IMAGETYPE_CUBE: return GL_TEXTURE_CUBE_MAP; + case SG_IMAGETYPE_3D: return GL_TEXTURE_3D; + case SG_IMAGETYPE_ARRAY: return GL_TEXTURE_2D_ARRAY; + default: SOKOL_UNREACHABLE; return 0; + } + #endif +} + +_SOKOL_PRIVATE GLenum _sg_gl_buffer_usage(const sg_buffer_usage* usg) { + if (usg->immutable) { + return GL_STATIC_DRAW; + } else if (usg->dynamic_update) { + return GL_DYNAMIC_DRAW; + } else if (usg->stream_update) { + return GL_STREAM_DRAW; + } else { + SOKOL_UNREACHABLE; return 0; + } +} + +_SOKOL_PRIVATE GLenum _sg_gl_shader_stage(sg_shader_stage stage) { + switch (stage) { + case SG_SHADERSTAGE_VERTEX: return GL_VERTEX_SHADER; + case SG_SHADERSTAGE_FRAGMENT: return GL_FRAGMENT_SHADER; + case SG_SHADERSTAGE_COMPUTE: return GL_COMPUTE_SHADER; + default: SOKOL_UNREACHABLE; return 0; + } +} + +_SOKOL_PRIVATE GLint _sg_gl_vertexformat_size(sg_vertex_format fmt) { + switch (fmt) { + case SG_VERTEXFORMAT_FLOAT: return 1; + case SG_VERTEXFORMAT_FLOAT2: return 2; + case SG_VERTEXFORMAT_FLOAT3: return 3; + case SG_VERTEXFORMAT_FLOAT4: return 4; + case SG_VERTEXFORMAT_INT: return 1; + case SG_VERTEXFORMAT_INT2: return 2; + case SG_VERTEXFORMAT_INT3: return 3; + case SG_VERTEXFORMAT_INT4: return 4; + case SG_VERTEXFORMAT_UINT: return 1; + case SG_VERTEXFORMAT_UINT2: return 2; + case SG_VERTEXFORMAT_UINT3: return 3; + case SG_VERTEXFORMAT_UINT4: return 4; + case SG_VERTEXFORMAT_BYTE4: return 4; + case SG_VERTEXFORMAT_BYTE4N: return 4; + case SG_VERTEXFORMAT_UBYTE4: return 4; + case SG_VERTEXFORMAT_UBYTE4N: return 4; + case SG_VERTEXFORMAT_SHORT2: return 2; + case SG_VERTEXFORMAT_SHORT2N: return 2; + case SG_VERTEXFORMAT_USHORT2: return 2; + case SG_VERTEXFORMAT_USHORT2N: return 2; + case SG_VERTEXFORMAT_SHORT4: return 4; + case SG_VERTEXFORMAT_SHORT4N: return 4; + case SG_VERTEXFORMAT_USHORT4: return 4; + case SG_VERTEXFORMAT_USHORT4N: return 4; + case SG_VERTEXFORMAT_UINT10_N2: return 4; + case SG_VERTEXFORMAT_HALF2: return 2; + case SG_VERTEXFORMAT_HALF4: return 4; + default: SOKOL_UNREACHABLE; return 0; + } +} + +_SOKOL_PRIVATE GLenum _sg_gl_vertexformat_type(sg_vertex_format fmt) { + switch (fmt) { + case SG_VERTEXFORMAT_FLOAT: + case SG_VERTEXFORMAT_FLOAT2: + case SG_VERTEXFORMAT_FLOAT3: + case SG_VERTEXFORMAT_FLOAT4: + return GL_FLOAT; + case SG_VERTEXFORMAT_INT: + case SG_VERTEXFORMAT_INT2: + case SG_VERTEXFORMAT_INT3: + case SG_VERTEXFORMAT_INT4: + return GL_INT; + case SG_VERTEXFORMAT_UINT: + case SG_VERTEXFORMAT_UINT2: + case SG_VERTEXFORMAT_UINT3: + case SG_VERTEXFORMAT_UINT4: + return GL_UNSIGNED_INT; + case SG_VERTEXFORMAT_BYTE4: + case SG_VERTEXFORMAT_BYTE4N: + return GL_BYTE; + case SG_VERTEXFORMAT_UBYTE4: + case SG_VERTEXFORMAT_UBYTE4N: + return GL_UNSIGNED_BYTE; + case SG_VERTEXFORMAT_SHORT2: + case SG_VERTEXFORMAT_SHORT2N: + case SG_VERTEXFORMAT_SHORT4: + case SG_VERTEXFORMAT_SHORT4N: + return GL_SHORT; + case SG_VERTEXFORMAT_USHORT2: + case SG_VERTEXFORMAT_USHORT2N: + case SG_VERTEXFORMAT_USHORT4: + case SG_VERTEXFORMAT_USHORT4N: + return GL_UNSIGNED_SHORT; + case SG_VERTEXFORMAT_UINT10_N2: + return GL_UNSIGNED_INT_2_10_10_10_REV; + case SG_VERTEXFORMAT_HALF2: + case SG_VERTEXFORMAT_HALF4: + return GL_HALF_FLOAT; + default: + SOKOL_UNREACHABLE; return 0; + } +} + +_SOKOL_PRIVATE GLboolean _sg_gl_vertexformat_normalized(sg_vertex_format fmt) { + switch (fmt) { + case SG_VERTEXFORMAT_BYTE4N: + case SG_VERTEXFORMAT_UBYTE4N: + case SG_VERTEXFORMAT_SHORT2N: + case SG_VERTEXFORMAT_USHORT2N: + case SG_VERTEXFORMAT_SHORT4N: + case SG_VERTEXFORMAT_USHORT4N: + case SG_VERTEXFORMAT_UINT10_N2: + return GL_TRUE; + default: + return GL_FALSE; + } +} + +_SOKOL_PRIVATE GLenum _sg_gl_primitive_type(sg_primitive_type t) { + switch (t) { + case SG_PRIMITIVETYPE_POINTS: return GL_POINTS; + case SG_PRIMITIVETYPE_LINES: return GL_LINES; + case SG_PRIMITIVETYPE_LINE_STRIP: return GL_LINE_STRIP; + case SG_PRIMITIVETYPE_TRIANGLES: return GL_TRIANGLES; + case SG_PRIMITIVETYPE_TRIANGLE_STRIP: return GL_TRIANGLE_STRIP; + default: SOKOL_UNREACHABLE; return 0; + } +} + +_SOKOL_PRIVATE GLenum _sg_gl_index_type(sg_index_type t) { + switch (t) { + case SG_INDEXTYPE_NONE: return 0; + case SG_INDEXTYPE_UINT16: return GL_UNSIGNED_SHORT; + case SG_INDEXTYPE_UINT32: return GL_UNSIGNED_INT; + default: SOKOL_UNREACHABLE; return 0; + } +} + +_SOKOL_PRIVATE GLenum _sg_gl_compare_func(sg_compare_func cmp) { + switch (cmp) { + case SG_COMPAREFUNC_NEVER: return GL_NEVER; + case SG_COMPAREFUNC_LESS: return GL_LESS; + case SG_COMPAREFUNC_EQUAL: return GL_EQUAL; + case SG_COMPAREFUNC_LESS_EQUAL: return GL_LEQUAL; + case SG_COMPAREFUNC_GREATER: return GL_GREATER; + case SG_COMPAREFUNC_NOT_EQUAL: return GL_NOTEQUAL; + case SG_COMPAREFUNC_GREATER_EQUAL: return GL_GEQUAL; + case SG_COMPAREFUNC_ALWAYS: return GL_ALWAYS; + default: SOKOL_UNREACHABLE; return 0; + } +} + +_SOKOL_PRIVATE GLenum _sg_gl_stencil_op(sg_stencil_op op) { + switch (op) { + case SG_STENCILOP_KEEP: return GL_KEEP; + case SG_STENCILOP_ZERO: return GL_ZERO; + case SG_STENCILOP_REPLACE: return GL_REPLACE; + case SG_STENCILOP_INCR_CLAMP: return GL_INCR; + case SG_STENCILOP_DECR_CLAMP: return GL_DECR; + case SG_STENCILOP_INVERT: return GL_INVERT; + case SG_STENCILOP_INCR_WRAP: return GL_INCR_WRAP; + case SG_STENCILOP_DECR_WRAP: return GL_DECR_WRAP; + default: SOKOL_UNREACHABLE; return 0; + } +} + +_SOKOL_PRIVATE GLenum _sg_gl_blend_factor(sg_blend_factor f) { + switch (f) { + case SG_BLENDFACTOR_ZERO: return GL_ZERO; + case SG_BLENDFACTOR_ONE: return GL_ONE; + case SG_BLENDFACTOR_SRC_COLOR: return GL_SRC_COLOR; + case SG_BLENDFACTOR_ONE_MINUS_SRC_COLOR: return GL_ONE_MINUS_SRC_COLOR; + case SG_BLENDFACTOR_SRC_ALPHA: return GL_SRC_ALPHA; + case SG_BLENDFACTOR_ONE_MINUS_SRC_ALPHA: return GL_ONE_MINUS_SRC_ALPHA; + case SG_BLENDFACTOR_DST_COLOR: return GL_DST_COLOR; + case SG_BLENDFACTOR_ONE_MINUS_DST_COLOR: return GL_ONE_MINUS_DST_COLOR; + case SG_BLENDFACTOR_DST_ALPHA: return GL_DST_ALPHA; + case SG_BLENDFACTOR_ONE_MINUS_DST_ALPHA: return GL_ONE_MINUS_DST_ALPHA; + case SG_BLENDFACTOR_SRC_ALPHA_SATURATED: return GL_SRC_ALPHA_SATURATE; + case SG_BLENDFACTOR_BLEND_COLOR: return GL_CONSTANT_COLOR; + case SG_BLENDFACTOR_ONE_MINUS_BLEND_COLOR: return GL_ONE_MINUS_CONSTANT_COLOR; + case SG_BLENDFACTOR_BLEND_ALPHA: return GL_CONSTANT_ALPHA; + case SG_BLENDFACTOR_ONE_MINUS_BLEND_ALPHA: return GL_ONE_MINUS_CONSTANT_ALPHA; + default: SOKOL_UNREACHABLE; return 0; + } +} + +_SOKOL_PRIVATE GLenum _sg_gl_blend_op(sg_blend_op op) { + switch (op) { + case SG_BLENDOP_ADD: return GL_FUNC_ADD; + case SG_BLENDOP_SUBTRACT: return GL_FUNC_SUBTRACT; + case SG_BLENDOP_REVERSE_SUBTRACT: return GL_FUNC_REVERSE_SUBTRACT; + case SG_BLENDOP_MIN: return GL_MIN; + case SG_BLENDOP_MAX: return GL_MAX; + default: SOKOL_UNREACHABLE; return 0; + } +} + +_SOKOL_PRIVATE GLenum _sg_gl_min_filter(sg_filter min_f, sg_filter mipmap_f) { + if (min_f == SG_FILTER_NEAREST) { + switch (mipmap_f) { + case SG_FILTER_NEAREST: return GL_NEAREST_MIPMAP_NEAREST; + case SG_FILTER_LINEAR: return GL_NEAREST_MIPMAP_LINEAR; + default: SOKOL_UNREACHABLE; return (GLenum)0; + } + } else if (min_f == SG_FILTER_LINEAR) { + switch (mipmap_f) { + case SG_FILTER_NEAREST: return GL_LINEAR_MIPMAP_NEAREST; + case SG_FILTER_LINEAR: return GL_LINEAR_MIPMAP_LINEAR; + default: SOKOL_UNREACHABLE; return (GLenum)0; + } + } else { + SOKOL_UNREACHABLE; return (GLenum)0; + } +} + +_SOKOL_PRIVATE GLenum _sg_gl_mag_filter(sg_filter mag_f) { + if (mag_f == SG_FILTER_NEAREST) { + return GL_NEAREST; + } else { + return GL_LINEAR; + } +} + +_SOKOL_PRIVATE GLenum _sg_gl_wrap(sg_wrap w) { + switch (w) { + case SG_WRAP_CLAMP_TO_EDGE: return GL_CLAMP_TO_EDGE; + #if defined(SOKOL_GLCORE) + case SG_WRAP_CLAMP_TO_BORDER: return GL_CLAMP_TO_BORDER; + #else + case SG_WRAP_CLAMP_TO_BORDER: return GL_CLAMP_TO_EDGE; + #endif + case SG_WRAP_REPEAT: return GL_REPEAT; + case SG_WRAP_MIRRORED_REPEAT: return GL_MIRRORED_REPEAT; + default: SOKOL_UNREACHABLE; return 0; + } +} + +_SOKOL_PRIVATE GLenum _sg_gl_teximage_type(sg_pixel_format fmt) { + switch (fmt) { + case SG_PIXELFORMAT_R8: + case SG_PIXELFORMAT_R8UI: + case SG_PIXELFORMAT_RG8: + case SG_PIXELFORMAT_RG8UI: + case SG_PIXELFORMAT_RGBA8: + case SG_PIXELFORMAT_SRGB8A8: + case SG_PIXELFORMAT_RGBA8UI: + case SG_PIXELFORMAT_BGRA8: + return GL_UNSIGNED_BYTE; + case SG_PIXELFORMAT_R8SN: + case SG_PIXELFORMAT_R8SI: + case SG_PIXELFORMAT_RG8SN: + case SG_PIXELFORMAT_RG8SI: + case SG_PIXELFORMAT_RGBA8SN: + case SG_PIXELFORMAT_RGBA8SI: + return GL_BYTE; + case SG_PIXELFORMAT_R16: + case SG_PIXELFORMAT_R16UI: + case SG_PIXELFORMAT_RG16: + case SG_PIXELFORMAT_RG16UI: + case SG_PIXELFORMAT_RGBA16: + case SG_PIXELFORMAT_RGBA16UI: + return GL_UNSIGNED_SHORT; + case SG_PIXELFORMAT_R16SN: + case SG_PIXELFORMAT_R16SI: + case SG_PIXELFORMAT_RG16SN: + case SG_PIXELFORMAT_RG16SI: + case SG_PIXELFORMAT_RGBA16SN: + case SG_PIXELFORMAT_RGBA16SI: + return GL_SHORT; + case SG_PIXELFORMAT_R16F: + case SG_PIXELFORMAT_RG16F: + case SG_PIXELFORMAT_RGBA16F: + return GL_HALF_FLOAT; + case SG_PIXELFORMAT_R32UI: + case SG_PIXELFORMAT_RG32UI: + case SG_PIXELFORMAT_RGBA32UI: + return GL_UNSIGNED_INT; + case SG_PIXELFORMAT_R32SI: + case SG_PIXELFORMAT_RG32SI: + case SG_PIXELFORMAT_RGBA32SI: + return GL_INT; + case SG_PIXELFORMAT_R32F: + case SG_PIXELFORMAT_RG32F: + case SG_PIXELFORMAT_RGBA32F: + return GL_FLOAT; + case SG_PIXELFORMAT_RGB10A2: + return GL_UNSIGNED_INT_2_10_10_10_REV; + case SG_PIXELFORMAT_RG11B10F: + return GL_UNSIGNED_INT_10F_11F_11F_REV; + case SG_PIXELFORMAT_RGB9E5: + return GL_UNSIGNED_INT_5_9_9_9_REV; + case SG_PIXELFORMAT_DEPTH: + return GL_FLOAT; + case SG_PIXELFORMAT_DEPTH_STENCIL: + return GL_UNSIGNED_INT_24_8; + default: + SOKOL_UNREACHABLE; return 0; + } +} + +_SOKOL_PRIVATE GLenum _sg_gl_teximage_format(sg_pixel_format fmt) { + switch (fmt) { + case SG_PIXELFORMAT_R8: + case SG_PIXELFORMAT_R8SN: + case SG_PIXELFORMAT_R16: + case SG_PIXELFORMAT_R16SN: + case SG_PIXELFORMAT_R16F: + case SG_PIXELFORMAT_R32F: + return GL_RED; + case SG_PIXELFORMAT_R8UI: + case SG_PIXELFORMAT_R8SI: + case SG_PIXELFORMAT_R16UI: + case SG_PIXELFORMAT_R16SI: + case SG_PIXELFORMAT_R32UI: + case SG_PIXELFORMAT_R32SI: + return GL_RED_INTEGER; + case SG_PIXELFORMAT_RG8: + case SG_PIXELFORMAT_RG8SN: + case SG_PIXELFORMAT_RG16: + case SG_PIXELFORMAT_RG16SN: + case SG_PIXELFORMAT_RG16F: + case SG_PIXELFORMAT_RG32F: + return GL_RG; + case SG_PIXELFORMAT_RG8UI: + case SG_PIXELFORMAT_RG8SI: + case SG_PIXELFORMAT_RG16UI: + case SG_PIXELFORMAT_RG16SI: + case SG_PIXELFORMAT_RG32UI: + case SG_PIXELFORMAT_RG32SI: + return GL_RG_INTEGER; + case SG_PIXELFORMAT_RGBA8: + case SG_PIXELFORMAT_SRGB8A8: + case SG_PIXELFORMAT_RGBA8SN: + case SG_PIXELFORMAT_RGBA16: + case SG_PIXELFORMAT_RGBA16SN: + case SG_PIXELFORMAT_RGBA16F: + case SG_PIXELFORMAT_RGBA32F: + case SG_PIXELFORMAT_RGB10A2: + return GL_RGBA; + case SG_PIXELFORMAT_RGBA8UI: + case SG_PIXELFORMAT_RGBA8SI: + case SG_PIXELFORMAT_RGBA16UI: + case SG_PIXELFORMAT_RGBA16SI: + case SG_PIXELFORMAT_RGBA32UI: + case SG_PIXELFORMAT_RGBA32SI: + return GL_RGBA_INTEGER; + case SG_PIXELFORMAT_RG11B10F: + case SG_PIXELFORMAT_RGB9E5: + return GL_RGB; + case SG_PIXELFORMAT_DEPTH: + return GL_DEPTH_COMPONENT; + case SG_PIXELFORMAT_DEPTH_STENCIL: + return GL_DEPTH_STENCIL; + case SG_PIXELFORMAT_BC1_RGBA: + return GL_COMPRESSED_RGBA_S3TC_DXT1_EXT; + case SG_PIXELFORMAT_BC2_RGBA: + return GL_COMPRESSED_RGBA_S3TC_DXT3_EXT; + case SG_PIXELFORMAT_BC3_RGBA: + return GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; + case SG_PIXELFORMAT_BC3_SRGBA: + return GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT; + case SG_PIXELFORMAT_BC4_R: + return GL_COMPRESSED_RED_RGTC1; + case SG_PIXELFORMAT_BC4_RSN: + return GL_COMPRESSED_SIGNED_RED_RGTC1; + case SG_PIXELFORMAT_BC5_RG: + return GL_COMPRESSED_RED_GREEN_RGTC2; + case SG_PIXELFORMAT_BC5_RGSN: + return GL_COMPRESSED_SIGNED_RED_GREEN_RGTC2; + case SG_PIXELFORMAT_BC6H_RGBF: + return GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT_ARB; + case SG_PIXELFORMAT_BC6H_RGBUF: + return GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_ARB; + case SG_PIXELFORMAT_BC7_RGBA: + return GL_COMPRESSED_RGBA_BPTC_UNORM_ARB; + case SG_PIXELFORMAT_BC7_SRGBA: + return GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM_ARB; + case SG_PIXELFORMAT_ETC2_RGB8: + return GL_COMPRESSED_RGB8_ETC2; + case SG_PIXELFORMAT_ETC2_SRGB8: + return GL_COMPRESSED_SRGB8_ETC2; + case SG_PIXELFORMAT_ETC2_RGB8A1: + return GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2; + case SG_PIXELFORMAT_ETC2_RGBA8: + return GL_COMPRESSED_RGBA8_ETC2_EAC; + case SG_PIXELFORMAT_ETC2_SRGB8A8: + return GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC; + case SG_PIXELFORMAT_EAC_R11: + return GL_COMPRESSED_R11_EAC; + case SG_PIXELFORMAT_EAC_R11SN: + return GL_COMPRESSED_SIGNED_R11_EAC; + case SG_PIXELFORMAT_EAC_RG11: + return GL_COMPRESSED_RG11_EAC; + case SG_PIXELFORMAT_EAC_RG11SN: + return GL_COMPRESSED_SIGNED_RG11_EAC; + case SG_PIXELFORMAT_ASTC_4x4_RGBA: + return GL_COMPRESSED_RGBA_ASTC_4x4_KHR; + case SG_PIXELFORMAT_ASTC_4x4_SRGBA: + return GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR; + default: + SOKOL_UNREACHABLE; return 0; + } +} + +_SOKOL_PRIVATE GLenum _sg_gl_teximage_internal_format(sg_pixel_format fmt) { + switch (fmt) { + case SG_PIXELFORMAT_R8: return GL_R8; + case SG_PIXELFORMAT_R8SN: return GL_R8_SNORM; + case SG_PIXELFORMAT_R8UI: return GL_R8UI; + case SG_PIXELFORMAT_R8SI: return GL_R8I; + #if !defined(SOKOL_GLES3) + case SG_PIXELFORMAT_R16: return GL_R16; + case SG_PIXELFORMAT_R16SN: return GL_R16_SNORM; + #endif + case SG_PIXELFORMAT_R16UI: return GL_R16UI; + case SG_PIXELFORMAT_R16SI: return GL_R16I; + case SG_PIXELFORMAT_R16F: return GL_R16F; + case SG_PIXELFORMAT_RG8: return GL_RG8; + case SG_PIXELFORMAT_RG8SN: return GL_RG8_SNORM; + case SG_PIXELFORMAT_RG8UI: return GL_RG8UI; + case SG_PIXELFORMAT_RG8SI: return GL_RG8I; + case SG_PIXELFORMAT_R32UI: return GL_R32UI; + case SG_PIXELFORMAT_R32SI: return GL_R32I; + case SG_PIXELFORMAT_R32F: return GL_R32F; + #if !defined(SOKOL_GLES3) + case SG_PIXELFORMAT_RG16: return GL_RG16; + case SG_PIXELFORMAT_RG16SN: return GL_RG16_SNORM; + #endif + case SG_PIXELFORMAT_RG16UI: return GL_RG16UI; + case SG_PIXELFORMAT_RG16SI: return GL_RG16I; + case SG_PIXELFORMAT_RG16F: return GL_RG16F; + case SG_PIXELFORMAT_RGBA8: return GL_RGBA8; + case SG_PIXELFORMAT_SRGB8A8: return GL_SRGB8_ALPHA8; + case SG_PIXELFORMAT_RGBA8SN: return GL_RGBA8_SNORM; + case SG_PIXELFORMAT_RGBA8UI: return GL_RGBA8UI; + case SG_PIXELFORMAT_RGBA8SI: return GL_RGBA8I; + case SG_PIXELFORMAT_RGB10A2: return GL_RGB10_A2; + case SG_PIXELFORMAT_RG11B10F: return GL_R11F_G11F_B10F; + case SG_PIXELFORMAT_RGB9E5: return GL_RGB9_E5; + case SG_PIXELFORMAT_RG32UI: return GL_RG32UI; + case SG_PIXELFORMAT_RG32SI: return GL_RG32I; + case SG_PIXELFORMAT_RG32F: return GL_RG32F; + #if !defined(SOKOL_GLES3) + case SG_PIXELFORMAT_RGBA16: return GL_RGBA16; + case SG_PIXELFORMAT_RGBA16SN: return GL_RGBA16_SNORM; + #endif + case SG_PIXELFORMAT_RGBA16UI: return GL_RGBA16UI; + case SG_PIXELFORMAT_RGBA16SI: return GL_RGBA16I; + case SG_PIXELFORMAT_RGBA16F: return GL_RGBA16F; + case SG_PIXELFORMAT_RGBA32UI: return GL_RGBA32UI; + case SG_PIXELFORMAT_RGBA32SI: return GL_RGBA32I; + case SG_PIXELFORMAT_RGBA32F: return GL_RGBA32F; + case SG_PIXELFORMAT_DEPTH: return GL_DEPTH_COMPONENT32F; + case SG_PIXELFORMAT_DEPTH_STENCIL: return GL_DEPTH24_STENCIL8; + case SG_PIXELFORMAT_BC1_RGBA: return GL_COMPRESSED_RGBA_S3TC_DXT1_EXT; + case SG_PIXELFORMAT_BC2_RGBA: return GL_COMPRESSED_RGBA_S3TC_DXT3_EXT; + case SG_PIXELFORMAT_BC3_RGBA: return GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; + case SG_PIXELFORMAT_BC3_SRGBA: return GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT; + case SG_PIXELFORMAT_BC4_R: return GL_COMPRESSED_RED_RGTC1; + case SG_PIXELFORMAT_BC4_RSN: return GL_COMPRESSED_SIGNED_RED_RGTC1; + case SG_PIXELFORMAT_BC5_RG: return GL_COMPRESSED_RED_GREEN_RGTC2; + case SG_PIXELFORMAT_BC5_RGSN: return GL_COMPRESSED_SIGNED_RED_GREEN_RGTC2; + case SG_PIXELFORMAT_BC6H_RGBF: return GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT_ARB; + case SG_PIXELFORMAT_BC6H_RGBUF: return GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_ARB; + case SG_PIXELFORMAT_BC7_RGBA: return GL_COMPRESSED_RGBA_BPTC_UNORM_ARB; + case SG_PIXELFORMAT_BC7_SRGBA: return GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM_ARB; + case SG_PIXELFORMAT_ETC2_RGB8: return GL_COMPRESSED_RGB8_ETC2; + case SG_PIXELFORMAT_ETC2_SRGB8: return GL_COMPRESSED_SRGB8_ETC2; + case SG_PIXELFORMAT_ETC2_RGB8A1: return GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2; + case SG_PIXELFORMAT_ETC2_RGBA8: return GL_COMPRESSED_RGBA8_ETC2_EAC; + case SG_PIXELFORMAT_ETC2_SRGB8A8: return GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC; + case SG_PIXELFORMAT_EAC_R11: return GL_COMPRESSED_R11_EAC; + case SG_PIXELFORMAT_EAC_R11SN: return GL_COMPRESSED_SIGNED_R11_EAC; + case SG_PIXELFORMAT_EAC_RG11: return GL_COMPRESSED_RG11_EAC; + case SG_PIXELFORMAT_EAC_RG11SN: return GL_COMPRESSED_SIGNED_RG11_EAC; + case SG_PIXELFORMAT_ASTC_4x4_RGBA: return GL_COMPRESSED_RGBA_ASTC_4x4_KHR; + case SG_PIXELFORMAT_ASTC_4x4_SRGBA: return GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR; + default: SOKOL_UNREACHABLE; return 0; + } +} + +_SOKOL_PRIVATE GLenum _sg_gl_cubeface_target(int face_index) { + switch (face_index) { + case 0: return GL_TEXTURE_CUBE_MAP_POSITIVE_X; + case 1: return GL_TEXTURE_CUBE_MAP_NEGATIVE_X; + case 2: return GL_TEXTURE_CUBE_MAP_POSITIVE_Y; + case 3: return GL_TEXTURE_CUBE_MAP_NEGATIVE_Y; + case 4: return GL_TEXTURE_CUBE_MAP_POSITIVE_Z; + case 5: return GL_TEXTURE_CUBE_MAP_NEGATIVE_Z; + default: SOKOL_UNREACHABLE; return 0; + } +} + +// see: https://www.khronos.org/registry/OpenGL-Refpages/es3.0/html/glTexImage2D.xhtml +_SOKOL_PRIVATE void _sg_gl_init_pixelformats(bool has_bgra) { + _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_R8]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_R8SN]); + _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_R8UI]); + _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_R8SI]); + #if !defined(SOKOL_GLES3) + _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_R16]); + _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_R16SN]); + #endif + _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_R16UI]); + _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_R16SI]); + _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RG8]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_RG8SN]); + _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RG8UI]); + _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RG8SI]); + _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_R32UI]); + _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_R32SI]); + #if !defined(SOKOL_GLES3) + _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RG16]); + _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RG16SN]); + #endif + _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RG16UI]); + _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RG16SI]); + _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RGBA8]); + _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_SRGB8A8]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_RGBA8SN]); + _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RGBA8UI]); + _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RGBA8SI]); + if (has_bgra) { + _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_BGRA8]); + } + _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RGB10A2]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_RGB9E5]); + _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RG32UI]); + _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RG32SI]); + #if !defined(SOKOL_GLES3) + _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RGBA16]); + _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RGBA16SN]); + #endif + _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RGBA16UI]); + _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RGBA16SI]); + _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RGBA32UI]); + _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RGBA32SI]); + _sg_pixelformat_srmd(&_sg.formats[SG_PIXELFORMAT_DEPTH]); + _sg_pixelformat_srmd(&_sg.formats[SG_PIXELFORMAT_DEPTH_STENCIL]); +} + +// FIXME: OES_half_float_blend +_SOKOL_PRIVATE void _sg_gl_init_pixelformats_half_float(bool has_colorbuffer_half_float) { + if (has_colorbuffer_half_float) { + _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_R16F]); + _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RG16F]); + _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RGBA16F]); + } else { + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_R16F]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_RG16F]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_RGBA16F]); + } +} + +_SOKOL_PRIVATE void _sg_gl_init_pixelformats_float(bool has_colorbuffer_float, bool has_texture_float_linear, bool has_float_blend) { + if (has_texture_float_linear) { + if (has_colorbuffer_float) { + if (has_float_blend) { + _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_R32F]); + _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RG32F]); + _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RGBA32F]); + } else { + _sg_pixelformat_sfrm(&_sg.formats[SG_PIXELFORMAT_R32F]); + _sg_pixelformat_sfrm(&_sg.formats[SG_PIXELFORMAT_RG32F]); + _sg_pixelformat_sfrm(&_sg.formats[SG_PIXELFORMAT_RGBA32F]); + } + _sg_pixelformat_sfrm(&_sg.formats[SG_PIXELFORMAT_RG11B10F]); + } else { + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_R32F]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_RG32F]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_RGBA32F]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_RG11B10F]); + } + } else { + if (has_colorbuffer_float) { + _sg_pixelformat_sbrm(&_sg.formats[SG_PIXELFORMAT_R32F]); + _sg_pixelformat_sbrm(&_sg.formats[SG_PIXELFORMAT_RG32F]); + _sg_pixelformat_sbrm(&_sg.formats[SG_PIXELFORMAT_RGBA32F]); + _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RG11B10F]); + } else { + _sg_pixelformat_s(&_sg.formats[SG_PIXELFORMAT_R32F]); + _sg_pixelformat_s(&_sg.formats[SG_PIXELFORMAT_RG32F]); + _sg_pixelformat_s(&_sg.formats[SG_PIXELFORMAT_RGBA32F]); + _sg_pixelformat_s(&_sg.formats[SG_PIXELFORMAT_RG11B10F]); + } + } +} + +_SOKOL_PRIVATE void _sg_gl_init_pixelformats_s3tc(void) { + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC1_RGBA]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC2_RGBA]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC3_RGBA]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC3_SRGBA]); +} + +_SOKOL_PRIVATE void _sg_gl_init_pixelformats_rgtc(void) { + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC4_R]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC4_RSN]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC5_RG]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC5_RGSN]); +} + +_SOKOL_PRIVATE void _sg_gl_init_pixelformats_bptc(void) { + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC6H_RGBF]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC6H_RGBUF]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC7_RGBA]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC7_SRGBA]); +} + +_SOKOL_PRIVATE void _sg_gl_init_pixelformats_etc2(void) { + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_ETC2_RGB8]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_ETC2_SRGB8]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_ETC2_RGB8A1]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_ETC2_RGBA8]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_ETC2_SRGB8A8]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_EAC_R11]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_EAC_R11SN]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_EAC_RG11]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_EAC_RG11SN]); +} + +_SOKOL_PRIVATE void _sg_gl_init_pixelformats_astc(void) { + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_ASTC_4x4_RGBA]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_ASTC_4x4_SRGBA]); +} + +_SOKOL_PRIVATE void _sg_gl_init_pixelformats_compute(void) { + // using Vulkan's conservative default caps (see: https://github.com/gpuweb/gpuweb/issues/513) + _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_RGBA8]); + _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_RGBA8SN]); + _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_RGBA8UI]); + _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_RGBA8SI]); + _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_RGBA16UI]); + _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_RGBA16SI]); + _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_RGBA16F]); + _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_R32UI]); + _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_R32SI]); + _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_R32F]); + _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_RG32UI]); + _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_RG32SI]); + _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_RG32F]); + _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_RGBA32UI]); + _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_RGBA32SI]); + _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_RGBA32F]); +} + +_SOKOL_PRIVATE void _sg_gl_init_limits(void) { + _SG_GL_CHECK_ERROR(); + + GLint gl_int; + glGetIntegerv(GL_MAX_TEXTURE_SIZE, &gl_int); + _SG_GL_CHECK_ERROR(); + + _sg.limits.max_image_size_2d = gl_int; + _sg.limits.max_image_size_array = gl_int; + + glGetIntegerv(GL_MAX_CUBE_MAP_TEXTURE_SIZE, &gl_int); + _SG_GL_CHECK_ERROR(); + _sg.limits.max_image_size_cube = gl_int; + + glGetIntegerv(GL_MAX_3D_TEXTURE_SIZE, &gl_int); + _SG_GL_CHECK_ERROR(); + _sg.limits.max_image_size_3d = gl_int; + + glGetIntegerv(GL_MAX_ARRAY_TEXTURE_LAYERS, &gl_int); + _SG_GL_CHECK_ERROR(); + _sg.limits.max_image_array_layers = gl_int; + + glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &gl_int); + _SG_GL_CHECK_ERROR(); + _sg.limits.max_vertex_attrs = _sg_min(gl_int, SG_MAX_VERTEX_ATTRIBUTES); + + glGetIntegerv(GL_MAX_DRAW_BUFFERS, &gl_int); + _SG_GL_CHECK_ERROR(); + _sg.limits.max_color_attachments = _sg_min(gl_int, SG_MAX_COLOR_ATTACHMENTS); + + glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &gl_int); + _SG_GL_CHECK_ERROR(); + _sg.limits.max_texture_bindings_per_stage = _sg_min(gl_int, SG_MAX_VIEW_BINDSLOTS); + + #if defined(_SOKOL_GL_HAS_COMPUTE) + if (_sg.features.compute) { + glGetIntegerv(GL_MAX_SHADER_STORAGE_BUFFER_BINDINGS, &gl_int); + _SG_GL_CHECK_ERROR(); + _sg.limits.max_storage_buffer_bindings_per_stage = _sg_min(gl_int, SG_MAX_VIEW_BINDSLOTS); + + glGetIntegerv(GL_MAX_IMAGE_UNITS, &gl_int); + _SG_GL_CHECK_ERROR(); + _sg.limits.max_storage_image_bindings_per_stage = _sg_min(gl_int, SG_MAX_VIEW_BINDSLOTS); + } + #endif + + glGetIntegerv(GL_MAX_VERTEX_UNIFORM_COMPONENTS, &gl_int); + _SG_GL_CHECK_ERROR(); + _sg.limits.gl_max_vertex_uniform_components = gl_int; + + if (_sg.gl.ext_anisotropic) { + glGetIntegerv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &gl_int); + _SG_GL_CHECK_ERROR(); + _sg.gl.max_anisotropy = gl_int; + } else { + _sg.gl.max_anisotropy = 1; + } + + glGetIntegerv(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, &gl_int); + _SG_GL_CHECK_ERROR(); + _sg.limits.gl_max_combined_texture_image_units = gl_int; +} + +#if defined(SOKOL_GLCORE) +_SOKOL_PRIVATE void _sg_gl_init_caps_glcore(void) { + _sg.backend = SG_BACKEND_GLCORE; + + GLint major_version = 0; + GLint minor_version = 0; + glGetIntegerv(GL_MAJOR_VERSION, &major_version); + glGetIntegerv(GL_MINOR_VERSION, &minor_version); + const int version = major_version * 100 + minor_version * 10; + _sg.features.origin_top_left = false; + _sg.features.image_clamp_to_border = true; + _sg.features.mrt_independent_blend_state = false; + _sg.features.mrt_independent_write_mask = true; + _sg.features.compute = version >= 430; + _sg.features.gl_texture_views = version >= 430; + #if defined(__APPLE__) + _sg.features.msaa_texture_bindings = false; + #else + _sg.features.msaa_texture_bindings = true; + #endif + _sg.features.draw_base_vertex = version >= 320; + _sg.features.draw_base_instance = version >= 420; + + // scan extensions + bool has_s3tc = false; // BC1..BC3 + bool has_rgtc = false; // BC4 and BC5 + bool has_bptc = false; // BC6H and BC7 + bool has_etc2 = false; + bool has_astc = false; + GLint num_ext = 0; + glGetIntegerv(GL_NUM_EXTENSIONS, &num_ext); + for (int i = 0; i < num_ext; i++) { + const char* ext = (const char*) glGetStringi(GL_EXTENSIONS, (GLuint)i); + if (ext) { + if (strstr(ext, "_texture_compression_s3tc")) { + has_s3tc = true; + } else if (strstr(ext, "_texture_compression_rgtc")) { + has_rgtc = true; + } else if (strstr(ext, "_texture_compression_bptc")) { + has_bptc = true; + } else if (strstr(ext, "_ES3_compatibility")) { + has_etc2 = true; + } else if (strstr(ext, "_texture_filter_anisotropic")) { + _sg.gl.ext_anisotropic = true; + } else if (strstr(ext, "_texture_compression_astc_ldr")) { + has_astc = true; + } + } + } + + // limits + _sg_gl_init_limits(); + + // pixel formats + const bool has_bgra = false; // not a bug + const bool has_colorbuffer_float = true; + const bool has_colorbuffer_half_float = true; + const bool has_texture_float_linear = true; // FIXME??? + const bool has_float_blend = true; + _sg_gl_init_pixelformats(has_bgra); + _sg_gl_init_pixelformats_float(has_colorbuffer_float, has_texture_float_linear, has_float_blend); + _sg_gl_init_pixelformats_half_float(has_colorbuffer_half_float); + if (has_s3tc) { + _sg_gl_init_pixelformats_s3tc(); + } + if (has_rgtc) { + _sg_gl_init_pixelformats_rgtc(); + } + if (has_bptc) { + _sg_gl_init_pixelformats_bptc(); + } + if (has_etc2) { + _sg_gl_init_pixelformats_etc2(); + } + if (has_astc) { + _sg_gl_init_pixelformats_astc(); + } + if (_sg.features.compute) { + _sg_gl_init_pixelformats_compute(); + } +} +#endif + +#if defined(SOKOL_GLES3) +_SOKOL_PRIVATE void _sg_gl_init_caps_gles3(void) { + _sg.backend = SG_BACKEND_GLES3; + + GLint major_version = 0; + GLint minor_version = 0; + glGetIntegerv(GL_MAJOR_VERSION, &major_version); + glGetIntegerv(GL_MINOR_VERSION, &minor_version); + const int version = major_version * 100 + minor_version * 10; + _sg.features.origin_top_left = false; + _sg.features.image_clamp_to_border = false; + _sg.features.mrt_independent_blend_state = false; + _sg.features.mrt_independent_write_mask = false; + _sg.features.compute = version >= 310; + _sg.features.msaa_texture_bindings = false; + _sg.features.gl_texture_views = version >= 430; + #if defined(__EMSCRIPTEN__) + _sg.features.separate_buffer_types = true; + #else + _sg.features.separate_buffer_types = false; + #endif + _sg.features.draw_base_vertex = version >= 320; + _sg.features.draw_base_instance = false; + + bool has_s3tc = false; // BC1..BC3 + bool has_rgtc = false; // BC4 and BC5 + bool has_bptc = false; // BC6H and BC7 + #if defined(__EMSCRIPTEN__) + bool has_etc2 = false; + #else + bool has_etc2 = true; + #endif + bool has_astc = false; + bool has_colorbuffer_float = false; + bool has_colorbuffer_half_float = false; + bool has_texture_float_linear = false; + bool has_float_blend = false; + GLint num_ext = 0; + glGetIntegerv(GL_NUM_EXTENSIONS, &num_ext); + for (int i = 0; i < num_ext; i++) { + const char* ext = (const char*) glGetStringi(GL_EXTENSIONS, (GLuint)i); + if (ext) { + if (strstr(ext, "_texture_compression_s3tc")) { + has_s3tc = true; + } else if (strstr(ext, "_compressed_texture_s3tc")) { + has_s3tc = true; + } else if (strstr(ext, "_texture_compression_rgtc")) { + has_rgtc = true; + } else if (strstr(ext, "_texture_compression_bptc")) { + has_bptc = true; + } else if (strstr(ext, "_compressed_texture_etc")) { + has_etc2 = true; + } else if (strstr(ext, "_compressed_texture_astc")) { + has_astc = true; + } else if (strstr(ext, "_color_buffer_float")) { + has_colorbuffer_float = true; + } else if (strstr(ext, "_color_buffer_half_float")) { + has_colorbuffer_half_float = true; + } else if (strstr(ext, "_texture_float_linear")) { + has_texture_float_linear = true; + } else if (strstr(ext, "_float_blend")) { + has_float_blend = true; + } else if (strstr(ext, "_texture_filter_anisotropic")) { + _sg.gl.ext_anisotropic = true; + } + } + } + + /* on WebGL2, color_buffer_float also includes 16-bit formats + see: https://developer.mozilla.org/en-US/docs/Web/API/EXT_color_buffer_float + */ + #if defined(__EMSCRIPTEN__) + if (!has_colorbuffer_half_float && has_colorbuffer_float) { + has_colorbuffer_half_float = has_colorbuffer_float; + } + #endif + + // limits + _sg_gl_init_limits(); + + // pixel formats + const bool has_bgra = false; // not a bug + _sg_gl_init_pixelformats(has_bgra); + _sg_gl_init_pixelformats_float(has_colorbuffer_float, has_texture_float_linear, has_float_blend); + _sg_gl_init_pixelformats_half_float(has_colorbuffer_half_float); + if (has_s3tc) { + _sg_gl_init_pixelformats_s3tc(); + } + if (has_rgtc) { + _sg_gl_init_pixelformats_rgtc(); + } + if (has_bptc) { + _sg_gl_init_pixelformats_bptc(); + } + if (has_etc2) { + _sg_gl_init_pixelformats_etc2(); + } + if (has_astc) { + _sg_gl_init_pixelformats_astc(); + } + if (_sg.features.compute) { + _sg_gl_init_pixelformats_compute(); + } +} +#endif + +//-- state cache implementation ------------------------------------------------ +_SOKOL_PRIVATE void _sg_gl_cache_clear_buffer_bindings(bool force) { + if (force || (_sg.gl.cache.vertex_buffer != 0)) { + glBindBuffer(GL_ARRAY_BUFFER, 0); + _sg.gl.cache.vertex_buffer = 0; + _sg_stats_add(gl.num_bind_buffer, 1); + } + if (force || (_sg.gl.cache.index_buffer != 0)) { + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + _sg.gl.cache.index_buffer = 0; + _sg_stats_add(gl.num_bind_buffer, 1); + } + if (force || (_sg.gl.cache.storage_buffer != 0)) { + if (_sg.features.compute) { + glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0); + } + _sg.gl.cache.storage_buffer = 0; + _sg_stats_add(gl.num_bind_buffer, 1); + } + for (int i = 0; i < _SG_GL_MAX_SBUF_BINDINGS; i++) { + if (force || (_sg.gl.cache.storage_buffers[i] != 0)) { + if (_sg.features.compute && (i < _sg.limits.max_storage_buffer_bindings_per_stage)) { + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, (GLuint)i, 0); + } + _sg.gl.cache.storage_buffers[i] = 0; + _sg_stats_add(gl.num_bind_buffer, 1); + } + } +} + +_SOKOL_PRIVATE void _sg_gl_cache_bind_buffer(GLenum target, GLuint buffer) { + SOKOL_ASSERT((GL_ARRAY_BUFFER == target) || (GL_ELEMENT_ARRAY_BUFFER == target) || (GL_SHADER_STORAGE_BUFFER == target)); + if (target == GL_ARRAY_BUFFER) { + if (_sg.gl.cache.vertex_buffer != buffer) { + _sg.gl.cache.vertex_buffer = buffer; + glBindBuffer(target, buffer); + _sg_stats_add(gl.num_bind_buffer, 1); + } + } else if (target == GL_ELEMENT_ARRAY_BUFFER) { + if (_sg.gl.cache.index_buffer != buffer) { + _sg.gl.cache.index_buffer = buffer; + glBindBuffer(target, buffer); + _sg_stats_add(gl.num_bind_buffer, 1); + } + } else if (target == GL_SHADER_STORAGE_BUFFER) { + if (_sg.gl.cache.storage_buffer != buffer) { + _sg.gl.cache.storage_buffer = buffer; + if (_sg.features.compute) { + glBindBuffer(target, buffer); + } + _sg_stats_add(gl.num_bind_buffer, 1); + } + } else { + SOKOL_UNREACHABLE; + } +} + +_SOKOL_PRIVATE void _sg_gl_cache_bind_storage_buffer(uint8_t glsl_binding_n, GLuint buffer, int offset, int buf_size) { + SOKOL_ASSERT(glsl_binding_n < _SG_GL_MAX_SBUF_BINDINGS); + SOKOL_ASSERT(offset < buf_size); + const bool buf_neql = _sg.gl.cache.storage_buffers[glsl_binding_n] != buffer; + const bool off_neql = _sg.gl.cache.storage_buffer_offsets[glsl_binding_n] != offset; + if (buf_neql || off_neql) { + _sg.gl.cache.storage_buffers[glsl_binding_n] = buffer; + _sg.gl.cache.storage_buffer_offsets[glsl_binding_n] = offset; + _sg.gl.cache.storage_buffer = buffer; // not a bug + if (_sg.features.compute) { + SOKOL_ASSERT(glsl_binding_n < _sg.limits.max_storage_buffer_bindings_per_stage); + glBindBufferRange(GL_SHADER_STORAGE_BUFFER, glsl_binding_n, buffer, offset, buf_size - offset); + } + _sg_stats_add(gl.num_bind_buffer, 1); + } +} + +_SOKOL_PRIVATE void _sg_gl_cache_store_buffer_binding(GLenum target) { + if (target == GL_ARRAY_BUFFER) { + _sg.gl.cache.stored_vertex_buffer = _sg.gl.cache.vertex_buffer; + } else if (target == GL_ELEMENT_ARRAY_BUFFER) { + _sg.gl.cache.stored_index_buffer = _sg.gl.cache.index_buffer; + } else if (target == GL_SHADER_STORAGE_BUFFER) { + _sg.gl.cache.stored_storage_buffer = _sg.gl.cache.storage_buffer; + } else { + SOKOL_UNREACHABLE; + } +} + +_SOKOL_PRIVATE void _sg_gl_cache_restore_buffer_binding(GLenum target) { + if (target == GL_ARRAY_BUFFER) { + if (_sg.gl.cache.stored_vertex_buffer != 0) { + // we only care about restoring valid ids + _sg_gl_cache_bind_buffer(target, _sg.gl.cache.stored_vertex_buffer); + _sg.gl.cache.stored_vertex_buffer = 0; + } + } else if (target == GL_ELEMENT_ARRAY_BUFFER) { + if (_sg.gl.cache.stored_index_buffer != 0) { + // we only care about restoring valid ids + _sg_gl_cache_bind_buffer(target, _sg.gl.cache.stored_index_buffer); + _sg.gl.cache.stored_index_buffer = 0; + } + } else if (target == GL_SHADER_STORAGE_BUFFER) { + if (_sg.gl.cache.stored_storage_buffer != 0) { + // we only care about restoring valid ids + _sg_gl_cache_bind_buffer(target, _sg.gl.cache.stored_storage_buffer); + _sg.gl.cache.stored_storage_buffer = 0; + } + } else { + SOKOL_UNREACHABLE; + } +} + +// called from _sg_gl_discard_buffer() +_SOKOL_PRIVATE void _sg_gl_cache_invalidate_buffer(GLuint buf) { + if (buf == _sg.gl.cache.vertex_buffer) { + _sg.gl.cache.vertex_buffer = 0; + glBindBuffer(GL_ARRAY_BUFFER, 0); + _sg_stats_add(gl.num_bind_buffer, 1); + } + if (buf == _sg.gl.cache.index_buffer) { + _sg.gl.cache.index_buffer = 0; + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + _sg_stats_add(gl.num_bind_buffer, 1); + } + if (buf == _sg.gl.cache.storage_buffer) { + _sg.gl.cache.storage_buffer = 0; + glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0); + _sg_stats_add(gl.num_bind_buffer, 1); + } + for (int i = 0; i < _SG_GL_MAX_SBUF_BINDINGS; i++) { + if (buf == _sg.gl.cache.storage_buffers[i]) { + _sg.gl.cache.storage_buffers[i] = 0; + _sg.gl.cache.storage_buffer = 0; // not a bug! + if (_sg.features.compute && (i < _sg.limits.max_storage_buffer_bindings_per_stage)) { + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, (GLuint)i, 0); + } + _sg_stats_add(gl.num_bind_buffer, 1); + } + } + if (buf == _sg.gl.cache.stored_vertex_buffer) { + _sg.gl.cache.stored_vertex_buffer = 0; + } + if (buf == _sg.gl.cache.stored_index_buffer) { + _sg.gl.cache.stored_index_buffer = 0; + } + if (buf == _sg.gl.cache.stored_storage_buffer) { + _sg.gl.cache.stored_storage_buffer = 0; + } + for (int i = 0; i < SG_MAX_VERTEX_ATTRIBUTES; i++) { + if (buf == _sg.gl.cache.attrs[i].gl_vbuf) { + _sg.gl.cache.attrs[i].gl_vbuf = 0; + } + } +} + +_SOKOL_PRIVATE void _sg_gl_cache_active_texture(GLenum texture) { + _SG_GL_CHECK_ERROR(); + if (_sg.gl.cache.cur_active_texture != texture) { + _sg.gl.cache.cur_active_texture = texture; + glActiveTexture(texture); + _sg_stats_add(gl.num_active_texture, 1); + } + _SG_GL_CHECK_ERROR(); +} + +_SOKOL_PRIVATE void _sg_gl_cache_clear_texture_sampler_bindings(bool force) { + _SG_GL_CHECK_ERROR(); + for (int i = 0; (i < _SG_GL_MAX_TEX_SMP_BINDINGS) && (i < _sg.limits.gl_max_combined_texture_image_units); i++) { + if (force || (_sg.gl.cache.texture_samplers[i].texture != 0)) { + GLenum gl_texture_unit = (GLenum) (GL_TEXTURE0 + i); + glActiveTexture(gl_texture_unit); + _sg_stats_add(gl.num_active_texture, 1); + glBindTexture(GL_TEXTURE_2D, 0); + glBindTexture(GL_TEXTURE_CUBE_MAP, 0); + glBindTexture(GL_TEXTURE_3D, 0); + glBindTexture(GL_TEXTURE_2D_ARRAY, 0); + _sg_stats_add(gl.num_bind_texture, 4); + glBindSampler((GLuint)i, 0); + _sg_stats_add(gl.num_bind_sampler, 1); + _sg.gl.cache.texture_samplers[i].target = 0; + _sg.gl.cache.texture_samplers[i].texture = 0; + _sg.gl.cache.texture_samplers[i].sampler = 0; + _sg.gl.cache.cur_active_texture = gl_texture_unit; + } + } + _SG_GL_CHECK_ERROR(); +} + +_SOKOL_PRIVATE void _sg_gl_cache_bind_texture_sampler(int8_t gl_tex_slot, GLenum target, GLuint texture, GLuint sampler) { + /* it's valid to call this function with target=0 and/or texture=0 + target=0 will unbind the previous binding, texture=0 will clear + the new binding + */ + SOKOL_ASSERT((gl_tex_slot >= 0) && (gl_tex_slot < _SG_GL_MAX_TEX_SMP_BINDINGS)); + if (gl_tex_slot >= _sg.limits.gl_max_combined_texture_image_units) { + return; + } + _SG_GL_CHECK_ERROR(); + _sg_gl_cache_texture_sampler_bind_slot* slot = &_sg.gl.cache.texture_samplers[gl_tex_slot]; + if ((slot->target != target) || (slot->texture != texture) || (slot->sampler != sampler)) { + _sg_gl_cache_active_texture((GLenum)(GL_TEXTURE0 + gl_tex_slot)); + // if the target has changed, clear the previous binding on that target + if ((target != slot->target) && (slot->target != 0)) { + glBindTexture(slot->target, 0); + _SG_GL_CHECK_ERROR(); + _sg_stats_add(gl.num_bind_texture, 1); + } + // apply new binding (can be 0 to unbind) + if (target != 0) { + glBindTexture(target, texture); + _SG_GL_CHECK_ERROR(); + _sg_stats_add(gl.num_bind_texture, 1); + } + // apply new sampler (can be 0 to unbind) + glBindSampler((GLuint)gl_tex_slot, sampler); + _SG_GL_CHECK_ERROR(); + _sg_stats_add(gl.num_bind_sampler, 1); + + slot->target = target; + slot->texture = texture; + slot->sampler = sampler; + } +} + +_SOKOL_PRIVATE void _sg_gl_cache_store_texture_sampler_binding(int8_t gl_tex_slot) { + SOKOL_ASSERT((gl_tex_slot >= 0) && (gl_tex_slot < _SG_GL_MAX_TEX_SMP_BINDINGS)); + _sg.gl.cache.stored_texture_sampler = _sg.gl.cache.texture_samplers[gl_tex_slot]; +} + +_SOKOL_PRIVATE void _sg_gl_cache_restore_texture_sampler_binding(int8_t gl_tex_slot) { + SOKOL_ASSERT((gl_tex_slot >= 0) && (gl_tex_slot < _SG_GL_MAX_TEX_SMP_BINDINGS)); + _sg_gl_cache_texture_sampler_bind_slot* slot = &_sg.gl.cache.stored_texture_sampler; + if (slot->texture != 0) { + // we only care about restoring valid ids + SOKOL_ASSERT(slot->target != 0); + _sg_gl_cache_bind_texture_sampler(gl_tex_slot, slot->target, slot->texture, slot->sampler); + slot->target = 0; + slot->texture = 0; + slot->sampler = 0; + } +} + +// called from _sg_gl_discard_texture() and _sg_gl_discard_sampler() +_SOKOL_PRIVATE void _sg_gl_cache_invalidate_texture_sampler(GLuint tex, GLuint smp) { + _SG_GL_CHECK_ERROR(); + for (size_t i = 0; i < _SG_GL_MAX_TEX_SMP_BINDINGS; i++) { + _sg_gl_cache_texture_sampler_bind_slot* slot = &_sg.gl.cache.texture_samplers[i]; + if ((0 != slot->target) && ((tex == slot->texture) || (smp == slot->sampler))) { + _sg_gl_cache_active_texture((GLenum)(GL_TEXTURE0 + i)); + glBindTexture(slot->target, 0); + _SG_GL_CHECK_ERROR(); + _sg_stats_add(gl.num_bind_texture, 1); + glBindSampler((GLuint)i, 0); + _SG_GL_CHECK_ERROR(); + _sg_stats_add(gl.num_bind_sampler, 1); + slot->target = 0; + slot->texture = 0; + slot->sampler = 0; + } + } + if ((tex == _sg.gl.cache.stored_texture_sampler.texture) || (smp == _sg.gl.cache.stored_texture_sampler.sampler)) { + _sg.gl.cache.stored_texture_sampler.target = 0; + _sg.gl.cache.stored_texture_sampler.texture = 0; + _sg.gl.cache.stored_texture_sampler.sampler = 0; + } +} + +// called from _sg_gl_discard_shader() +_SOKOL_PRIVATE void _sg_gl_cache_invalidate_program(GLuint prog) { + if (prog == _sg.gl.cache.prog) { + _sg.gl.cache.prog = 0; + glUseProgram(0); + _sg_stats_add(gl.num_use_program, 1); + } +} + +// called from _sg_gl_discard_pipeline() +_SOKOL_PRIVATE void _sg_gl_cache_invalidate_pipeline(_sg_pipeline_t* pip) { + if (_sg_sref_slot_eql(&_sg.gl.cache.cur_pip, &pip->slot)) { + _sg.gl.cache.cur_pip = _sg_sref(0); + } +} + +_SOKOL_PRIVATE void _sg_gl_reset_state_cache(void) { + _SG_GL_CHECK_ERROR(); + glBindVertexArray(_sg.gl.vao); + _SG_GL_CHECK_ERROR(); + _sg_clear(&_sg.gl.cache, sizeof(_sg.gl.cache)); + _sg_gl_cache_clear_buffer_bindings(true); + _SG_GL_CHECK_ERROR(); + _sg_gl_cache_clear_texture_sampler_bindings(true); + _SG_GL_CHECK_ERROR(); + for (int i = 0; i < _sg.limits.max_vertex_attrs; i++) { + _sg_gl_attr_t* attr = &_sg.gl.cache.attrs[i].gl_attr; + attr->vb_index = -1; + attr->divisor = -1; + glDisableVertexAttribArray((GLuint)i); + _SG_GL_CHECK_ERROR(); + _sg_stats_add(gl.num_disable_vertex_attrib_array, 1); + } + _sg.gl.cache.cur_primitive_type = GL_TRIANGLES; + + // shader program + glGetIntegerv(GL_CURRENT_PROGRAM, (GLint*)&_sg.gl.cache.prog); + _SG_GL_CHECK_ERROR(); + + // depth and stencil state + _sg.gl.cache.depth.compare = SG_COMPAREFUNC_ALWAYS; + _sg.gl.cache.stencil.front.compare = SG_COMPAREFUNC_ALWAYS; + _sg.gl.cache.stencil.front.fail_op = SG_STENCILOP_KEEP; + _sg.gl.cache.stencil.front.depth_fail_op = SG_STENCILOP_KEEP; + _sg.gl.cache.stencil.front.pass_op = SG_STENCILOP_KEEP; + _sg.gl.cache.stencil.back.compare = SG_COMPAREFUNC_ALWAYS; + _sg.gl.cache.stencil.back.fail_op = SG_STENCILOP_KEEP; + _sg.gl.cache.stencil.back.depth_fail_op = SG_STENCILOP_KEEP; + _sg.gl.cache.stencil.back.pass_op = SG_STENCILOP_KEEP; + glEnable(GL_DEPTH_TEST); + glDepthFunc(GL_ALWAYS); + glDepthMask(GL_FALSE); + glDisable(GL_STENCIL_TEST); + glStencilFunc(GL_ALWAYS, 0, 0); + glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); + glStencilMask(0); + _sg_stats_add(gl.num_render_state, 7); + + // blend state + _sg.gl.cache.blend.src_factor_rgb = SG_BLENDFACTOR_ONE; + _sg.gl.cache.blend.dst_factor_rgb = SG_BLENDFACTOR_ZERO; + _sg.gl.cache.blend.op_rgb = SG_BLENDOP_ADD; + _sg.gl.cache.blend.src_factor_alpha = SG_BLENDFACTOR_ONE; + _sg.gl.cache.blend.dst_factor_alpha = SG_BLENDFACTOR_ZERO; + _sg.gl.cache.blend.op_alpha = SG_BLENDOP_ADD; + glDisable(GL_BLEND); + glBlendFuncSeparate(GL_ONE, GL_ZERO, GL_ONE, GL_ZERO); + glBlendEquationSeparate(GL_FUNC_ADD, GL_FUNC_ADD); + glBlendColor(0.0f, 0.0f, 0.0f, 0.0f); + _sg_stats_add(gl.num_render_state, 4); + + // standalone state + for (int i = 0; i < SG_MAX_COLOR_ATTACHMENTS; i++) { + _sg.gl.cache.color_write_mask[i] = SG_COLORMASK_RGBA; + } + _sg.gl.cache.cull_mode = SG_CULLMODE_NONE; + _sg.gl.cache.face_winding = SG_FACEWINDING_CW; + _sg.gl.cache.sample_count = 1; + glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + glPolygonOffset(0.0f, 0.0f); + glDisable(GL_POLYGON_OFFSET_FILL); + glDisable(GL_CULL_FACE); + glFrontFace(GL_CW); + glCullFace(GL_BACK); + glEnable(GL_SCISSOR_TEST); + glDisable(GL_SAMPLE_ALPHA_TO_COVERAGE); + glEnable(GL_DITHER); + glDisable(GL_POLYGON_OFFSET_FILL); + _sg_stats_add(gl.num_render_state, 10); + #if defined(SOKOL_GLCORE) + glEnable(GL_MULTISAMPLE); + glEnable(GL_PROGRAM_POINT_SIZE); + _sg_stats_add(gl.num_render_state, 2); + #endif +} + +_SOKOL_PRIVATE void _sg_gl_setup_backend(const sg_desc* desc) { + _SOKOL_UNUSED(desc); + + // assumes that _sg.gl is already zero-initialized + _sg.gl.valid = true; + + #if defined(_SOKOL_USE_WIN32_GL_LOADER) + _sg_gl_load_opengl(); + #endif + + // clear initial GL error state + #if defined(SOKOL_DEBUG) + while (glGetError() != GL_NO_ERROR); + #endif + #if defined(SOKOL_GLCORE) + _sg_gl_init_caps_glcore(); + #elif defined(SOKOL_GLES3) + _sg_gl_init_caps_gles3(); + #endif + + // create and bind global vertex array object which will be mutated as needed + glGenVertexArrays(1, &_sg.gl.vao); + glBindVertexArray(_sg.gl.vao); + _SG_GL_CHECK_ERROR(); + + // create global framebuffer object which will be mutated as needed + glGenFramebuffers(1, &_sg.gl.fb); + _SG_GL_CHECK_ERROR(); + + // incoming texture data is generally expected to be packed tightly + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + #if defined(SOKOL_GLCORE) + // enable seamless cubemap sampling (only desktop GL) + glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS); + #endif + _sg_gl_reset_state_cache(); +} + +_SOKOL_PRIVATE void _sg_gl_discard_backend(void) { + SOKOL_ASSERT(_sg.gl.valid); + if (_sg.gl.fb) { + glDeleteFramebuffers(1, &_sg.gl.fb); + } + if (_sg.gl.vao) { + glDeleteVertexArrays(1, &_sg.gl.vao); + } + #if defined(_SOKOL_USE_WIN32_GL_LOADER) + _sg_gl_unload_opengl(); + #endif + _sg.gl.valid = false; +} + +//-- GL backend resource creation and destruction ------------------------------ +_SOKOL_PRIVATE sg_resource_state _sg_gl_create_buffer(_sg_buffer_t* buf, const sg_buffer_desc* desc) { + SOKOL_ASSERT(buf && desc); + _SG_GL_CHECK_ERROR(); + buf->gl.injected = (0 != desc->gl_buffers[0]); + const GLenum gl_target = _sg_gl_buffer_target(&buf->cmn.usage); + const GLenum gl_usage = _sg_gl_buffer_usage(&buf->cmn.usage); + for (int slot = 0; slot < buf->cmn.num_slots; slot++) { + GLuint gl_buf = 0; + if (buf->gl.injected) { + SOKOL_ASSERT(desc->gl_buffers[slot]); + gl_buf = desc->gl_buffers[slot]; + } else { + glGenBuffers(1, &gl_buf); + SOKOL_ASSERT(gl_buf); + _sg_gl_cache_store_buffer_binding(gl_target); + _sg_gl_cache_bind_buffer(gl_target, gl_buf); + glBufferData(gl_target, buf->cmn.size, 0, gl_usage); + if (desc->data.ptr) { + glBufferSubData(gl_target, 0, buf->cmn.size, desc->data.ptr); + } + _sg_gl_cache_restore_buffer_binding(gl_target); + } + buf->gl.buf[slot] = gl_buf; + } + _SG_GL_CHECK_ERROR(); + return SG_RESOURCESTATE_VALID; +} + +_SOKOL_PRIVATE void _sg_gl_discard_buffer(_sg_buffer_t* buf) { + SOKOL_ASSERT(buf); + _SG_GL_CHECK_ERROR(); + for (int slot = 0; slot < buf->cmn.num_slots; slot++) { + if (buf->gl.buf[slot]) { + _sg_gl_cache_invalidate_buffer(buf->gl.buf[slot]); + if (!buf->gl.injected) { + glDeleteBuffers(1, &buf->gl.buf[slot]); + } + } + } + _SG_GL_CHECK_ERROR(); +} + +_SOKOL_PRIVATE bool _sg_gl_supported_texture_format(sg_pixel_format fmt) { + const int fmt_index = (int) fmt; + SOKOL_ASSERT((fmt_index > SG_PIXELFORMAT_NONE) && (fmt_index < _SG_PIXELFORMAT_NUM)); + return _sg.formats[fmt_index].sample; +} + +_SOKOL_PRIVATE void _sg_gl_texstorage(const _sg_image_t* img) { + const GLenum tgt = img->gl.target; + const int num_mips = img->cmn.num_mipmaps; + #if defined(_SOKOL_GL_HAS_TEXSTORAGE) + const GLenum ifmt = _sg_gl_teximage_internal_format(img->cmn.pixel_format); + const bool msaa = img->cmn.sample_count > 1; + const int w = img->cmn.width; + const int h = img->cmn.height; + if ((SG_IMAGETYPE_2D == img->cmn.type) || (SG_IMAGETYPE_CUBE == img->cmn.type)) { + #if defined(SOKOL_GLCORE) + if (msaa) { + glTexStorage2DMultisample(tgt, img->cmn.sample_count, ifmt, w, h, GL_TRUE); + } else { + glTexStorage2D(tgt, num_mips, ifmt, w, h); + } + #else + SOKOL_ASSERT(!msaa); _SOKOL_UNUSED(msaa); + glTexStorage2D(tgt, num_mips, ifmt, w, h); + #endif + } else if ((SG_IMAGETYPE_3D == img->cmn.type) || (SG_IMAGETYPE_ARRAY == img->cmn.type)) { + const int depth = img->cmn.num_slices; + #if defined(SOKOL_GLCORE) + if (msaa) { + // NOTE: MSAA works only for array textures, not 3D textures + glTexStorage3DMultisample(tgt, img->cmn.sample_count, ifmt, w, h, depth, GL_TRUE); + } else { + glTexStorage3D(tgt, num_mips, ifmt, w, h, depth); + } + #else + SOKOL_ASSERT(!msaa); _SOKOL_UNUSED(msaa); + glTexStorage3D(tgt, num_mips, ifmt, w, h, depth); + #endif + } + #else + glTexParameteri(tgt, GL_TEXTURE_MAX_LEVEL, num_mips - 1); + #endif + _SG_GL_CHECK_ERROR(); +} + +_SOKOL_PRIVATE void _sg_gl_texsubimage(const _sg_image_t* img, GLenum tgt, int mip_index, int w, int h, int depth, const GLvoid* data_ptr, GLsizei data_size) { + SOKOL_ASSERT(data_ptr && (data_size > 0)); + SOKOL_ASSERT(img->cmn.sample_count == 1); + const bool compressed = _sg_is_compressed_pixel_format(img->cmn.pixel_format); + if ((SG_IMAGETYPE_2D == img->cmn.type) || (SG_IMAGETYPE_CUBE == img->cmn.type)) { + if (compressed) { + const GLenum ifmt = _sg_gl_teximage_internal_format(img->cmn.pixel_format); + glCompressedTexSubImage2D(tgt, mip_index, 0, 0, w, h, ifmt, data_size, data_ptr); + } else { + const GLenum type = _sg_gl_teximage_type(img->cmn.pixel_format); + const GLenum fmt = _sg_gl_teximage_format(img->cmn.pixel_format); + glTexSubImage2D(tgt, mip_index, 0, 0, w, h, fmt, type, data_ptr); + } + } else if ((SG_IMAGETYPE_3D == img->cmn.type) || (SG_IMAGETYPE_ARRAY == img->cmn.type)) { + if (compressed) { + const GLenum ifmt = _sg_gl_teximage_internal_format(img->cmn.pixel_format); + glCompressedTexSubImage3D(tgt, mip_index, 0, 0, 0, w, h, depth, ifmt, data_size, data_ptr); + } else { + const GLenum type = _sg_gl_teximage_type(img->cmn.pixel_format); + const GLenum fmt = _sg_gl_teximage_format(img->cmn.pixel_format); + glTexSubImage3D(tgt, mip_index, 0, 0, 0, w, h, depth, fmt, type, data_ptr); + } + } +} + +_SOKOL_PRIVATE void _sg_gl_teximage(const _sg_image_t* img, GLenum tgt, int mip_index, int w, int h, int depth, const GLvoid* data_ptr, GLsizei data_size) { + #if defined(_SOKOL_GL_HAS_TEXSTORAGE) + if (data_ptr == 0) { + return; + } + _sg_gl_texsubimage(img, tgt, mip_index, w, h, depth, data_ptr, data_size); + #else + const bool compressed = _sg_is_compressed_pixel_format(img->cmn.pixel_format); + const GLenum ifmt = _sg_gl_teximage_internal_format(img->cmn.pixel_format); + const bool msaa = img->cmn.sample_count > 1; + if ((SG_IMAGETYPE_2D == img->cmn.type) || (SG_IMAGETYPE_CUBE == img->cmn.type)) { + if (compressed) { + SOKOL_ASSERT(!msaa); _SOKOL_UNUSED(msaa); + glCompressedTexImage2D(tgt, mip_index, ifmt, w, h, 0, data_size, data_ptr); + } else { + const GLenum type = _sg_gl_teximage_type(img->cmn.pixel_format); + const GLenum fmt = _sg_gl_teximage_format(img->cmn.pixel_format); + #if defined(SOKOL_GLCORE) && !defined(__APPLE__) + if (msaa) { + glTexImage2DMultisample(tgt, img->cmn.sample_count, ifmt, w, h, GL_TRUE); + } else { + glTexImage2D(tgt, mip_index, (GLint)ifmt, w, h, 0, fmt, type, data_ptr); + } + #else + SOKOL_ASSERT(!msaa); _SOKOL_UNUSED(msaa); + glTexImage2D(tgt, mip_index, (GLint)ifmt, w, h, 0, fmt, type, data_ptr); + #endif + } + } else if ((SG_IMAGETYPE_3D == img->cmn.type) || (SG_IMAGETYPE_ARRAY == img->cmn.type)) { + if (compressed) { + SOKOL_ASSERT(!msaa); _SOKOL_UNUSED(msaa); + glCompressedTexImage3D(tgt, mip_index, ifmt, w, h, depth, 0, data_size, data_ptr); + } else { + const GLenum type = _sg_gl_teximage_type(img->cmn.pixel_format); + const GLenum fmt = _sg_gl_teximage_format(img->cmn.pixel_format); + #if defined(SOKOL_GLCORE) && !defined(__APPLE__) + if (msaa) { + // NOTE: MSAA works only for array textures, not 3D textures + glTexImage3DMultisample(tgt, img->cmn.sample_count, ifmt, w, h, depth, GL_TRUE); + } else { + glTexImage3D(tgt, mip_index, (GLint)ifmt, w, h, depth, 0, fmt, type, data_ptr); + } + #else + SOKOL_ASSERT(!msaa); _SOKOL_UNUSED(msaa); + glTexImage3D(tgt, mip_index, (GLint)ifmt, w, h, depth, 0, fmt, type, data_ptr); + #endif + } + } + #endif + _SG_GL_CHECK_ERROR(); +} + +_SOKOL_PRIVATE sg_resource_state _sg_gl_create_image(_sg_image_t* img, const sg_image_desc* desc) { + SOKOL_ASSERT(img && desc); + _SG_GL_CHECK_ERROR(); + img->gl.injected = (0 != desc->gl_textures[0]); + + // check if texture format is support + if (!_sg_gl_supported_texture_format(img->cmn.pixel_format)) { + _SG_ERROR(GL_TEXTURE_FORMAT_NOT_SUPPORTED); + return SG_RESOURCESTATE_FAILED; + } + + if (img->gl.injected) { + img->gl.target = _sg_gl_texture_target(img->cmn.type, img->cmn.sample_count); + // inject externally GL textures + for (int slot = 0; slot < img->cmn.num_slots; slot++) { + SOKOL_ASSERT(desc->gl_textures[slot]); + img->gl.tex[slot] = desc->gl_textures[slot]; + } + if (desc->gl_texture_target) { + img->gl.target = (GLenum)desc->gl_texture_target; + } + } else { + // on platforms that don't support MSAA texture bindings, no actual GL + // texture object is created, instead only attachment view object can be built + const bool msaa = img->cmn.sample_count > 1; + if (msaa && !_sg.features.msaa_texture_bindings) { + if (img->cmn.usage.color_attachment || img->cmn.usage.depth_stencil_attachment) { + return SG_RESOURCESTATE_VALID; + } else { + return SG_RESOURCESTATE_FAILED; + } + } + img->gl.target = _sg_gl_texture_target(img->cmn.type, img->cmn.sample_count); + for (int slot = 0; slot < img->cmn.num_slots; slot++) { + glGenTextures(1, &img->gl.tex[slot]); + SOKOL_ASSERT(img->gl.tex[slot]); + _sg_gl_cache_store_texture_sampler_binding(0); + _sg_gl_cache_bind_texture_sampler(0, img->gl.target, img->gl.tex[slot], 0); + _sg_gl_texstorage(img); + for (int mip_index = 0; mip_index < img->cmn.num_mipmaps; mip_index++) { + const GLvoid* data_ptr = desc->data.mip_levels[mip_index].ptr; + const GLsizei data_size = (GLsizei)desc->data.mip_levels[mip_index].size; + const int mip_width = _sg_miplevel_dim(img->cmn.width, mip_index); + const int mip_height = _sg_miplevel_dim(img->cmn.height, mip_index); + const int mip_depth = (SG_IMAGETYPE_3D == img->cmn.type) ? _sg_miplevel_dim(img->cmn.num_slices, mip_index) : img->cmn.num_slices; + if (SG_IMAGETYPE_CUBE == img->cmn.type) { + const int surf_pitch = _sg_surface_pitch(img->cmn.pixel_format, mip_width, mip_height, 1); + // NOTE: surf_ptr is allowed to be null here + const uint8_t* surf_ptr = (const uint8_t*) data_ptr; + for (int i = 0; i < 6; i++) { + const GLenum gl_img_target = _sg_gl_cubeface_target(i); + _sg_gl_teximage(img, gl_img_target, mip_index, mip_width, mip_height, mip_depth, surf_ptr, surf_pitch); + if (data_ptr) { + SOKOL_ASSERT((6 * surf_pitch) <= data_size); + surf_ptr += surf_pitch; + } + } + } else { + _sg_gl_teximage(img, img->gl.target, mip_index, mip_width, mip_height, mip_depth, data_ptr, data_size); + } + } + _sg_gl_cache_restore_texture_sampler_binding(0); + } + } + _SG_GL_CHECK_ERROR(); + return SG_RESOURCESTATE_VALID; +} + +_SOKOL_PRIVATE void _sg_gl_discard_image(_sg_image_t* img) { + SOKOL_ASSERT(img); + _SG_GL_CHECK_ERROR(); + for (int slot = 0; slot < img->cmn.num_slots; slot++) { + if (img->gl.tex[slot]) { + _sg_gl_cache_invalidate_texture_sampler(img->gl.tex[slot], 0); + if (!img->gl.injected) { + glDeleteTextures(1, &img->gl.tex[slot]); + } + } + } + _SG_GL_CHECK_ERROR(); +} + +_SOKOL_PRIVATE sg_resource_state _sg_gl_create_sampler(_sg_sampler_t* smp, const sg_sampler_desc* desc) { + SOKOL_ASSERT(smp && desc); + _SG_GL_CHECK_ERROR(); + smp->gl.injected = (0 != desc->gl_sampler); + if (smp->gl.injected) { + smp->gl.smp = (GLuint) desc->gl_sampler; + } else { + glGenSamplers(1, &smp->gl.smp); + SOKOL_ASSERT(smp->gl.smp); + + const GLenum gl_min_filter = _sg_gl_min_filter(smp->cmn.min_filter, smp->cmn.mipmap_filter); + const GLenum gl_mag_filter = _sg_gl_mag_filter(smp->cmn.mag_filter); + glSamplerParameteri(smp->gl.smp, GL_TEXTURE_MIN_FILTER, (GLint)gl_min_filter); + glSamplerParameteri(smp->gl.smp, GL_TEXTURE_MAG_FILTER, (GLint)gl_mag_filter); + // GL spec has strange defaults for mipmap min/max lod: -1000 to +1000 + const float min_lod = _sg_clamp(desc->min_lod, 0.0f, 1000.0f); + const float max_lod = _sg_clamp(desc->max_lod, 0.0f, 1000.0f); + glSamplerParameterf(smp->gl.smp, GL_TEXTURE_MIN_LOD, min_lod); + glSamplerParameterf(smp->gl.smp, GL_TEXTURE_MAX_LOD, max_lod); + glSamplerParameteri(smp->gl.smp, GL_TEXTURE_WRAP_S, (GLint)_sg_gl_wrap(smp->cmn.wrap_u)); + glSamplerParameteri(smp->gl.smp, GL_TEXTURE_WRAP_T, (GLint)_sg_gl_wrap(smp->cmn.wrap_v)); + glSamplerParameteri(smp->gl.smp, GL_TEXTURE_WRAP_R, (GLint)_sg_gl_wrap(smp->cmn.wrap_w)); + #if defined(SOKOL_GLCORE) + float border[4]; + switch (smp->cmn.border_color) { + case SG_BORDERCOLOR_TRANSPARENT_BLACK: + border[0] = 0.0f; border[1] = 0.0f; border[2] = 0.0f; border[3] = 0.0f; + break; + case SG_BORDERCOLOR_OPAQUE_WHITE: + border[0] = 1.0f; border[1] = 1.0f; border[2] = 1.0f; border[3] = 1.0f; + break; + default: + border[0] = 0.0f; border[1] = 0.0f; border[2] = 0.0f; border[3] = 1.0f; + break; + } + glSamplerParameterfv(smp->gl.smp, GL_TEXTURE_BORDER_COLOR, border); + #endif + if (smp->cmn.compare != SG_COMPAREFUNC_NEVER) { + glSamplerParameteri(smp->gl.smp, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE); + glSamplerParameteri(smp->gl.smp, GL_TEXTURE_COMPARE_FUNC, (GLint)_sg_gl_compare_func(smp->cmn.compare)); + } else { + glSamplerParameteri(smp->gl.smp, GL_TEXTURE_COMPARE_MODE, GL_NONE); + } + if (_sg.gl.ext_anisotropic && (smp->cmn.max_anisotropy > 1)) { + GLint max_aniso = (GLint) smp->cmn.max_anisotropy; + if (max_aniso > _sg.gl.max_anisotropy) { + max_aniso = _sg.gl.max_anisotropy; + } + glSamplerParameteri(smp->gl.smp, GL_TEXTURE_MAX_ANISOTROPY_EXT, max_aniso); + } + } + _SG_GL_CHECK_ERROR(); + return SG_RESOURCESTATE_VALID; +} + +_SOKOL_PRIVATE void _sg_gl_discard_sampler(_sg_sampler_t* smp) { + SOKOL_ASSERT(smp); + _SG_GL_CHECK_ERROR(); + _sg_gl_cache_invalidate_texture_sampler(0, smp->gl.smp); + if (!smp->gl.injected) { + glDeleteSamplers(1, &smp->gl.smp); + } + _SG_GL_CHECK_ERROR(); +} + +_SOKOL_PRIVATE GLuint _sg_gl_compile_shader(sg_shader_stage stage, const char* src) { + SOKOL_ASSERT(src); + _SG_GL_CHECK_ERROR(); + GLuint gl_shd = glCreateShader(_sg_gl_shader_stage(stage)); + glShaderSource(gl_shd, 1, &src, 0); + glCompileShader(gl_shd); + GLint compile_status = 0; + glGetShaderiv(gl_shd, GL_COMPILE_STATUS, &compile_status); + if (!compile_status) { + // compilation failed, log error and delete shader + GLint log_len = 0; + glGetShaderiv(gl_shd, GL_INFO_LOG_LENGTH, &log_len); + if (log_len > 0) { + GLchar* log_buf = (GLchar*) _sg_malloc((size_t)log_len); + glGetShaderInfoLog(gl_shd, log_len, &log_len, log_buf); + _SG_ERROR(GL_SHADER_COMPILATION_FAILED); + _SG_LOGMSG(GL_SHADER_COMPILATION_FAILED, log_buf); + _sg_free(log_buf); + } + glDeleteShader(gl_shd); + gl_shd = 0; + } + _SG_GL_CHECK_ERROR(); + return gl_shd; +} + +// NOTE: this is an out-of-range check for GLSL bindslots that's also active in release mode +_SOKOL_PRIVATE bool _sg_gl_ensure_glsl_bindslot_ranges(const sg_shader_desc* desc) { + SOKOL_ASSERT(desc); _SOKOL_UNUSED(desc); + #if defined(_SOKOL_GL_HAS_COMPUTE) + SOKOL_ASSERT(_sg.limits.max_storage_buffer_bindings_per_stage <= _SG_GL_MAX_SBUF_BINDINGS); + SOKOL_ASSERT(_sg.limits.max_storage_image_bindings_per_stage <= _SG_GL_MAX_SIMG_BINDINGS); + for (size_t i = 0; i < SG_MAX_VIEW_BINDSLOTS; i++) { + const sg_shader_view* view = &desc->views[i]; + if (view->storage_buffer.stage != SG_SHADERSTAGE_NONE) { + if (view->storage_buffer.glsl_binding_n >= _sg.limits.max_storage_buffer_bindings_per_stage) { + _SG_ERROR(GL_STORAGEBUFFER_GLSL_BINDING_OUT_OF_RANGE); + return false; + } + } + if (view->storage_image.stage != SG_SHADERSTAGE_NONE) { + if (view->storage_image.glsl_binding_n >= _sg.limits.max_storage_image_bindings_per_stage) { + _SG_ERROR(GL_STORAGEIMAGE_GLSL_BINDING_OUT_OF_RANGE); + return false; + } + } + } + #endif + return true; +} + +_SOKOL_PRIVATE sg_resource_state _sg_gl_create_shader(_sg_shader_t* shd, const sg_shader_desc* desc) { + SOKOL_ASSERT(shd && desc); + SOKOL_ASSERT(!shd->gl.prog); + _SG_GL_CHECK_ERROR(); + + // perform a fatal range-check on GLSL bindslots that's also active + // in release mode to avoid potential out-of-bounds array accesses + if (!_sg_gl_ensure_glsl_bindslot_ranges(desc)) { + return SG_RESOURCESTATE_FAILED; + } + + // copy the optional vertex attribute names over + for (int i = 0; i < SG_MAX_VERTEX_ATTRIBUTES; i++) { + _sg_strcpy(&shd->gl.attrs[i].name, desc->attrs[i].glsl_name); + } + + const bool has_vs = desc->vertex_func.source; + const bool has_fs = desc->fragment_func.source; + const bool has_cs = desc->compute_func.source; + SOKOL_ASSERT((has_vs && has_fs) || has_cs); + GLuint gl_prog = glCreateProgram(); + if (has_vs && has_fs) { + GLuint gl_vs = _sg_gl_compile_shader(SG_SHADERSTAGE_VERTEX, desc->vertex_func.source); + GLuint gl_fs = _sg_gl_compile_shader(SG_SHADERSTAGE_FRAGMENT, desc->fragment_func.source); + if (!(gl_vs && gl_fs)) { + glDeleteProgram(gl_prog); + if (gl_vs) { glDeleteShader(gl_vs); } + if (gl_fs) { glDeleteShader(gl_fs); } + return SG_RESOURCESTATE_FAILED; + } + glAttachShader(gl_prog, gl_vs); + glAttachShader(gl_prog, gl_fs); + glLinkProgram(gl_prog); + glDeleteShader(gl_vs); + glDeleteShader(gl_fs); + _SG_GL_CHECK_ERROR(); + } else if (has_cs) { + GLuint gl_cs = _sg_gl_compile_shader(SG_SHADERSTAGE_COMPUTE, desc->compute_func.source); + if (!gl_cs) { + glDeleteProgram(gl_prog); + return SG_RESOURCESTATE_FAILED; + } + glAttachShader(gl_prog, gl_cs); + glLinkProgram(gl_prog); + glDeleteShader(gl_cs); + _SG_GL_CHECK_ERROR(); + } else { + SOKOL_UNREACHABLE; + } + GLint link_status; + glGetProgramiv(gl_prog, GL_LINK_STATUS, &link_status); + if (!link_status) { + GLint log_len = 0; + glGetProgramiv(gl_prog, GL_INFO_LOG_LENGTH, &log_len); + if (log_len > 0) { + GLchar* log_buf = (GLchar*) _sg_malloc((size_t)log_len); + glGetProgramInfoLog(gl_prog, log_len, &log_len, log_buf); + _SG_ERROR(GL_SHADER_LINKING_FAILED); + _SG_LOGMSG(GL_SHADER_LINKING_FAILED, log_buf); + _sg_free(log_buf); + } + glDeleteProgram(gl_prog); + return SG_RESOURCESTATE_FAILED; + } + shd->gl.prog = gl_prog; + + // resolve uniforms + _SG_GL_CHECK_ERROR(); + for (size_t ub_index = 0; ub_index < SG_MAX_UNIFORMBLOCK_BINDSLOTS; ub_index++) { + const sg_shader_uniform_block* ub_desc = &desc->uniform_blocks[ub_index]; + if (ub_desc->stage == SG_SHADERSTAGE_NONE) { + continue; + } + SOKOL_ASSERT(ub_desc->size > 0); + _sg_gl_uniform_block_t* ub = &shd->gl.uniform_blocks[ub_index]; + SOKOL_ASSERT(ub->num_uniforms == 0); + uint32_t cur_uniform_offset = 0; + for (int u_index = 0; u_index < SG_MAX_UNIFORMBLOCK_MEMBERS; u_index++) { + const sg_glsl_shader_uniform* u_desc = &ub_desc->glsl_uniforms[u_index]; + if (u_desc->type == SG_UNIFORMTYPE_INVALID) { + break; + } + const uint32_t u_align = _sg_uniform_alignment(u_desc->type, u_desc->array_count, ub_desc->layout); + const uint32_t u_size = _sg_uniform_size(u_desc->type, u_desc->array_count, ub_desc->layout); + cur_uniform_offset = _sg_align_u32(cur_uniform_offset, u_align); + _sg_gl_uniform_t* u = &ub->uniforms[u_index]; + u->type = u_desc->type; + u->count = (uint16_t) u_desc->array_count; + u->offset = (uint16_t) cur_uniform_offset; + SOKOL_ASSERT(u_desc->glsl_name); + u->gl_loc = glGetUniformLocation(gl_prog, u_desc->glsl_name); + if (u->gl_loc == -1) { + _SG_WARN(GL_UNIFORMBLOCK_NAME_NOT_FOUND_IN_SHADER); + _SG_LOGMSG(GL_UNIFORMBLOCK_NAME_NOT_FOUND_IN_SHADER, u_desc->glsl_name); + } + cur_uniform_offset += u_size; + ub->num_uniforms++; + } + if (ub_desc->layout == SG_UNIFORMLAYOUT_STD140) { + cur_uniform_offset = _sg_align_u32(cur_uniform_offset, 16); + } + SOKOL_ASSERT(ub_desc->size == (size_t)cur_uniform_offset); + _SOKOL_UNUSED(cur_uniform_offset); + } + + // copy resource bindslot mappings + for (size_t i = 0; i < SG_MAX_VIEW_BINDSLOTS; i++) { + const sg_shader_view* view = &desc->views[i]; + SOKOL_ASSERT(0 == shd->gl.sbuf_binding[i]); + SOKOL_ASSERT(0 == shd->gl.simg_binding[i]); + if (view->storage_buffer.stage != SG_SHADERSTAGE_NONE) { + shd->gl.sbuf_binding[i] = view->storage_buffer.glsl_binding_n; + } else if (view->storage_image.stage != SG_SHADERSTAGE_NONE) { + shd->gl.simg_binding[i] = view->storage_buffer.glsl_binding_n; + } + } + + // record image sampler location in shader program + _SG_GL_CHECK_ERROR(); + GLuint cur_prog = 0; + glGetIntegerv(GL_CURRENT_PROGRAM, (GLint*)&cur_prog); + glUseProgram(gl_prog); + GLint gl_tex_slot = 0; + for (size_t tex_smp_index = 0; tex_smp_index < SG_MAX_TEXTURE_SAMPLER_PAIRS; tex_smp_index++) { + const sg_shader_texture_sampler_pair* tex_smp_desc = &desc->texture_sampler_pairs[tex_smp_index]; + if (tex_smp_desc->stage == SG_SHADERSTAGE_NONE) { + continue; + } + SOKOL_ASSERT(tex_smp_desc->glsl_name); + GLint gl_loc = glGetUniformLocation(gl_prog, tex_smp_desc->glsl_name); + if (gl_loc != -1) { + glUniform1i(gl_loc, gl_tex_slot); + shd->gl.tex_slot[tex_smp_index] = (int8_t)gl_tex_slot++; + } else { + shd->gl.tex_slot[tex_smp_index] = -1; + _SG_WARN(GL_IMAGE_SAMPLER_NAME_NOT_FOUND_IN_SHADER); + _SG_LOGMSG(GL_IMAGE_SAMPLER_NAME_NOT_FOUND_IN_SHADER, tex_smp_desc->glsl_name); + } + } + + // it's legal to call glUseProgram with 0 + glUseProgram(cur_prog); + _SG_GL_CHECK_ERROR(); + return SG_RESOURCESTATE_VALID; +} + +_SOKOL_PRIVATE void _sg_gl_discard_shader(_sg_shader_t* shd) { + SOKOL_ASSERT(shd); + _SG_GL_CHECK_ERROR(); + if (shd->gl.prog) { + _sg_gl_cache_invalidate_program(shd->gl.prog); + glDeleteProgram(shd->gl.prog); + } + _SG_GL_CHECK_ERROR(); +} + +_SOKOL_PRIVATE sg_resource_state _sg_gl_create_pipeline(_sg_pipeline_t* pip, const sg_pipeline_desc* desc) { + SOKOL_ASSERT(pip && desc); + SOKOL_ASSERT(_sg.limits.max_vertex_attrs <= SG_MAX_VERTEX_ATTRIBUTES); + if (pip->cmn.is_compute) { + // shortcut for compute pipelines + return SG_RESOURCESTATE_VALID; + } + pip->gl.primitive_type = desc->primitive_type; + pip->gl.depth = desc->depth; + pip->gl.stencil = desc->stencil; + // FIXME: blend color and write mask per draw-buffer-attachment (requires GL4) + pip->gl.blend = desc->colors[0].blend; + for (int i = 0; i < SG_MAX_COLOR_ATTACHMENTS; i++) { + pip->gl.color_write_mask[i] = desc->colors[i].write_mask; + } + pip->gl.cull_mode = desc->cull_mode; + pip->gl.face_winding = desc->face_winding; + pip->gl.sample_count = desc->sample_count; + pip->gl.alpha_to_coverage_enabled = desc->alpha_to_coverage_enabled; + + // NOTE: GLSL compilers may remove unused vertex attributes so we can't rely + // on the 'prepopulated' vertex_buffer_layout_active[] state and need to + // fill this array from scratch with the actual info after GLSL compilation + for (int i = 0; i < SG_MAX_VERTEXBUFFER_BINDSLOTS; i++) { + pip->cmn.vertex_buffer_layout_active[i] = false; + } + + // resolve vertex attributes + const _sg_shader_t* shd = _sg_shader_ref_ptr(&pip->cmn.shader); + SOKOL_ASSERT(shd->gl.prog); + for (int attr_index = 0; attr_index < SG_MAX_VERTEX_ATTRIBUTES; attr_index++) { + pip->gl.attrs[attr_index].vb_index = -1; + } + for (int attr_index = 0; attr_index < _sg.limits.max_vertex_attrs; attr_index++) { + const sg_vertex_attr_state* a_state = &desc->layout.attrs[attr_index]; + if (a_state->format == SG_VERTEXFORMAT_INVALID) { + break; + } + SOKOL_ASSERT(a_state->buffer_index < SG_MAX_VERTEXBUFFER_BINDSLOTS); + const sg_vertex_buffer_layout_state* l_state = &desc->layout.buffers[a_state->buffer_index]; + const sg_vertex_step step_func = l_state->step_func; + const int step_rate = l_state->step_rate; + GLint attr_loc = attr_index; + if (!_sg_strempty(&shd->gl.attrs[attr_index].name)) { + attr_loc = glGetAttribLocation(shd->gl.prog, _sg_strptr(&shd->gl.attrs[attr_index].name)); + } + if (attr_loc != -1) { + SOKOL_ASSERT(attr_loc < (GLint)_sg.limits.max_vertex_attrs); + _sg_gl_attr_t* gl_attr = &pip->gl.attrs[attr_loc]; + SOKOL_ASSERT(gl_attr->vb_index == -1); + gl_attr->vb_index = (int8_t) a_state->buffer_index; + if (step_func == SG_VERTEXSTEP_PER_VERTEX) { + gl_attr->divisor = 0; + } else { + gl_attr->divisor = (int8_t) step_rate; + } + SOKOL_ASSERT(l_state->stride > 0); + gl_attr->stride = (uint8_t) l_state->stride; + gl_attr->offset = a_state->offset; + gl_attr->size = (uint8_t) _sg_gl_vertexformat_size(a_state->format); + gl_attr->type = _sg_gl_vertexformat_type(a_state->format); + gl_attr->normalized = _sg_gl_vertexformat_normalized(a_state->format); + gl_attr->base_type = _sg_vertexformat_basetype(a_state->format); + pip->cmn.vertex_buffer_layout_active[a_state->buffer_index] = true; + } else { + _SG_WARN(GL_VERTEX_ATTRIBUTE_NOT_FOUND_IN_SHADER); + _SG_LOGMSG(GL_VERTEX_ATTRIBUTE_NOT_FOUND_IN_SHADER, _sg_strptr(&shd->gl.attrs[attr_index].name)); + } + } + return SG_RESOURCESTATE_VALID; +} + +_SOKOL_PRIVATE void _sg_gl_discard_pipeline(_sg_pipeline_t* pip) { + SOKOL_ASSERT(pip); + _sg_gl_cache_invalidate_pipeline(pip); +} + +_SOKOL_PRIVATE void _sg_gl_fb_attach_texture(const _sg_view_t* view, GLenum gl_att_type) { + const _sg_image_t* img = _sg_image_ref_ptr(&view->cmn.img.ref); + const GLuint gl_tex = img->gl.tex[0]; + SOKOL_ASSERT(gl_tex); + const GLuint gl_target = img->gl.target; + SOKOL_ASSERT(gl_target); + const int mip_level = view->cmn.img.mip_level; + const int slice = view->cmn.img.slice; + switch (img->cmn.type) { + case SG_IMAGETYPE_2D: + glFramebufferTexture2D(GL_FRAMEBUFFER, gl_att_type, gl_target, gl_tex, mip_level); + break; + case SG_IMAGETYPE_CUBE: + glFramebufferTexture2D(GL_FRAMEBUFFER, gl_att_type, _sg_gl_cubeface_target(slice), gl_tex, mip_level); + break; + default: + glFramebufferTextureLayer(GL_FRAMEBUFFER, gl_att_type, gl_tex, mip_level, slice); + break; + } +} + +_SOKOL_PRIVATE GLenum _sg_gl_depth_stencil_attachment_type(const _sg_image_t* ds_img) { + if (_sg_is_depth_stencil_format(ds_img->cmn.pixel_format)) { + return GL_DEPTH_STENCIL_ATTACHMENT; + } else { + return GL_DEPTH_ATTACHMENT; + } +} + +_SOKOL_PRIVATE bool _sg_gl_check_framebuffer_status(void) { + const GLenum fb_status = glCheckFramebufferStatus(GL_FRAMEBUFFER); + if (fb_status != GL_FRAMEBUFFER_COMPLETE) { + switch (fb_status) { + case GL_FRAMEBUFFER_UNDEFINED: + _SG_ERROR(GL_FRAMEBUFFER_STATUS_UNDEFINED); + break; + case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: + _SG_ERROR(GL_FRAMEBUFFER_STATUS_INCOMPLETE_ATTACHMENT); + break; + case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: + _SG_ERROR(GL_FRAMEBUFFER_STATUS_INCOMPLETE_MISSING_ATTACHMENT); + break; + case GL_FRAMEBUFFER_UNSUPPORTED: + _SG_ERROR(GL_FRAMEBUFFER_STATUS_UNSUPPORTED); + break; + case GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE: + _SG_ERROR(GL_FRAMEBUFFER_STATUS_INCOMPLETE_MULTISAMPLE); + break; + default: + _SG_ERROR(GL_FRAMEBUFFER_STATUS_UNKNOWN); + break; + } + return false; + } + return true; +} + +_SOKOL_PRIVATE sg_resource_state _sg_gl_create_view(_sg_view_t* view, const sg_view_desc* desc) { + SOKOL_ASSERT(view && desc); + _SOKOL_UNUSED(desc); + _SG_GL_CHECK_ERROR(); + if ((view->cmn.type == SG_VIEWTYPE_TEXTURE) && (_sg.features.gl_texture_views)) { + #if defined(_SOKOL_GL_HAS_TEXVIEWS) + if (_sg.features.gl_texture_views) { + const _sg_image_t* img = _sg_image_ref_ptr(&view->cmn.img.ref); + for (int slot = 0; slot < img->cmn.num_slots; slot++) { + SOKOL_ASSERT(img->gl.tex[slot] != 0); + const GLuint min_level = (GLuint)view->cmn.img.mip_level; + const GLuint num_levels = (GLuint)view->cmn.img.mip_level_count; + const GLuint min_layer = (GLuint)view->cmn.img.slice; + const GLuint num_layers = (GLuint)view->cmn.img.slice_count; + const GLenum ifmt = _sg_gl_teximage_internal_format(img->cmn.pixel_format); + glGenTextures(1, &view->gl.tex_view[slot]); + glTextureView(view->gl.tex_view[slot], img->gl.target, img->gl.tex[slot], ifmt, min_level, num_levels, min_layer, num_layers); + } + } + #endif + } else if ((view->cmn.type == SG_VIEWTYPE_COLORATTACHMENT) || (view->cmn.type == SG_VIEWTYPE_DEPTHSTENCILATTACHMENT)) { + // create MSAA render buffer if MSAA textures are not supported + const _sg_image_t* img = _sg_image_ref_ptr(&view->cmn.img.ref); + const bool msaa = img->cmn.sample_count > 1; + if (msaa && !_sg.features.msaa_texture_bindings) { + const GLenum gl_internal_format = _sg_gl_teximage_internal_format(img->cmn.pixel_format); + glGenRenderbuffers(1, &view->gl.msaa_render_buffer); + glBindRenderbuffer(GL_RENDERBUFFER, view->gl.msaa_render_buffer); + glRenderbufferStorageMultisample(GL_RENDERBUFFER, img->cmn.sample_count, gl_internal_format, img->cmn.width, img->cmn.height); + } + } else if (view->cmn.type == SG_VIEWTYPE_RESOLVEATTACHMENT) { + // store current framebuffer binding (restored at end of block) + GLuint gl_orig_fb; + glGetIntegerv(GL_FRAMEBUFFER_BINDING, (GLint*)&gl_orig_fb); + + // create MSAA resolve framebuffer + glGenFramebuffers(1, &view->gl.msaa_resolve_frame_buffer); + glBindFramebuffer(GL_FRAMEBUFFER, view->gl.msaa_resolve_frame_buffer); + _sg_gl_fb_attach_texture(view, GL_COLOR_ATTACHMENT0); + if (!_sg_gl_check_framebuffer_status()) { + return SG_RESOURCESTATE_FAILED; + } + // setup color attachments for the framebuffer + static const GLenum gl_draw_buf = GL_COLOR_ATTACHMENT0; + glDrawBuffers(1, &gl_draw_buf); + // bind original framebuffer + glBindFramebuffer(GL_FRAMEBUFFER, gl_orig_fb); + } + _SG_GL_CHECK_ERROR(); + return SG_RESOURCESTATE_VALID; +} + +_SOKOL_PRIVATE void _sg_gl_discard_view(_sg_view_t* view) { + SOKOL_ASSERT(view); + _SG_GL_CHECK_ERROR(); + for (size_t slot = 0; slot < SG_NUM_INFLIGHT_FRAMES; slot++) { + if (0 != view->gl.tex_view[slot]) { + // NOTE: cache invalidation also works as expected without + // GL texture view support, in that case the view's texture object + // will simply remain bound until the sg_image object is discarded + _sg_gl_cache_invalidate_texture_sampler(view->gl.tex_view[slot], 0); + glDeleteTextures(1, &view->gl.tex_view[slot]); + } + } + if (view->gl.msaa_render_buffer) { + glDeleteRenderbuffers(1, &view->gl.msaa_render_buffer); + } + if (view->gl.msaa_resolve_frame_buffer) { + glDeleteFramebuffers(1, &view->gl.msaa_resolve_frame_buffer); + } + _SG_GL_CHECK_ERROR(); +} + +#if defined(_SOKOL_GL_HAS_COMPUTE) +_SOKOL_PRIVATE void _sg_gl_handle_memory_barriers(const _sg_shader_t* shd, const _sg_bindings_ptrs_t* bnd, const _sg_attachments_ptrs_t* atts) { + SOKOL_ASSERT((shd && bnd && atts == 0) || (atts && shd == 0 && bnd == 0)); + if (!_sg.features.compute) { + return; + } + GLbitfield gl_barrier_bits = 0; + + // if vertex-, index- or storage-buffer bindings have been written + // by a compute shader before, a barrier must be issued + if (bnd) { + for (size_t i = 0; i < SG_MAX_VERTEXBUFFER_BINDSLOTS; i++) { + _sg_buffer_t* buf = bnd->vbs[i]; + if (!buf) { + continue; + } + if (buf->gl.gpu_dirty_flags & _SG_GL_GPUDIRTY_VERTEXBUFFER) { + gl_barrier_bits |= GL_VERTEX_ATTRIB_ARRAY_BARRIER_BIT; + buf->gl.gpu_dirty_flags &= (uint8_t)~_SG_GL_GPUDIRTY_VERTEXBUFFER; + } + } + if (bnd->ib) { + _sg_buffer_t* buf = bnd->ib; + if (buf->gl.gpu_dirty_flags & _SG_GL_GPUDIRTY_INDEXBUFFER) { + gl_barrier_bits |= GL_ELEMENT_ARRAY_BARRIER_BIT; + buf->gl.gpu_dirty_flags &= (uint8_t)~_SG_GL_GPUDIRTY_INDEXBUFFER; + } + } + for (size_t i = 0; i < SG_MAX_VIEW_BINDSLOTS; i++) { + const _sg_view_t* view = bnd->views[i]; + if (!view) { + continue; + } + if (view->cmn.type == SG_VIEWTYPE_STORAGEBUFFER) { + _sg_buffer_t* buf = _sg_buffer_ref_ptr(&view->cmn.buf.ref); + if (buf->gl.gpu_dirty_flags & _SG_GL_GPUDIRTY_STORAGEBUFFER) { + gl_barrier_bits |= GL_SHADER_STORAGE_BARRIER_BIT; + buf->gl.gpu_dirty_flags &= (uint8_t)~_SG_GL_GPUDIRTY_STORAGEBUFFER; + } + } else if (view->cmn.type == SG_VIEWTYPE_TEXTURE) { + _sg_image_t* img = _sg_image_ref_ptr(&view->cmn.img.ref); + if (img->gl.gpu_dirty_flags & _SG_GL_GPUDIRTY_TEXTURE) { + gl_barrier_bits |= GL_TEXTURE_FETCH_BARRIER_BIT; + img->gl.gpu_dirty_flags &= (uint8_t)~_SG_GL_GPUDIRTY_TEXTURE; + } + } else if (view->cmn.type == SG_VIEWTYPE_STORAGEIMAGE) { + _sg_image_t* img = _sg_image_ref_ptr(&view->cmn.img.ref); + if (img->gl.gpu_dirty_flags &= _SG_GL_GPUDIRTY_STORAGEIMAGE) { + gl_barrier_bits |= GL_SHADER_IMAGE_ACCESS_BARRIER_BIT; + img->gl.gpu_dirty_flags &= (uint8_t)~_SG_GL_GPUDIRTY_STORAGEIMAGE; + } + } else { + SOKOL_UNREACHABLE; + } + } + } + if (atts) { + for (int i = 0; i < atts->num_color_views; i++) { + const _sg_view_t* view = atts->color_views[i]; + SOKOL_ASSERT(view); + _sg_image_t* img = _sg_image_ref_ptr(&view->cmn.img.ref); + if (img->gl.gpu_dirty_flags & _SG_GL_GPUDIRTY_ATTACHMENT) { + gl_barrier_bits |= GL_FRAMEBUFFER_BARRIER_BIT; + img->gl.gpu_dirty_flags &= (uint8_t)~_SG_GL_GPUDIRTY_ATTACHMENT; + } + } + } + if (0 != gl_barrier_bits) { + glMemoryBarrier(gl_barrier_bits); + _sg_stats_add(gl.num_memory_barriers, 1); + } + + // mark resources as dirty which will be written by compute shaders + // (don't merge this into the above loop, this would mess up the + // dirty flags if the same resource is bound multiple times) + if (bnd) { + for (size_t i = 0; i < SG_MAX_VIEW_BINDSLOTS; i++) { + const _sg_view_t* view = bnd->views[i]; + if (!view) { + continue; + } + if (view->cmn.type == SG_VIEWTYPE_STORAGEBUFFER) { + if (!shd->cmn.views[i].sbuf_readonly) { + _sg_buffer_t* buf = _sg_buffer_ref_ptr(&view->cmn.buf.ref); + buf->gl.gpu_dirty_flags = _SG_GL_GPUDIRTY_BUFFER_ALL; + } + } else if (view->cmn.type == SG_VIEWTYPE_STORAGEIMAGE) { + // NOTE: storage image bindings are always written, otherwise + // they would be texture bindings! + _sg_image_t* img = _sg_image_ref_ptr(&view->cmn.img.ref); + img->gl.gpu_dirty_flags = _SG_GL_GPUDIRTY_IMAGE_ALL; + } + } + } +} +#endif + +_SOKOL_PRIVATE void _sg_gl_begin_pass(const sg_pass* pass, const _sg_attachments_ptrs_t* atts) { + SOKOL_ASSERT(pass && atts); + _SG_GL_CHECK_ERROR(); + + // early out if this a compute pass + if (pass->compute) { + return; + } + + const sg_swapchain* swapchain = &pass->swapchain; + const sg_pass_action* action = &pass->action; + const bool is_swapchain_pass = atts->empty; + const bool is_offscreen_pass = !atts->empty; + + // bind the render pass framebuffer + // + // FIXME: Disabling SRGB conversion for the default framebuffer is + // a crude hack to make behaviour for sRGB render target textures + // identical with the Metal and D3D11 swapchains created by sokol-app. + // + // This will need a cleaner solution (e.g. allowing to configure + // sokol_app.h with an sRGB or RGB framebuffer. + if (is_offscreen_pass) { + + // offscreen pass, mutate the global offscreen framebuffer object + #if defined(SOKOL_GLCORE) + glEnable(GL_FRAMEBUFFER_SRGB); + #endif + glBindFramebuffer(GL_FRAMEBUFFER, _sg.gl.fb); + for (int i = 0; i < atts->num_color_views; i++) { + const _sg_view_t* view = atts->color_views[i]; + const GLenum gl_att_type = (GLenum)(GL_COLOR_ATTACHMENT0 + i); + if (view->gl.msaa_render_buffer) { + glFramebufferRenderbuffer(GL_FRAMEBUFFER, gl_att_type, GL_RENDERBUFFER, view->gl.msaa_render_buffer); + } else { + _sg_gl_fb_attach_texture(view, gl_att_type); + } + } + if (atts->ds_view) { + const _sg_view_t* view = atts->ds_view; + const _sg_image_t* img = _sg_image_ref_ptr(&view->cmn.img.ref); + const GLenum gl_att_type = _sg_gl_depth_stencil_attachment_type(img); + if (view->gl.msaa_render_buffer) { + glFramebufferRenderbuffer(GL_FRAMEBUFFER, gl_att_type, GL_RENDERBUFFER, view->gl.msaa_render_buffer); + } else { + _sg_gl_fb_attach_texture(view, gl_att_type); + } + } + if (!_sg_gl_check_framebuffer_status()) { + _sg.cur_pass.valid = false; + return; + } + static const GLenum gl_draw_bufs[SG_MAX_COLOR_ATTACHMENTS] = { + GL_COLOR_ATTACHMENT0, + GL_COLOR_ATTACHMENT1, + GL_COLOR_ATTACHMENT2, + GL_COLOR_ATTACHMENT3 + }; + glDrawBuffers(atts->num_color_views, gl_draw_bufs); + + #if defined(_SOKOL_GL_HAS_COMPUTE) + _sg_gl_handle_memory_barriers(0, 0, atts); + _SG_GL_CHECK_ERROR(); + #endif + + } else { + // swapchain pass + #if defined(SOKOL_GLCORE) + glDisable(GL_FRAMEBUFFER_SRGB); + #endif + // NOTE: on some platforms, the default framebuffer of a context + // is null, so we can't actually assert here that the + // framebuffer has been provided + glBindFramebuffer(GL_FRAMEBUFFER, swapchain->gl.framebuffer); + } + glViewport(0, 0, _sg.cur_pass.dim.width, _sg.cur_pass.dim.height); + glScissor(0, 0, _sg.cur_pass.dim.width, _sg.cur_pass.dim.height); + + // number of color attachments + const int num_color_atts = is_offscreen_pass ? atts->num_color_views : 1; + + // clear color and depth-stencil attachments if needed + bool clear_any_color = false; + for (int i = 0; i < num_color_atts; i++) { + if (SG_LOADACTION_CLEAR == action->colors[i].load_action) { + clear_any_color = true; + break; + } + } + const bool clear_depth = (action->depth.load_action == SG_LOADACTION_CLEAR); + const bool clear_stencil = (action->stencil.load_action == SG_LOADACTION_CLEAR); + + bool need_pip_cache_flush = false; + if (clear_any_color) { + bool need_color_mask_flush = false; + // NOTE: not a bug to iterate over all possible color attachments + for (int i = 0; i < SG_MAX_COLOR_ATTACHMENTS; i++) { + if (SG_COLORMASK_RGBA != _sg.gl.cache.color_write_mask[i]) { + need_pip_cache_flush = true; + need_color_mask_flush = true; + _sg.gl.cache.color_write_mask[i] = SG_COLORMASK_RGBA; + } + } + if (need_color_mask_flush) { + glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + } + } + if (clear_depth) { + if (!_sg.gl.cache.depth.write_enabled) { + need_pip_cache_flush = true; + _sg.gl.cache.depth.write_enabled = true; + glDepthMask(GL_TRUE); + } + if (_sg.gl.cache.depth.compare != SG_COMPAREFUNC_ALWAYS) { + need_pip_cache_flush = true; + _sg.gl.cache.depth.compare = SG_COMPAREFUNC_ALWAYS; + glDepthFunc(GL_ALWAYS); + } + } + if (clear_stencil) { + if (_sg.gl.cache.stencil.write_mask != 0xFF) { + need_pip_cache_flush = true; + _sg.gl.cache.stencil.write_mask = 0xFF; + glStencilMask(0xFF); + } + } + if (need_pip_cache_flush) { + // we messed with the state cache directly, need to clear cached + // pipeline to force re-evaluation in next sg_apply_pipeline() + _sg.gl.cache.cur_pip = _sg_sref(0); + } + for (int i = 0; i < num_color_atts; i++) { + if (action->colors[i].load_action == SG_LOADACTION_CLEAR) { + glClearBufferfv(GL_COLOR, i, &action->colors[i].clear_value.r); + } + } + if (is_swapchain_pass || atts->ds_view) { + if (clear_depth && clear_stencil) { + glClearBufferfi(GL_DEPTH_STENCIL, 0, action->depth.clear_value, action->stencil.clear_value); + } else if (clear_depth) { + glClearBufferfv(GL_DEPTH, 0, &action->depth.clear_value); + } else if (clear_stencil) { + GLint val = (GLint) action->stencil.clear_value; + glClearBufferiv(GL_STENCIL, 0, &val); + } + } + // keep store actions for end-pass + for (int i = 0; i < SG_MAX_COLOR_ATTACHMENTS; i++) { + _sg.gl.color_store_actions[i] = action->colors[i].store_action; + } + _sg.gl.depth_store_action = action->depth.store_action; + _sg.gl.stencil_store_action = action->stencil.store_action; + + _SG_GL_CHECK_ERROR(); +} + +_SOKOL_PRIVATE void _sg_gl_end_render_pass(const _sg_attachments_ptrs_t* atts) { + SOKOL_ASSERT(atts); + if (!atts->empty) { + bool fb_read_bound = false; + bool fb_draw_bound = false; + const int num_color_atts = atts->num_color_views; + for (int i = 0; i < num_color_atts; i++) { + // perform MSAA resolve if needed + const _sg_view_t* rsv_view = atts->resolve_views[i]; + if (rsv_view && rsv_view->gl.msaa_resolve_frame_buffer) { + if (!fb_read_bound) { + glBindFramebuffer(GL_READ_FRAMEBUFFER, _sg.gl.fb); + fb_read_bound = true; + } + const _sg_image_t* rsv_img = _sg_image_ref_ptr(&rsv_view->cmn.img.ref); + const int w = rsv_img->cmn.width; + const int h = rsv_img->cmn.height; + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, rsv_view->gl.msaa_resolve_frame_buffer); + glReadBuffer((GLenum)(GL_COLOR_ATTACHMENT0 + i)); + glBlitFramebuffer(0, 0, w, h, 0, 0, w, h, GL_COLOR_BUFFER_BIT, GL_NEAREST); + fb_draw_bound = true; + } + } + + // invalidate framebuffers + _SOKOL_UNUSED(fb_draw_bound); + #if defined(SOKOL_GLES3) + // need to restore framebuffer binding before invalidate if the MSAA resolve had changed the binding + if (fb_draw_bound) { + glBindFramebuffer(GL_FRAMEBUFFER, _sg.gl.fb); + } + GLenum invalidate_atts[SG_MAX_COLOR_ATTACHMENTS + 2] = { 0 }; + int att_index = 0; + for (int i = 0; i < num_color_atts; i++) { + if (_sg.gl.color_store_actions[i] == SG_STOREACTION_DONTCARE) { + invalidate_atts[att_index++] = (GLenum)(GL_COLOR_ATTACHMENT0 + i); + } + } + if (!atts->ds_view) { + if (_sg.gl.depth_store_action == SG_STOREACTION_DONTCARE) { + invalidate_atts[att_index++] = GL_DEPTH_ATTACHMENT; + } + if (_sg.gl.stencil_store_action == SG_STOREACTION_DONTCARE) { + invalidate_atts[att_index++] = GL_STENCIL_ATTACHMENT; + } + } + if (att_index > 0) { + glInvalidateFramebuffer(GL_DRAW_FRAMEBUFFER, att_index, invalidate_atts); + } + #endif + } +} + +_SOKOL_PRIVATE void _sg_gl_end_pass(const _sg_attachments_ptrs_t* atts) { + _SG_GL_CHECK_ERROR(); + if (!_sg.cur_pass.is_compute) { + _sg_gl_end_render_pass(atts); + } + _SG_GL_CHECK_ERROR(); +} + +_SOKOL_PRIVATE void _sg_gl_apply_viewport(int x, int y, int w, int h, bool origin_top_left) { + y = origin_top_left ? (_sg.cur_pass.dim.height - (y+h)) : y; + glViewport(x, y, w, h); +} + +_SOKOL_PRIVATE void _sg_gl_apply_scissor_rect(int x, int y, int w, int h, bool origin_top_left) { + y = origin_top_left ? (_sg.cur_pass.dim.height - (y+h)) : y; + glScissor(x, y, w, h); +} + +_SOKOL_PRIVATE void _sg_gl_apply_render_pipeline_state(_sg_pipeline_t* pip) { + // update render pipeline state + _sg.gl.cache.cur_primitive_type = _sg_gl_primitive_type(pip->gl.primitive_type); + _sg.gl.cache.cur_index_type = _sg_gl_index_type(pip->cmn.index_type); + + // update depth state + { + const sg_depth_state* state_ds = &pip->gl.depth; + sg_depth_state* cache_ds = &_sg.gl.cache.depth; + if (state_ds->compare != cache_ds->compare) { + cache_ds->compare = state_ds->compare; + glDepthFunc(_sg_gl_compare_func(state_ds->compare)); + _sg_stats_add(gl.num_render_state, 1); + } + if (state_ds->write_enabled != cache_ds->write_enabled) { + cache_ds->write_enabled = state_ds->write_enabled; + glDepthMask(state_ds->write_enabled); + _sg_stats_add(gl.num_render_state, 1); + } + if (!_sg_fequal(state_ds->bias, cache_ds->bias, 0.000001f) || + !_sg_fequal(state_ds->bias_slope_scale, cache_ds->bias_slope_scale, 0.000001f)) + { + /* according to ANGLE's D3D11 backend: + D3D11 SlopeScaledDepthBias ==> GL polygonOffsetFactor + D3D11 DepthBias ==> GL polygonOffsetUnits + DepthBiasClamp has no meaning on GL + */ + cache_ds->bias = state_ds->bias; + cache_ds->bias_slope_scale = state_ds->bias_slope_scale; + glPolygonOffset(state_ds->bias_slope_scale, state_ds->bias); + _sg_stats_add(gl.num_render_state, 1); + bool po_enabled = true; + if (_sg_fequal(state_ds->bias, 0.0f, 0.000001f) && + _sg_fequal(state_ds->bias_slope_scale, 0.0f, 0.000001f)) + { + po_enabled = false; + } + if (po_enabled != _sg.gl.cache.polygon_offset_enabled) { + _sg.gl.cache.polygon_offset_enabled = po_enabled; + if (po_enabled) { + glEnable(GL_POLYGON_OFFSET_FILL); + } else { + glDisable(GL_POLYGON_OFFSET_FILL); + } + _sg_stats_add(gl.num_render_state, 1); + } + } + } + + // update stencil state + { + const sg_stencil_state* state_ss = &pip->gl.stencil; + sg_stencil_state* cache_ss = &_sg.gl.cache.stencil; + if (state_ss->enabled != cache_ss->enabled) { + cache_ss->enabled = state_ss->enabled; + if (state_ss->enabled) { + glEnable(GL_STENCIL_TEST); + } else { + glDisable(GL_STENCIL_TEST); + } + _sg_stats_add(gl.num_render_state, 1); + } + if (state_ss->write_mask != cache_ss->write_mask) { + cache_ss->write_mask = state_ss->write_mask; + glStencilMask(state_ss->write_mask); + _sg_stats_add(gl.num_render_state, 1); + } + for (int i = 0; i < 2; i++) { + const sg_stencil_face_state* state_sfs = (i==0)? &state_ss->front : &state_ss->back; + sg_stencil_face_state* cache_sfs = (i==0)? &cache_ss->front : &cache_ss->back; + GLenum gl_face = (i==0)? GL_FRONT : GL_BACK; + if ((state_sfs->compare != cache_sfs->compare) || + (state_ss->read_mask != cache_ss->read_mask) || + (state_ss->ref != cache_ss->ref)) + { + cache_sfs->compare = state_sfs->compare; + glStencilFuncSeparate(gl_face, + _sg_gl_compare_func(state_sfs->compare), + state_ss->ref, + state_ss->read_mask); + _sg_stats_add(gl.num_render_state, 1); + } + if ((state_sfs->fail_op != cache_sfs->fail_op) || + (state_sfs->depth_fail_op != cache_sfs->depth_fail_op) || + (state_sfs->pass_op != cache_sfs->pass_op)) + { + cache_sfs->fail_op = state_sfs->fail_op; + cache_sfs->depth_fail_op = state_sfs->depth_fail_op; + cache_sfs->pass_op = state_sfs->pass_op; + glStencilOpSeparate(gl_face, + _sg_gl_stencil_op(state_sfs->fail_op), + _sg_gl_stencil_op(state_sfs->depth_fail_op), + _sg_gl_stencil_op(state_sfs->pass_op)); + _sg_stats_add(gl.num_render_state, 1); + } + } + cache_ss->read_mask = state_ss->read_mask; + cache_ss->ref = state_ss->ref; + } + + if (pip->cmn.color_count > 0) { + // update blend state + // FIXME: separate blend state per color attachment + const sg_blend_state* state_bs = &pip->gl.blend; + sg_blend_state* cache_bs = &_sg.gl.cache.blend; + if (state_bs->enabled != cache_bs->enabled) { + cache_bs->enabled = state_bs->enabled; + if (state_bs->enabled) { + glEnable(GL_BLEND); + } else { + glDisable(GL_BLEND); + } + _sg_stats_add(gl.num_render_state, 1); + } + if ((state_bs->src_factor_rgb != cache_bs->src_factor_rgb) || + (state_bs->dst_factor_rgb != cache_bs->dst_factor_rgb) || + (state_bs->src_factor_alpha != cache_bs->src_factor_alpha) || + (state_bs->dst_factor_alpha != cache_bs->dst_factor_alpha)) + { + cache_bs->src_factor_rgb = state_bs->src_factor_rgb; + cache_bs->dst_factor_rgb = state_bs->dst_factor_rgb; + cache_bs->src_factor_alpha = state_bs->src_factor_alpha; + cache_bs->dst_factor_alpha = state_bs->dst_factor_alpha; + glBlendFuncSeparate(_sg_gl_blend_factor(state_bs->src_factor_rgb), + _sg_gl_blend_factor(state_bs->dst_factor_rgb), + _sg_gl_blend_factor(state_bs->src_factor_alpha), + _sg_gl_blend_factor(state_bs->dst_factor_alpha)); + _sg_stats_add(gl.num_render_state, 1); + } + if ((state_bs->op_rgb != cache_bs->op_rgb) || (state_bs->op_alpha != cache_bs->op_alpha)) { + cache_bs->op_rgb = state_bs->op_rgb; + cache_bs->op_alpha = state_bs->op_alpha; + glBlendEquationSeparate(_sg_gl_blend_op(state_bs->op_rgb), _sg_gl_blend_op(state_bs->op_alpha)); + _sg_stats_add(gl.num_render_state, 1); + } + + // standalone color target state + for (GLuint i = 0; i < (GLuint)pip->cmn.color_count; i++) { + if (pip->gl.color_write_mask[i] != _sg.gl.cache.color_write_mask[i]) { + const sg_color_mask cm = pip->gl.color_write_mask[i]; + _sg.gl.cache.color_write_mask[i] = cm; + #ifdef SOKOL_GLCORE + glColorMaski(i, + (cm & SG_COLORMASK_R) != 0, + (cm & SG_COLORMASK_G) != 0, + (cm & SG_COLORMASK_B) != 0, + (cm & SG_COLORMASK_A) != 0); + #else + if (0 == i) { + glColorMask((cm & SG_COLORMASK_R) != 0, + (cm & SG_COLORMASK_G) != 0, + (cm & SG_COLORMASK_B) != 0, + (cm & SG_COLORMASK_A) != 0); + } + #endif + _sg_stats_add(gl.num_render_state, 1); + } + } + + if (!_sg_fequal(pip->cmn.blend_color.r, _sg.gl.cache.blend_color.r, 0.0001f) || + !_sg_fequal(pip->cmn.blend_color.g, _sg.gl.cache.blend_color.g, 0.0001f) || + !_sg_fequal(pip->cmn.blend_color.b, _sg.gl.cache.blend_color.b, 0.0001f) || + !_sg_fequal(pip->cmn.blend_color.a, _sg.gl.cache.blend_color.a, 0.0001f)) + { + sg_color c = pip->cmn.blend_color; + _sg.gl.cache.blend_color = c; + glBlendColor(c.r, c.g, c.b, c.a); + _sg_stats_add(gl.num_render_state, 1); + } + } // pip->cmn.color_count > 0 + + if (pip->gl.cull_mode != _sg.gl.cache.cull_mode) { + _sg.gl.cache.cull_mode = pip->gl.cull_mode; + if (SG_CULLMODE_NONE == pip->gl.cull_mode) { + glDisable(GL_CULL_FACE); + _sg_stats_add(gl.num_render_state, 1); + } else { + glEnable(GL_CULL_FACE); + GLenum gl_mode = (SG_CULLMODE_FRONT == pip->gl.cull_mode) ? GL_FRONT : GL_BACK; + glCullFace(gl_mode); + _sg_stats_add(gl.num_render_state, 2); + } + } + if (pip->gl.face_winding != _sg.gl.cache.face_winding) { + _sg.gl.cache.face_winding = pip->gl.face_winding; + GLenum gl_winding = (SG_FACEWINDING_CW == pip->gl.face_winding) ? GL_CW : GL_CCW; + glFrontFace(gl_winding); + _sg_stats_add(gl.num_render_state, 1); + } + if (pip->gl.alpha_to_coverage_enabled != _sg.gl.cache.alpha_to_coverage_enabled) { + _sg.gl.cache.alpha_to_coverage_enabled = pip->gl.alpha_to_coverage_enabled; + if (pip->gl.alpha_to_coverage_enabled) { + glEnable(GL_SAMPLE_ALPHA_TO_COVERAGE); + } else { + glDisable(GL_SAMPLE_ALPHA_TO_COVERAGE); + } + _sg_stats_add(gl.num_render_state, 1); + } + #ifdef SOKOL_GLCORE + if (pip->gl.sample_count != _sg.gl.cache.sample_count) { + _sg.gl.cache.sample_count = pip->gl.sample_count; + if (pip->gl.sample_count > 1) { + glEnable(GL_MULTISAMPLE); + } else { + glDisable(GL_MULTISAMPLE); + } + _sg_stats_add(gl.num_render_state, 1); + } + #endif +} + +_SOKOL_PRIVATE void _sg_gl_apply_pipeline(_sg_pipeline_t* pip) { + SOKOL_ASSERT(pip); + _SG_GL_CHECK_ERROR(); + if (!_sg_sref_slot_eql(&_sg.gl.cache.cur_pip, &pip->slot)) { + _sg.gl.cache.cur_pip = _sg_sref(&pip->slot); + + // bind shader program + const _sg_shader_t* shd = _sg_shader_ref_ptr(&pip->cmn.shader); + if (shd->gl.prog != _sg.gl.cache.prog) { + _sg.gl.cache.prog = shd->gl.prog; + glUseProgram(shd->gl.prog); + _sg_stats_add(gl.num_use_program, 1); + } + + if (!pip->cmn.is_compute) { + _sg_gl_apply_render_pipeline_state(pip); + } + } + _SG_GL_CHECK_ERROR(); +} + +_SOKOL_PRIVATE bool _sg_gl_apply_bindings(_sg_bindings_ptrs_t* bnd) { + SOKOL_ASSERT(bnd); + SOKOL_ASSERT(bnd->pip); + _SG_GL_CHECK_ERROR(); + const _sg_shader_t* shd = _sg_shader_ref_ptr(&bnd->pip->cmn.shader); + + // bind combined texture-samplers + _SG_GL_CHECK_ERROR(); + for (size_t tex_smp_index = 0; tex_smp_index < SG_MAX_TEXTURE_SAMPLER_PAIRS; tex_smp_index++) { + const _sg_shader_texture_sampler_t* tex_smp = &shd->cmn.texture_samplers[tex_smp_index]; + if (tex_smp->stage == SG_SHADERSTAGE_NONE) { + continue; + } + const int8_t gl_tex_slot = (GLint)shd->gl.tex_slot[tex_smp_index]; + if (gl_tex_slot != -1) { + SOKOL_ASSERT(tex_smp->view_slot < SG_MAX_VIEW_BINDSLOTS); + SOKOL_ASSERT(tex_smp->sampler_slot < SG_MAX_SAMPLER_BINDSLOTS); + const _sg_view_t* view = bnd->views[tex_smp->view_slot]; + const _sg_sampler_t* smp = bnd->smps[tex_smp->sampler_slot]; + SOKOL_ASSERT(view); + SOKOL_ASSERT(smp); + const _sg_image_t* img = _sg_image_ref_ptr(&view->cmn.img.ref); + const GLenum gl_tgt = img->gl.target; + const GLuint gl_smp = smp->gl.smp; + GLuint gl_tex; + if (_sg.features.gl_texture_views) { + gl_tex = view->gl.tex_view[img->cmn.active_slot]; + } else { + gl_tex = img->gl.tex[img->cmn.active_slot]; + } + _sg_gl_cache_bind_texture_sampler(gl_tex_slot, gl_tgt, gl_tex, gl_smp); + } + } + _SG_GL_CHECK_ERROR(); + + // bind storage buffer and images + for (size_t i = 0; i < SG_MAX_VIEW_BINDSLOTS; i++) { + if (shd->cmn.views[i].stage == SG_SHADERSTAGE_NONE) { + continue; + } + const _sg_view_t* view = bnd->views[i]; + if (view->cmn.type == SG_VIEWTYPE_STORAGEBUFFER) { + const _sg_buffer_t* sbuf = _sg_buffer_ref_ptr(&view->cmn.buf.ref); + const uint8_t gl_binding = shd->gl.sbuf_binding[i]; + GLuint gl_sbuf = sbuf->gl.buf[sbuf->cmn.active_slot]; + _sg_gl_cache_bind_storage_buffer(gl_binding, gl_sbuf, view->cmn.buf.offset, sbuf->cmn.size); + } else if (view->cmn.type == SG_VIEWTYPE_STORAGEIMAGE) { + #if defined(_SOKOL_GL_HAS_COMPUTE) + const _sg_image_t* img = _sg_image_ref_ptr(&view->cmn.img.ref); + const uint8_t gl_unit = shd->gl.simg_binding[i]; + SOKOL_ASSERT((int)gl_unit < _sg.limits.max_storage_image_bindings_per_stage); + GLuint gl_tex = img->gl.tex[img->cmn.active_slot]; + GLint level = (GLint)view->cmn.img.mip_level; + GLint layer = (GLint)view->cmn.img.slice; + GLboolean layered = shd->cmn.views[i].image_type != SG_IMAGETYPE_2D; + GLenum access = shd->cmn.views[i].simg_writeonly ? GL_WRITE_ONLY : GL_READ_WRITE; + GLenum format = _sg_gl_teximage_internal_format(shd->cmn.views[i].access_format); + // NOTE: we specifically don't go through the GL cache since storage images + // are not supported on WebGL2, and on native platforms call caching isn't + // worth the hassle + glBindImageTexture(gl_unit, gl_tex, level, layered, layer, access, format); + _sg_stats_add(gl.num_bind_image_texture, 1); + #endif + } + } + _SG_GL_CHECK_ERROR(); + + if (!bnd->pip->cmn.is_compute) { + // index buffer (can be 0) + const GLuint gl_ib = bnd->ib ? bnd->ib->gl.buf[bnd->ib->cmn.active_slot] : 0; + _sg_gl_cache_bind_buffer(GL_ELEMENT_ARRAY_BUFFER, gl_ib); + _sg.gl.cache.cur_ib_offset = bnd->ib_offset; + + // vertex attributes + for (GLuint attr_index = 0; attr_index < (GLuint)_sg.limits.max_vertex_attrs; attr_index++) { + _sg_gl_attr_t* attr = &bnd->pip->gl.attrs[attr_index]; + _sg_gl_cache_attr_t* cache_attr = &_sg.gl.cache.attrs[attr_index]; + bool cache_attr_dirty = false; + int vb_offset = 0; + GLuint gl_vb = 0; + if (attr->vb_index >= 0) { + // attribute is enabled + SOKOL_ASSERT(attr->vb_index < SG_MAX_VERTEXBUFFER_BINDSLOTS); + _sg_buffer_t* vb = bnd->vbs[attr->vb_index]; + SOKOL_ASSERT(vb); + gl_vb = vb->gl.buf[vb->cmn.active_slot]; + vb_offset = bnd->vb_offsets[attr->vb_index] + attr->offset; + if ((gl_vb != cache_attr->gl_vbuf) || + (attr->size != cache_attr->gl_attr.size) || + (attr->type != cache_attr->gl_attr.type) || + (attr->normalized != cache_attr->gl_attr.normalized) || + (attr->base_type != cache_attr->gl_attr.base_type) || + (attr->stride != cache_attr->gl_attr.stride) || + (vb_offset != cache_attr->gl_attr.offset) || + (cache_attr->gl_attr.divisor != attr->divisor)) + { + _sg_gl_cache_bind_buffer(GL_ARRAY_BUFFER, gl_vb); + if (attr->base_type == SG_SHADERATTRBASETYPE_FLOAT) { + glVertexAttribPointer(attr_index, attr->size, attr->type, attr->normalized, attr->stride, (const GLvoid*)(GLintptr)vb_offset); + } else { + glVertexAttribIPointer(attr_index, attr->size, attr->type, attr->stride, (const GLvoid*)(GLintptr)vb_offset); + } + _sg_stats_add(gl.num_vertex_attrib_pointer, 1); + glVertexAttribDivisor(attr_index, (GLuint)attr->divisor); + _sg_stats_add(gl.num_vertex_attrib_divisor, 1); + cache_attr_dirty = true; + } + if (cache_attr->gl_attr.vb_index == -1) { + glEnableVertexAttribArray(attr_index); + _sg_stats_add(gl.num_enable_vertex_attrib_array, 1); + cache_attr_dirty = true; + } + } else { + // attribute is disabled + if (cache_attr->gl_attr.vb_index != -1) { + glDisableVertexAttribArray(attr_index); + _sg_stats_add(gl.num_disable_vertex_attrib_array, 1); + cache_attr_dirty = true; + } + } + if (cache_attr_dirty) { + cache_attr->gl_attr = *attr; + cache_attr->gl_attr.offset = vb_offset; + cache_attr->gl_vbuf = gl_vb; + } + } + _SG_GL_CHECK_ERROR(); + } + + // take care of storage resource memory barriers (this needs to happen after the bindings are set) + #if defined(_SOKOL_GL_HAS_COMPUTE) + _sg_gl_handle_memory_barriers(shd, bnd, 0); + _SG_GL_CHECK_ERROR(); + #endif + + return true; +} + +_SOKOL_PRIVATE void _sg_gl_apply_uniforms(int ub_slot, const sg_range* data) { + SOKOL_ASSERT((ub_slot >= 0) && (ub_slot < SG_MAX_UNIFORMBLOCK_BINDSLOTS)); + const _sg_pipeline_t* pip = _sg_pipeline_ref_ptr(&_sg.cur_pip); + const _sg_shader_t* shd = _sg_shader_ref_ptr(&pip->cmn.shader); + SOKOL_ASSERT(SG_SHADERSTAGE_NONE != shd->cmn.uniform_blocks[ub_slot].stage); + SOKOL_ASSERT(data->size == shd->cmn.uniform_blocks[ub_slot].size); + const _sg_gl_uniform_block_t* gl_ub = &shd->gl.uniform_blocks[ub_slot]; + for (int u_index = 0; u_index < gl_ub->num_uniforms; u_index++) { + const _sg_gl_uniform_t* u = &gl_ub->uniforms[u_index]; + SOKOL_ASSERT(u->type != SG_UNIFORMTYPE_INVALID); + if (u->gl_loc == -1) { + continue; + } + _sg_stats_add(gl.num_uniform, 1); + GLfloat* fptr = (GLfloat*) (((uint8_t*)data->ptr) + u->offset); + GLint* iptr = (GLint*) (((uint8_t*)data->ptr) + u->offset); + switch (u->type) { + case SG_UNIFORMTYPE_INVALID: + break; + case SG_UNIFORMTYPE_FLOAT: + glUniform1fv(u->gl_loc, u->count, fptr); + break; + case SG_UNIFORMTYPE_FLOAT2: + glUniform2fv(u->gl_loc, u->count, fptr); + break; + case SG_UNIFORMTYPE_FLOAT3: + glUniform3fv(u->gl_loc, u->count, fptr); + break; + case SG_UNIFORMTYPE_FLOAT4: + glUniform4fv(u->gl_loc, u->count, fptr); + break; + case SG_UNIFORMTYPE_INT: + glUniform1iv(u->gl_loc, u->count, iptr); + break; + case SG_UNIFORMTYPE_INT2: + glUniform2iv(u->gl_loc, u->count, iptr); + break; + case SG_UNIFORMTYPE_INT3: + glUniform3iv(u->gl_loc, u->count, iptr); + break; + case SG_UNIFORMTYPE_INT4: + glUniform4iv(u->gl_loc, u->count, iptr); + break; + case SG_UNIFORMTYPE_MAT4: + glUniformMatrix4fv(u->gl_loc, u->count, GL_FALSE, fptr); + break; + default: + SOKOL_UNREACHABLE; + break; + } + } +} + +_SOKOL_PRIVATE void _sg_gl_draw(int base_element, int num_elements, int num_instances, int base_vertex, int base_instance) { + const GLenum p_type = _sg.gl.cache.cur_primitive_type; + const bool use_instanced_draw = (num_instances > 1) || _sg.use_instanced_draw; + if (_sg.use_indexed_draw) { + // indexed rendering + const GLenum i_type = _sg.gl.cache.cur_index_type; + const int i_size = (i_type == GL_UNSIGNED_SHORT) ? 2 : 4; + const int ib_offset = _sg.gl.cache.cur_ib_offset; + const GLvoid* indices = (const GLvoid*)(GLintptr)(base_element*i_size+ib_offset); + if (use_instanced_draw) { + if ((base_vertex == 0) && (base_instance == 0)) { + glDrawElementsInstanced(p_type, num_elements, i_type, indices, num_instances); + } else if ((base_vertex != 0) && (base_instance == 0) && _sg.features.draw_base_vertex) { + #if defined(_SOKOL_GL_HAS_BASEVERTEX) + glDrawElementsInstancedBaseVertex(p_type, num_elements, i_type, indices, num_instances, base_vertex); + #endif + } else if ((base_instance != 0) && _sg.features.draw_base_instance) { + #if defined(_SOKOL_GL_HAS_BASEINSTANCE) + glDrawElementsInstancedBaseVertexBaseInstance(p_type, num_elements, i_type, indices, num_instances, base_vertex, (GLuint)base_instance); + #endif + } + } else { + if (base_vertex == 0) { + glDrawElements(p_type, num_elements, i_type, indices); + } else if (_sg.features.draw_base_vertex) { + #if defined(_SOKOL_GL_HAS_BASEVERTEX) + glDrawElementsBaseVertex(p_type, num_elements, i_type, indices, base_vertex); + #endif + } + } + } else { + // non-indexed rendering + if (use_instanced_draw) { + if (base_instance == 0) { + glDrawArraysInstanced(p_type, base_element, num_elements, num_instances); + } else if (_sg.features.draw_base_instance) { + #if defined(_SOKOL_GL_HAS_BASEINSTANCE) + glDrawArraysInstancedBaseInstance(p_type, base_element, num_elements, num_instances, (GLuint)base_instance); + #endif + } + } else { + glDrawArrays(p_type, base_element, num_elements); + } + } +} + +_SOKOL_PRIVATE void _sg_gl_dispatch(int num_groups_x, int num_groups_y, int num_groups_z) { + #if defined(_SOKOL_GL_HAS_COMPUTE) + if (!_sg.features.compute) { + return; + } + glDispatchCompute((GLuint)num_groups_x, (GLuint)num_groups_y, (GLuint)num_groups_z); + #else + (void)num_groups_x; (void)num_groups_y; (void)num_groups_z; + #endif +} + +_SOKOL_PRIVATE void _sg_gl_commit(void) { + // "soft" clear bindings (only those that are actually bound) + _sg_gl_cache_clear_buffer_bindings(false); + _sg_gl_cache_clear_texture_sampler_bindings(false); +} + +_SOKOL_PRIVATE void _sg_gl_update_buffer(_sg_buffer_t* buf, const sg_range* data) { + SOKOL_ASSERT(buf && data && data->ptr && (data->size > 0)); + // only one update per buffer per frame allowed + if (++buf->cmn.active_slot >= buf->cmn.num_slots) { + buf->cmn.active_slot = 0; + } + GLenum gl_tgt = _sg_gl_buffer_target(&buf->cmn.usage); + SOKOL_ASSERT(buf->cmn.active_slot < SG_NUM_INFLIGHT_FRAMES); + GLuint gl_buf = buf->gl.buf[buf->cmn.active_slot]; + SOKOL_ASSERT(gl_buf); + _SG_GL_CHECK_ERROR(); + _sg_gl_cache_store_buffer_binding(gl_tgt); + _sg_gl_cache_bind_buffer(gl_tgt, gl_buf); + glBufferSubData(gl_tgt, 0, (GLsizeiptr)data->size, data->ptr); + _sg_gl_cache_restore_buffer_binding(gl_tgt); + _SG_GL_CHECK_ERROR(); +} + +_SOKOL_PRIVATE void _sg_gl_append_buffer(_sg_buffer_t* buf, const sg_range* data, bool new_frame) { + SOKOL_ASSERT(buf && data && data->ptr && (data->size > 0)); + if (new_frame) { + if (++buf->cmn.active_slot >= buf->cmn.num_slots) { + buf->cmn.active_slot = 0; + } + } + GLenum gl_tgt = _sg_gl_buffer_target(&buf->cmn.usage); + SOKOL_ASSERT(buf->cmn.active_slot < SG_NUM_INFLIGHT_FRAMES); + GLuint gl_buf = buf->gl.buf[buf->cmn.active_slot]; + SOKOL_ASSERT(gl_buf); + _SG_GL_CHECK_ERROR(); + _sg_gl_cache_store_buffer_binding(gl_tgt); + _sg_gl_cache_bind_buffer(gl_tgt, gl_buf); + glBufferSubData(gl_tgt, buf->cmn.append_pos, (GLsizeiptr)data->size, data->ptr); + _sg_gl_cache_restore_buffer_binding(gl_tgt); + _SG_GL_CHECK_ERROR(); +} + +_SOKOL_PRIVATE void _sg_gl_update_image(_sg_image_t* img, const sg_image_data* data) { + SOKOL_ASSERT(img && data); + // only one update per image per frame allowed + if (++img->cmn.active_slot >= img->cmn.num_slots) { + img->cmn.active_slot = 0; + } + SOKOL_ASSERT(img->cmn.active_slot < SG_NUM_INFLIGHT_FRAMES); + SOKOL_ASSERT(0 != img->gl.tex[img->cmn.active_slot]); + _sg_gl_cache_store_texture_sampler_binding(0); + _sg_gl_cache_bind_texture_sampler(0, img->gl.target, img->gl.tex[img->cmn.active_slot], 0); + const int num_mips = img->cmn.num_mipmaps; + for (int mip_index = 0; mip_index < num_mips; mip_index++) { + const GLvoid* data_ptr = data->mip_levels[mip_index].ptr; + const GLsizei data_size = (GLsizei)data->mip_levels[mip_index].size; + const int mip_width = _sg_miplevel_dim(img->cmn.width, mip_index); + const int mip_height = _sg_miplevel_dim(img->cmn.height, mip_index); + const int mip_depth = (SG_IMAGETYPE_3D == img->cmn.type) ? _sg_miplevel_dim(img->cmn.num_slices, mip_index) : img->cmn.num_slices; + if (SG_IMAGETYPE_CUBE == img->cmn.type) { + const int surf_pitch = _sg_surface_pitch(img->cmn.pixel_format, mip_width, mip_height, 1); + SOKOL_ASSERT((6 * surf_pitch) <= data_size); + const uint8_t* surf_ptr = (const uint8_t*) data_ptr; + for (int i = 0; i < 6; i++) { + const GLenum gl_img_target = _sg_gl_cubeface_target(i); + _sg_gl_texsubimage(img, gl_img_target, mip_index, mip_width, mip_height, mip_depth, surf_ptr, surf_pitch); + surf_ptr += surf_pitch; + } + } else { + _sg_gl_texsubimage(img, img->gl.target, mip_index, mip_width, mip_height, mip_depth, data_ptr, data_size); + } + } + _sg_gl_cache_restore_texture_sampler_binding(0); +} + +// ██████ ██████ ██████ ██ ██ ██████ █████ ██████ ██ ██ ███████ ███ ██ ██████ +// ██ ██ ██ ██ ██ ███ ███ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██ ██ ██ +// ██ ██ █████ ██ ██ ██ ██ ██████ ███████ ██ █████ █████ ██ ██ ██ ██ ██ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██████ ██████ ██████ ██ ██ ██████ ██ ██ ██████ ██ ██ ███████ ██ ████ ██████ +// +// >>d3d11 backend +#elif defined(SOKOL_D3D11) + +#if defined(__cplusplus) +#define _sg_d3d11_AddRef(self) (self)->AddRef() +#else +#define _sg_d3d11_AddRef(self) (self)->lpVtbl->AddRef(self) +#endif + +#if defined(__cplusplus) +#define _sg_d3d11_Release(self) (self)->Release() +#else +#define _sg_d3d11_Release(self) (self)->lpVtbl->Release(self) +#endif + +// NOTE: This needs to be a macro since we can't use the polymorphism in C. It's called on many kinds of resources. +// NOTE: Based on microsoft docs, it's fine to call this with pData=NULL if DataSize is also zero. +#if defined(__cplusplus) +#define _sg_d3d11_SetPrivateData(self, guid, DataSize, pData) (self)->SetPrivateData(guid, DataSize, pData) +#else +#define _sg_d3d11_SetPrivateData(self, guid, DataSize, pData) (self)->lpVtbl->SetPrivateData(self, guid, DataSize, pData) +#endif + +#if defined(__cplusplus) +#define _sg_win32_refguid(guid) guid +#else +#define _sg_win32_refguid(guid) &guid +#endif + +static const GUID _sg_d3d11_WKPDID_D3DDebugObjectName = { 0x429b8c22,0x9188,0x4b0c, {0x87,0x42,0xac,0xb0,0xbf,0x85,0xc2,0x00} }; + +#if defined(SOKOL_DEBUG) +#define _sg_d3d11_setlabel(self, label) _sg_d3d11_SetPrivateData(self, _sg_win32_refguid(_sg_d3d11_WKPDID_D3DDebugObjectName), label ? (UINT)strlen(label) : 0, label) +#else +#define _sg_d3d11_setlabel(self, label) +#endif + + +//-- D3D11 C/C++ wrappers ------------------------------------------------------ +static inline HRESULT _sg_d3d11_CheckFormatSupport(ID3D11Device* self, DXGI_FORMAT Format, UINT* pFormatSupport) { + #if defined(__cplusplus) + return self->CheckFormatSupport(Format, pFormatSupport); + #else + return self->lpVtbl->CheckFormatSupport(self, Format, pFormatSupport); + #endif +} + +static inline void _sg_d3d11_OMSetRenderTargets(ID3D11DeviceContext* self, UINT NumViews, ID3D11RenderTargetView* const* ppRenderTargetViews, ID3D11DepthStencilView *pDepthStencilView) { + #if defined(__cplusplus) + self->OMSetRenderTargets(NumViews, ppRenderTargetViews, pDepthStencilView); + #else + self->lpVtbl->OMSetRenderTargets(self, NumViews, ppRenderTargetViews, pDepthStencilView); + #endif +} + +static inline void _sg_d3d11_RSSetState(ID3D11DeviceContext* self, ID3D11RasterizerState* pRasterizerState) { + #if defined(__cplusplus) + self->RSSetState(pRasterizerState); + #else + self->lpVtbl->RSSetState(self, pRasterizerState); + #endif +} + +static inline void _sg_d3d11_OMSetDepthStencilState(ID3D11DeviceContext* self, ID3D11DepthStencilState* pDepthStencilState, UINT StencilRef) { + #if defined(__cplusplus) + self->OMSetDepthStencilState(pDepthStencilState, StencilRef); + #else + self->lpVtbl->OMSetDepthStencilState(self, pDepthStencilState, StencilRef); + #endif +} + +static inline void _sg_d3d11_OMSetBlendState(ID3D11DeviceContext* self, ID3D11BlendState* pBlendState, const FLOAT BlendFactor[4], UINT SampleMask) { + #if defined(__cplusplus) + self->OMSetBlendState(pBlendState, BlendFactor, SampleMask); + #else + self->lpVtbl->OMSetBlendState(self, pBlendState, BlendFactor, SampleMask); + #endif +} + +static inline void _sg_d3d11_IASetVertexBuffers(ID3D11DeviceContext* self, UINT StartSlot, UINT NumBuffers, ID3D11Buffer* const* ppVertexBuffers, const UINT* pStrides, const UINT* pOffsets) { + #if defined(__cplusplus) + self->IASetVertexBuffers(StartSlot, NumBuffers, ppVertexBuffers, pStrides, pOffsets); + #else + self->lpVtbl->IASetVertexBuffers(self, StartSlot, NumBuffers, ppVertexBuffers, pStrides, pOffsets); + #endif +} + +static inline void _sg_d3d11_IASetIndexBuffer(ID3D11DeviceContext* self, ID3D11Buffer* pIndexBuffer, DXGI_FORMAT Format, UINT Offset) { + #if defined(__cplusplus) + self->IASetIndexBuffer(pIndexBuffer, Format, Offset); + #else + self->lpVtbl->IASetIndexBuffer(self, pIndexBuffer, Format, Offset); + #endif +} + +static inline void _sg_d3d11_IASetInputLayout(ID3D11DeviceContext* self, ID3D11InputLayout* pInputLayout) { + #if defined(__cplusplus) + self->IASetInputLayout(pInputLayout); + #else + self->lpVtbl->IASetInputLayout(self, pInputLayout); + #endif +} + +static inline void _sg_d3d11_VSSetShader(ID3D11DeviceContext* self, ID3D11VertexShader* pVertexShader, ID3D11ClassInstance* const* ppClassInstances, UINT NumClassInstances) { + #if defined(__cplusplus) + self->VSSetShader(pVertexShader, ppClassInstances, NumClassInstances); + #else + self->lpVtbl->VSSetShader(self, pVertexShader, ppClassInstances, NumClassInstances); + #endif +} + +static inline void _sg_d3d11_PSSetShader(ID3D11DeviceContext* self, ID3D11PixelShader* pPixelShader, ID3D11ClassInstance* const* ppClassInstances, UINT NumClassInstances) { + #if defined(__cplusplus) + self->PSSetShader(pPixelShader, ppClassInstances, NumClassInstances); + #else + self->lpVtbl->PSSetShader(self, pPixelShader, ppClassInstances, NumClassInstances); + #endif +} + +static inline void _sg_d3d11_CSSetShader(ID3D11DeviceContext* self, ID3D11ComputeShader* pComputeShader, ID3D11ClassInstance* const* ppClassInstances, UINT NumClassInstances) { + #if defined(__cplusplus) + self->CSSetShader(pComputeShader, ppClassInstances, NumClassInstances); + #else + self->lpVtbl->CSSetShader(self, pComputeShader, ppClassInstances, NumClassInstances); + #endif +} + +static inline void _sg_d3d11_VSSetConstantBuffers(ID3D11DeviceContext* self, UINT StartSlot, UINT NumBuffers, ID3D11Buffer* const* ppConstantBuffers) { + #if defined(__cplusplus) + self->VSSetConstantBuffers(StartSlot, NumBuffers, ppConstantBuffers); + #else + self->lpVtbl->VSSetConstantBuffers(self, StartSlot, NumBuffers, ppConstantBuffers); + #endif +} + +static inline void _sg_d3d11_PSSetConstantBuffers(ID3D11DeviceContext* self, UINT StartSlot, UINT NumBuffers, ID3D11Buffer* const* ppConstantBuffers) { + #if defined(__cplusplus) + self->PSSetConstantBuffers(StartSlot, NumBuffers, ppConstantBuffers); + #else + self->lpVtbl->PSSetConstantBuffers(self, StartSlot, NumBuffers, ppConstantBuffers); + #endif +} + +static inline void _sg_d3d11_CSSetConstantBuffers(ID3D11DeviceContext* self, UINT StartSlot, UINT NumBuffers, ID3D11Buffer* const* ppConstantBuffers) { + #if defined(__cplusplus) + self->CSSetConstantBuffers(StartSlot, NumBuffers, ppConstantBuffers); + #else + self->lpVtbl->CSSetConstantBuffers(self, StartSlot, NumBuffers, ppConstantBuffers); + #endif +} + +static inline void _sg_d3d11_VSSetShaderResources(ID3D11DeviceContext* self, UINT StartSlot, UINT NumViews, ID3D11ShaderResourceView* const* ppShaderResourceViews) { + #if defined(__cplusplus) + self->VSSetShaderResources(StartSlot, NumViews, ppShaderResourceViews); + #else + self->lpVtbl->VSSetShaderResources(self, StartSlot, NumViews, ppShaderResourceViews); + #endif +} + +static inline void _sg_d3d11_PSSetShaderResources(ID3D11DeviceContext* self, UINT StartSlot, UINT NumViews, ID3D11ShaderResourceView* const* ppShaderResourceViews) { + #if defined(__cplusplus) + self->PSSetShaderResources(StartSlot, NumViews, ppShaderResourceViews); + #else + self->lpVtbl->PSSetShaderResources(self, StartSlot, NumViews, ppShaderResourceViews); + #endif +} + +static inline void _sg_d3d11_CSSetShaderResources(ID3D11DeviceContext* self, UINT StartSlot, UINT NumViews, ID3D11ShaderResourceView* const* ppShaderResourceViews) { + #if defined(__cplusplus) + self->CSSetShaderResources(StartSlot, NumViews, ppShaderResourceViews); + #else + self->lpVtbl->CSSetShaderResources(self, StartSlot, NumViews, ppShaderResourceViews); + #endif +} + +static inline void _sg_d3d11_VSSetSamplers(ID3D11DeviceContext* self, UINT StartSlot, UINT NumSamplers, ID3D11SamplerState* const* ppSamplers) { + #if defined(__cplusplus) + self->VSSetSamplers(StartSlot, NumSamplers, ppSamplers); + #else + self->lpVtbl->VSSetSamplers(self, StartSlot, NumSamplers, ppSamplers); + #endif +} + +static inline void _sg_d3d11_PSSetSamplers(ID3D11DeviceContext* self, UINT StartSlot, UINT NumSamplers, ID3D11SamplerState* const* ppSamplers) { + #if defined(__cplusplus) + self->PSSetSamplers(StartSlot, NumSamplers, ppSamplers); + #else + self->lpVtbl->PSSetSamplers(self, StartSlot, NumSamplers, ppSamplers); + #endif +} + +static inline void _sg_d3d11_CSSetSamplers(ID3D11DeviceContext* self, UINT StartSlot, UINT NumSamplers, ID3D11SamplerState* const* ppSamplers) { + #if defined(__cplusplus) + self->CSSetSamplers(StartSlot, NumSamplers, ppSamplers); + #else + self->lpVtbl->CSSetSamplers(self, StartSlot, NumSamplers, ppSamplers); + #endif +} + +static inline void _sg_d3d11_CSSetUnorderedAccessViews(ID3D11DeviceContext* self, UINT StartSlot, UINT NumUAVs, ID3D11UnorderedAccessView* const* ppUnorderedAccessViews, const UINT* pUAVInitialCounts) { + #if defined(__cplusplus) + self->CSSetUnorderedAccessViews(StartSlot, NumUAVs, ppUnorderedAccessViews, pUAVInitialCounts); + #else + self->lpVtbl->CSSetUnorderedAccessViews(self, StartSlot, NumUAVs, ppUnorderedAccessViews, pUAVInitialCounts); + #endif +} + +static inline HRESULT _sg_d3d11_CreateBuffer(ID3D11Device* self, const D3D11_BUFFER_DESC* pDesc, const D3D11_SUBRESOURCE_DATA* pInitialData, ID3D11Buffer** ppBuffer) { + #if defined(__cplusplus) + return self->CreateBuffer(pDesc, pInitialData, ppBuffer); + #else + return self->lpVtbl->CreateBuffer(self, pDesc, pInitialData, ppBuffer); + #endif +} + +static inline HRESULT _sg_d3d11_CreateTexture2D(ID3D11Device* self, const D3D11_TEXTURE2D_DESC* pDesc, const D3D11_SUBRESOURCE_DATA* pInitialData, ID3D11Texture2D** ppTexture2D) { + #if defined(__cplusplus) + return self->CreateTexture2D(pDesc, pInitialData, ppTexture2D); + #else + return self->lpVtbl->CreateTexture2D(self, pDesc, pInitialData, ppTexture2D); + #endif +} + +static inline HRESULT _sg_d3d11_CreateShaderResourceView(ID3D11Device* self, ID3D11Resource* pResource, const D3D11_SHADER_RESOURCE_VIEW_DESC* pDesc, ID3D11ShaderResourceView** ppSRView) { + #if defined(__cplusplus) + return self->CreateShaderResourceView(pResource, pDesc, ppSRView); + #else + return self->lpVtbl->CreateShaderResourceView(self, pResource, pDesc, ppSRView); + #endif +} + +static inline HRESULT _sg_d3d11_CreateUnorderedAccessView(ID3D11Device* self, ID3D11Resource* pResource, const D3D11_UNORDERED_ACCESS_VIEW_DESC* pDesc, ID3D11UnorderedAccessView** ppUAVView) { + #if defined(__cplusplus) + return self->CreateUnorderedAccessView(pResource, pDesc, ppUAVView); + #else + return self->lpVtbl->CreateUnorderedAccessView(self, pResource, pDesc, ppUAVView); + #endif +} + +static inline void _sg_d3d11_GetResource(ID3D11View* self, ID3D11Resource** ppResource) { + #if defined(__cplusplus) + self->GetResource(ppResource); + #else + self->lpVtbl->GetResource(self, ppResource); + #endif +} + +static inline HRESULT _sg_d3d11_CreateTexture3D(ID3D11Device* self, const D3D11_TEXTURE3D_DESC* pDesc, const D3D11_SUBRESOURCE_DATA* pInitialData, ID3D11Texture3D** ppTexture3D) { + #if defined(__cplusplus) + return self->CreateTexture3D(pDesc, pInitialData, ppTexture3D); + #else + return self->lpVtbl->CreateTexture3D(self, pDesc, pInitialData, ppTexture3D); + #endif +} + +static inline HRESULT _sg_d3d11_CreateSamplerState(ID3D11Device* self, const D3D11_SAMPLER_DESC* pSamplerDesc, ID3D11SamplerState** ppSamplerState) { + #if defined(__cplusplus) + return self->CreateSamplerState(pSamplerDesc, ppSamplerState); + #else + return self->lpVtbl->CreateSamplerState(self, pSamplerDesc, ppSamplerState); + #endif +} + +static inline LPVOID _sg_d3d11_GetBufferPointer(ID3D10Blob* self) { + #if defined(__cplusplus) + return self->GetBufferPointer(); + #else + return self->lpVtbl->GetBufferPointer(self); + #endif +} + +static inline SIZE_T _sg_d3d11_GetBufferSize(ID3D10Blob* self) { + #if defined(__cplusplus) + return self->GetBufferSize(); + #else + return self->lpVtbl->GetBufferSize(self); + #endif +} + +static inline HRESULT _sg_d3d11_CreateVertexShader(ID3D11Device* self, const void* pShaderBytecode, SIZE_T BytecodeLength, ID3D11ClassLinkage* pClassLinkage, ID3D11VertexShader** ppVertexShader) { + #if defined(__cplusplus) + return self->CreateVertexShader(pShaderBytecode, BytecodeLength, pClassLinkage, ppVertexShader); + #else + return self->lpVtbl->CreateVertexShader(self, pShaderBytecode, BytecodeLength, pClassLinkage, ppVertexShader); + #endif +} + +static inline HRESULT _sg_d3d11_CreatePixelShader(ID3D11Device* self, const void* pShaderBytecode, SIZE_T BytecodeLength, ID3D11ClassLinkage* pClassLinkage, ID3D11PixelShader** ppPixelShader) { + #if defined(__cplusplus) + return self->CreatePixelShader(pShaderBytecode, BytecodeLength, pClassLinkage, ppPixelShader); + #else + return self->lpVtbl->CreatePixelShader(self, pShaderBytecode, BytecodeLength, pClassLinkage, ppPixelShader); + #endif +} + +static inline HRESULT _sg_d3d11_CreateComputeShader(ID3D11Device* self, const void* pShaderBytecode, SIZE_T BytecodeLength, ID3D11ClassLinkage* pClassLinkage, ID3D11ComputeShader** ppComputeShader) { + #if defined(__cplusplus) + return self->CreateComputeShader(pShaderBytecode, BytecodeLength, pClassLinkage, ppComputeShader); + #else + return self->lpVtbl->CreateComputeShader(self, pShaderBytecode, BytecodeLength, pClassLinkage, ppComputeShader); + #endif +} + +static inline HRESULT _sg_d3d11_CreateInputLayout(ID3D11Device* self, const D3D11_INPUT_ELEMENT_DESC* pInputElementDescs, UINT NumElements, const void* pShaderBytecodeWithInputSignature, SIZE_T BytecodeLength, ID3D11InputLayout **ppInputLayout) { + #if defined(__cplusplus) + return self->CreateInputLayout(pInputElementDescs, NumElements, pShaderBytecodeWithInputSignature, BytecodeLength, ppInputLayout); + #else + return self->lpVtbl->CreateInputLayout(self, pInputElementDescs, NumElements, pShaderBytecodeWithInputSignature, BytecodeLength, ppInputLayout); + #endif +} + +static inline HRESULT _sg_d3d11_CreateRasterizerState(ID3D11Device* self, const D3D11_RASTERIZER_DESC* pRasterizerDesc, ID3D11RasterizerState** ppRasterizerState) { + #if defined(__cplusplus) + return self->CreateRasterizerState(pRasterizerDesc, ppRasterizerState); + #else + return self->lpVtbl->CreateRasterizerState(self, pRasterizerDesc, ppRasterizerState); + #endif +} + +static inline HRESULT _sg_d3d11_CreateDepthStencilState(ID3D11Device* self, const D3D11_DEPTH_STENCIL_DESC* pDepthStencilDesc, ID3D11DepthStencilState** ppDepthStencilState) { + #if defined(__cplusplus) + return self->CreateDepthStencilState(pDepthStencilDesc, ppDepthStencilState); + #else + return self->lpVtbl->CreateDepthStencilState(self, pDepthStencilDesc, ppDepthStencilState); + #endif +} + +static inline HRESULT _sg_d3d11_CreateBlendState(ID3D11Device* self, const D3D11_BLEND_DESC* pBlendStateDesc, ID3D11BlendState** ppBlendState) { + #if defined(__cplusplus) + return self->CreateBlendState(pBlendStateDesc, ppBlendState); + #else + return self->lpVtbl->CreateBlendState(self, pBlendStateDesc, ppBlendState); + #endif +} + +static inline HRESULT _sg_d3d11_CreateRenderTargetView(ID3D11Device* self, ID3D11Resource *pResource, const D3D11_RENDER_TARGET_VIEW_DESC* pDesc, ID3D11RenderTargetView** ppRTView) { + #if defined(__cplusplus) + return self->CreateRenderTargetView(pResource, pDesc, ppRTView); + #else + return self->lpVtbl->CreateRenderTargetView(self, pResource, pDesc, ppRTView); + #endif +} + +static inline HRESULT _sg_d3d11_CreateDepthStencilView(ID3D11Device* self, ID3D11Resource* pResource, const D3D11_DEPTH_STENCIL_VIEW_DESC* pDesc, ID3D11DepthStencilView** ppDepthStencilView) { + #if defined(__cplusplus) + return self->CreateDepthStencilView(pResource, pDesc, ppDepthStencilView); + #else + return self->lpVtbl->CreateDepthStencilView(self, pResource, pDesc, ppDepthStencilView); + #endif +} + +static inline void _sg_d3d11_RSSetViewports(ID3D11DeviceContext* self, UINT NumViewports, const D3D11_VIEWPORT* pViewports) { + #if defined(__cplusplus) + self->RSSetViewports(NumViewports, pViewports); + #else + self->lpVtbl->RSSetViewports(self, NumViewports, pViewports); + #endif +} + +static inline void _sg_d3d11_RSSetScissorRects(ID3D11DeviceContext* self, UINT NumRects, const D3D11_RECT* pRects) { + #if defined(__cplusplus) + self->RSSetScissorRects(NumRects, pRects); + #else + self->lpVtbl->RSSetScissorRects(self, NumRects, pRects); + #endif +} + +static inline void _sg_d3d11_ClearRenderTargetView(ID3D11DeviceContext* self, ID3D11RenderTargetView* pRenderTargetView, const FLOAT ColorRGBA[4]) { + #if defined(__cplusplus) + self->ClearRenderTargetView(pRenderTargetView, ColorRGBA); + #else + self->lpVtbl->ClearRenderTargetView(self, pRenderTargetView, ColorRGBA); + #endif +} + +static inline void _sg_d3d11_ClearDepthStencilView(ID3D11DeviceContext* self, ID3D11DepthStencilView* pDepthStencilView, UINT ClearFlags, FLOAT Depth, UINT8 Stencil) { + #if defined(__cplusplus) + self->ClearDepthStencilView(pDepthStencilView, ClearFlags, Depth, Stencil); + #else + self->lpVtbl->ClearDepthStencilView(self, pDepthStencilView, ClearFlags, Depth, Stencil); + #endif +} + +static inline void _sg_d3d11_ResolveSubresource(ID3D11DeviceContext* self, ID3D11Resource* pDstResource, UINT DstSubresource, ID3D11Resource* pSrcResource, UINT SrcSubresource, DXGI_FORMAT Format) { + #if defined(__cplusplus) + self->ResolveSubresource(pDstResource, DstSubresource, pSrcResource, SrcSubresource, Format); + #else + self->lpVtbl->ResolveSubresource(self, pDstResource, DstSubresource, pSrcResource, SrcSubresource, Format); + #endif +} + +static inline void _sg_d3d11_IASetPrimitiveTopology(ID3D11DeviceContext* self, D3D11_PRIMITIVE_TOPOLOGY Topology) { + #if defined(__cplusplus) + self->IASetPrimitiveTopology(Topology); + #else + self->lpVtbl->IASetPrimitiveTopology(self, Topology); + #endif +} + +static inline void _sg_d3d11_UpdateSubresource(ID3D11DeviceContext* self, ID3D11Resource* pDstResource, UINT DstSubresource, const D3D11_BOX* pDstBox, const void* pSrcData, UINT SrcRowPitch, UINT SrcDepthPitch) { + #if defined(__cplusplus) + self->UpdateSubresource(pDstResource, DstSubresource, pDstBox, pSrcData, SrcRowPitch, SrcDepthPitch); + #else + self->lpVtbl->UpdateSubresource(self, pDstResource, DstSubresource, pDstBox, pSrcData, SrcRowPitch, SrcDepthPitch); + #endif +} + +static inline void _sg_d3d11_DrawIndexed(ID3D11DeviceContext* self, UINT IndexCount, UINT StartIndexLocation, INT BaseVertexLocation) { + #if defined(__cplusplus) + self->DrawIndexed(IndexCount, StartIndexLocation, BaseVertexLocation); + #else + self->lpVtbl->DrawIndexed(self, IndexCount, StartIndexLocation, BaseVertexLocation); + #endif +} + +static inline void _sg_d3d11_DrawIndexedInstanced(ID3D11DeviceContext* self, UINT IndexCountPerInstance, UINT InstanceCount, UINT StartIndexLocation, INT BaseVertexLocation, UINT StartInstanceLocation) { + #if defined(__cplusplus) + self->DrawIndexedInstanced(IndexCountPerInstance, InstanceCount, StartIndexLocation, BaseVertexLocation, StartInstanceLocation); + #else + self->lpVtbl->DrawIndexedInstanced(self, IndexCountPerInstance, InstanceCount, StartIndexLocation, BaseVertexLocation, StartInstanceLocation); + #endif +} + +static inline void _sg_d3d11_Draw(ID3D11DeviceContext* self, UINT VertexCount, UINT StartVertexLocation) { + #if defined(__cplusplus) + self->Draw(VertexCount, StartVertexLocation); + #else + self->lpVtbl->Draw(self, VertexCount, StartVertexLocation); + #endif +} + +static inline void _sg_d3d11_DrawInstanced(ID3D11DeviceContext* self, UINT VertexCountPerInstance, UINT InstanceCount, UINT StartVertexLocation, UINT StartInstanceLocation) { + #if defined(__cplusplus) + self->DrawInstanced(VertexCountPerInstance, InstanceCount, StartVertexLocation, StartInstanceLocation); + #else + self->lpVtbl->DrawInstanced(self, VertexCountPerInstance, InstanceCount, StartVertexLocation, StartInstanceLocation); + #endif +} + +static inline void _sg_d3d11_Dispatch(ID3D11DeviceContext* self, UINT ThreadGroupCountX, UINT ThreadGroupCountY, UINT ThreadGroupCountZ) { + #if defined(__cplusplus) + self->Dispatch(ThreadGroupCountX, ThreadGroupCountY, ThreadGroupCountZ); + #else + self->lpVtbl->Dispatch(self, ThreadGroupCountX, ThreadGroupCountY, ThreadGroupCountZ); + #endif +} + +static inline HRESULT _sg_d3d11_Map(ID3D11DeviceContext* self, ID3D11Resource* pResource, UINT Subresource, D3D11_MAP MapType, UINT MapFlags, D3D11_MAPPED_SUBRESOURCE* pMappedResource) { + #if defined(__cplusplus) + return self->Map(pResource, Subresource, MapType, MapFlags, pMappedResource); + #else + return self->lpVtbl->Map(self, pResource, Subresource, MapType, MapFlags, pMappedResource); + #endif +} + +static inline void _sg_d3d11_Unmap(ID3D11DeviceContext* self, ID3D11Resource* pResource, UINT Subresource) { + #if defined(__cplusplus) + self->Unmap(pResource, Subresource); + #else + self->lpVtbl->Unmap(self, pResource, Subresource); + #endif +} + +static inline void _sg_d3d11_ClearState(ID3D11DeviceContext* self) { + #if defined(__cplusplus) + self->ClearState(); + #else + self->lpVtbl->ClearState(self); + #endif +} + +static inline D3D_FEATURE_LEVEL _sg_d3d11_GetFeatureLevel(ID3D11Device* self) { + #if defined(__cplusplus) + return self->GetFeatureLevel(); + #else + return self->lpVtbl->GetFeatureLevel(self); + #endif +} + +//-- enum translation functions ------------------------------------------------ +_SOKOL_PRIVATE D3D11_USAGE _sg_d3d11_image_usage(const sg_image_usage* usg) { + if (usg->immutable) { + if (usg->color_attachment || + usg->resolve_attachment || + usg->depth_stencil_attachment || + usg->storage_image) + { + return D3D11_USAGE_DEFAULT; + } else { + return D3D11_USAGE_IMMUTABLE; + } + } else { + return D3D11_USAGE_DYNAMIC; + } +} + +_SOKOL_PRIVATE UINT _sg_d3d11_image_bind_flags(const sg_image_usage* usg) { + UINT res = D3D11_BIND_SHADER_RESOURCE; + if (usg->color_attachment) { + res |= D3D11_BIND_RENDER_TARGET; + } + if (usg->depth_stencil_attachment) { + res |= D3D11_BIND_DEPTH_STENCIL; + } + if (usg->storage_image) { + res |= D3D11_BIND_UNORDERED_ACCESS; + } + return res; +} + +_SOKOL_PRIVATE UINT _sg_d3d11_image_cpu_access_flags(const sg_image_usage* usg) { + if (usg->color_attachment || + usg->resolve_attachment || + usg->depth_stencil_attachment || + usg->storage_image || + usg->immutable) + { + return 0; + } else { + return D3D11_CPU_ACCESS_WRITE; + } +} + +_SOKOL_PRIVATE D3D11_USAGE _sg_d3d11_buffer_usage(const sg_buffer_usage* usg) { + if (usg->immutable) { + return usg->storage_buffer ? D3D11_USAGE_DEFAULT : D3D11_USAGE_IMMUTABLE; + } else { + return D3D11_USAGE_DYNAMIC; + } +} + +_SOKOL_PRIVATE UINT _sg_d3d11_buffer_bind_flags(const sg_buffer_usage* usg) { + UINT res = 0; + if (usg->vertex_buffer) { + res |= D3D11_BIND_VERTEX_BUFFER; + } + if (usg->index_buffer) { + res |= D3D11_BIND_INDEX_BUFFER; + } + if (usg->storage_buffer) { + res |= D3D11_BIND_SHADER_RESOURCE; + if (usg->immutable) { + res |= D3D11_BIND_UNORDERED_ACCESS; + } + } + return res; +} + +_SOKOL_PRIVATE UINT _sg_d3d11_buffer_misc_flags(const sg_buffer_usage* usg) { + return usg->storage_buffer ? D3D11_RESOURCE_MISC_BUFFER_ALLOW_RAW_VIEWS : 0; +} + +_SOKOL_PRIVATE UINT _sg_d3d11_buffer_cpu_access_flags(const sg_buffer_usage* usg) { + return usg->immutable ? 0 : D3D11_CPU_ACCESS_WRITE; +} + +_SOKOL_PRIVATE DXGI_FORMAT _sg_d3d11_texture_pixel_format(sg_pixel_format fmt) { + switch (fmt) { + case SG_PIXELFORMAT_R8: return DXGI_FORMAT_R8_UNORM; + case SG_PIXELFORMAT_R8SN: return DXGI_FORMAT_R8_SNORM; + case SG_PIXELFORMAT_R8UI: return DXGI_FORMAT_R8_UINT; + case SG_PIXELFORMAT_R8SI: return DXGI_FORMAT_R8_SINT; + case SG_PIXELFORMAT_R16: return DXGI_FORMAT_R16_UNORM; + case SG_PIXELFORMAT_R16SN: return DXGI_FORMAT_R16_SNORM; + case SG_PIXELFORMAT_R16UI: return DXGI_FORMAT_R16_UINT; + case SG_PIXELFORMAT_R16SI: return DXGI_FORMAT_R16_SINT; + case SG_PIXELFORMAT_R16F: return DXGI_FORMAT_R16_FLOAT; + case SG_PIXELFORMAT_RG8: return DXGI_FORMAT_R8G8_UNORM; + case SG_PIXELFORMAT_RG8SN: return DXGI_FORMAT_R8G8_SNORM; + case SG_PIXELFORMAT_RG8UI: return DXGI_FORMAT_R8G8_UINT; + case SG_PIXELFORMAT_RG8SI: return DXGI_FORMAT_R8G8_SINT; + case SG_PIXELFORMAT_R32UI: return DXGI_FORMAT_R32_UINT; + case SG_PIXELFORMAT_R32SI: return DXGI_FORMAT_R32_SINT; + case SG_PIXELFORMAT_R32F: return DXGI_FORMAT_R32_FLOAT; + case SG_PIXELFORMAT_RG16: return DXGI_FORMAT_R16G16_UNORM; + case SG_PIXELFORMAT_RG16SN: return DXGI_FORMAT_R16G16_SNORM; + case SG_PIXELFORMAT_RG16UI: return DXGI_FORMAT_R16G16_UINT; + case SG_PIXELFORMAT_RG16SI: return DXGI_FORMAT_R16G16_SINT; + case SG_PIXELFORMAT_RG16F: return DXGI_FORMAT_R16G16_FLOAT; + case SG_PIXELFORMAT_RGBA8: return DXGI_FORMAT_R8G8B8A8_UNORM; + case SG_PIXELFORMAT_SRGB8A8: return DXGI_FORMAT_R8G8B8A8_UNORM_SRGB; + case SG_PIXELFORMAT_RGBA8SN: return DXGI_FORMAT_R8G8B8A8_SNORM; + case SG_PIXELFORMAT_RGBA8UI: return DXGI_FORMAT_R8G8B8A8_UINT; + case SG_PIXELFORMAT_RGBA8SI: return DXGI_FORMAT_R8G8B8A8_SINT; + case SG_PIXELFORMAT_BGRA8: return DXGI_FORMAT_B8G8R8A8_UNORM; + case SG_PIXELFORMAT_RGB10A2: return DXGI_FORMAT_R10G10B10A2_UNORM; + case SG_PIXELFORMAT_RG11B10F: return DXGI_FORMAT_R11G11B10_FLOAT; + case SG_PIXELFORMAT_RGB9E5: return DXGI_FORMAT_R9G9B9E5_SHAREDEXP; + case SG_PIXELFORMAT_RG32UI: return DXGI_FORMAT_R32G32_UINT; + case SG_PIXELFORMAT_RG32SI: return DXGI_FORMAT_R32G32_SINT; + case SG_PIXELFORMAT_RG32F: return DXGI_FORMAT_R32G32_FLOAT; + case SG_PIXELFORMAT_RGBA16: return DXGI_FORMAT_R16G16B16A16_UNORM; + case SG_PIXELFORMAT_RGBA16SN: return DXGI_FORMAT_R16G16B16A16_SNORM; + case SG_PIXELFORMAT_RGBA16UI: return DXGI_FORMAT_R16G16B16A16_UINT; + case SG_PIXELFORMAT_RGBA16SI: return DXGI_FORMAT_R16G16B16A16_SINT; + case SG_PIXELFORMAT_RGBA16F: return DXGI_FORMAT_R16G16B16A16_FLOAT; + case SG_PIXELFORMAT_RGBA32UI: return DXGI_FORMAT_R32G32B32A32_UINT; + case SG_PIXELFORMAT_RGBA32SI: return DXGI_FORMAT_R32G32B32A32_SINT; + case SG_PIXELFORMAT_RGBA32F: return DXGI_FORMAT_R32G32B32A32_FLOAT; + case SG_PIXELFORMAT_DEPTH: return DXGI_FORMAT_R32_TYPELESS; + case SG_PIXELFORMAT_DEPTH_STENCIL: return DXGI_FORMAT_R24G8_TYPELESS; + case SG_PIXELFORMAT_BC1_RGBA: return DXGI_FORMAT_BC1_UNORM; + case SG_PIXELFORMAT_BC2_RGBA: return DXGI_FORMAT_BC2_UNORM; + case SG_PIXELFORMAT_BC3_RGBA: return DXGI_FORMAT_BC3_UNORM; + case SG_PIXELFORMAT_BC3_SRGBA: return DXGI_FORMAT_BC3_UNORM_SRGB; + case SG_PIXELFORMAT_BC4_R: return DXGI_FORMAT_BC4_UNORM; + case SG_PIXELFORMAT_BC4_RSN: return DXGI_FORMAT_BC4_SNORM; + case SG_PIXELFORMAT_BC5_RG: return DXGI_FORMAT_BC5_UNORM; + case SG_PIXELFORMAT_BC5_RGSN: return DXGI_FORMAT_BC5_SNORM; + case SG_PIXELFORMAT_BC6H_RGBF: return DXGI_FORMAT_BC6H_SF16; + case SG_PIXELFORMAT_BC6H_RGBUF: return DXGI_FORMAT_BC6H_UF16; + case SG_PIXELFORMAT_BC7_RGBA: return DXGI_FORMAT_BC7_UNORM; + case SG_PIXELFORMAT_BC7_SRGBA: return DXGI_FORMAT_BC7_UNORM_SRGB; + default: return DXGI_FORMAT_UNKNOWN; + }; +} + +_SOKOL_PRIVATE DXGI_FORMAT _sg_d3d11_srv_pixel_format(sg_pixel_format fmt) { + if (fmt == SG_PIXELFORMAT_DEPTH) { + return DXGI_FORMAT_R32_FLOAT; + } else if (fmt == SG_PIXELFORMAT_DEPTH_STENCIL) { + return DXGI_FORMAT_R24_UNORM_X8_TYPELESS; + } else { + return _sg_d3d11_texture_pixel_format(fmt); + } +} + +_SOKOL_PRIVATE DXGI_FORMAT _sg_d3d11_dsv_pixel_format(sg_pixel_format fmt) { + if (fmt == SG_PIXELFORMAT_DEPTH) { + return DXGI_FORMAT_D32_FLOAT; + } else if (fmt == SG_PIXELFORMAT_DEPTH_STENCIL) { + return DXGI_FORMAT_D24_UNORM_S8_UINT; + } else { + return _sg_d3d11_texture_pixel_format(fmt); + } +} + +_SOKOL_PRIVATE DXGI_FORMAT _sg_d3d11_rtv_uav_pixel_format(sg_pixel_format fmt) { + if (fmt == SG_PIXELFORMAT_DEPTH) { + return DXGI_FORMAT_R32_FLOAT; + } else if (fmt == SG_PIXELFORMAT_DEPTH_STENCIL) { + return DXGI_FORMAT_R24_UNORM_X8_TYPELESS; + } else { + return _sg_d3d11_texture_pixel_format(fmt); + } +} + +_SOKOL_PRIVATE D3D11_PRIMITIVE_TOPOLOGY _sg_d3d11_primitive_topology(sg_primitive_type prim_type) { + switch (prim_type) { + case SG_PRIMITIVETYPE_POINTS: return D3D11_PRIMITIVE_TOPOLOGY_POINTLIST; + case SG_PRIMITIVETYPE_LINES: return D3D11_PRIMITIVE_TOPOLOGY_LINELIST; + case SG_PRIMITIVETYPE_LINE_STRIP: return D3D11_PRIMITIVE_TOPOLOGY_LINESTRIP; + case SG_PRIMITIVETYPE_TRIANGLES: return D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST; + case SG_PRIMITIVETYPE_TRIANGLE_STRIP: return D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP; + default: SOKOL_UNREACHABLE; return (D3D11_PRIMITIVE_TOPOLOGY) 0; + } +} + +_SOKOL_PRIVATE DXGI_FORMAT _sg_d3d11_index_format(sg_index_type index_type) { + switch (index_type) { + case SG_INDEXTYPE_NONE: return DXGI_FORMAT_UNKNOWN; + case SG_INDEXTYPE_UINT16: return DXGI_FORMAT_R16_UINT; + case SG_INDEXTYPE_UINT32: return DXGI_FORMAT_R32_UINT; + default: SOKOL_UNREACHABLE; return (DXGI_FORMAT) 0; + } +} + +_SOKOL_PRIVATE D3D11_FILTER _sg_d3d11_filter(sg_filter min_f, sg_filter mag_f, sg_filter mipmap_f, bool comparison, uint32_t max_anisotropy) { + uint32_t d3d11_filter = 0; + if (max_anisotropy > 1) { + // D3D11_FILTER_ANISOTROPIC = 0x55, + d3d11_filter |= 0x55; + } else { + // D3D11_FILTER_MIN_MAG_MIP_POINT = 0, + // D3D11_FILTER_MIN_MAG_POINT_MIP_LINEAR = 0x1, + // D3D11_FILTER_MIN_POINT_MAG_LINEAR_MIP_POINT = 0x4, + // D3D11_FILTER_MIN_POINT_MAG_MIP_LINEAR = 0x5, + // D3D11_FILTER_MIN_LINEAR_MAG_MIP_POINT = 0x10, + // D3D11_FILTER_MIN_LINEAR_MAG_POINT_MIP_LINEAR = 0x11, + // D3D11_FILTER_MIN_MAG_LINEAR_MIP_POINT = 0x14, + // D3D11_FILTER_MIN_MAG_MIP_LINEAR = 0x15, + if (mipmap_f == SG_FILTER_LINEAR) { + d3d11_filter |= 0x01; + } + if (mag_f == SG_FILTER_LINEAR) { + d3d11_filter |= 0x04; + } + if (min_f == SG_FILTER_LINEAR) { + d3d11_filter |= 0x10; + } + } + // D3D11_FILTER_COMPARISON_MIN_MAG_MIP_POINT = 0x80, + // D3D11_FILTER_COMPARISON_MIN_MAG_POINT_MIP_LINEAR = 0x81, + // D3D11_FILTER_COMPARISON_MIN_POINT_MAG_LINEAR_MIP_POINT = 0x84, + // D3D11_FILTER_COMPARISON_MIN_POINT_MAG_MIP_LINEAR = 0x85, + // D3D11_FILTER_COMPARISON_MIN_LINEAR_MAG_MIP_POINT = 0x90, + // D3D11_FILTER_COMPARISON_MIN_LINEAR_MAG_POINT_MIP_LINEAR = 0x91, + // D3D11_FILTER_COMPARISON_MIN_MAG_LINEAR_MIP_POINT = 0x94, + // D3D11_FILTER_COMPARISON_MIN_MAG_MIP_LINEAR = 0x95, + // D3D11_FILTER_COMPARISON_ANISOTROPIC = 0xd5, + if (comparison) { + d3d11_filter |= 0x80; + } + return (D3D11_FILTER)d3d11_filter; +} + +_SOKOL_PRIVATE D3D11_TEXTURE_ADDRESS_MODE _sg_d3d11_address_mode(sg_wrap m) { + switch (m) { + case SG_WRAP_REPEAT: return D3D11_TEXTURE_ADDRESS_WRAP; + case SG_WRAP_CLAMP_TO_EDGE: return D3D11_TEXTURE_ADDRESS_CLAMP; + case SG_WRAP_CLAMP_TO_BORDER: return D3D11_TEXTURE_ADDRESS_BORDER; + case SG_WRAP_MIRRORED_REPEAT: return D3D11_TEXTURE_ADDRESS_MIRROR; + default: SOKOL_UNREACHABLE; return (D3D11_TEXTURE_ADDRESS_MODE) 0; + } +} + +_SOKOL_PRIVATE DXGI_FORMAT _sg_d3d11_vertex_format(sg_vertex_format fmt) { + switch (fmt) { + case SG_VERTEXFORMAT_FLOAT: return DXGI_FORMAT_R32_FLOAT; + case SG_VERTEXFORMAT_FLOAT2: return DXGI_FORMAT_R32G32_FLOAT; + case SG_VERTEXFORMAT_FLOAT3: return DXGI_FORMAT_R32G32B32_FLOAT; + case SG_VERTEXFORMAT_FLOAT4: return DXGI_FORMAT_R32G32B32A32_FLOAT; + case SG_VERTEXFORMAT_INT: return DXGI_FORMAT_R32_SINT; + case SG_VERTEXFORMAT_INT2: return DXGI_FORMAT_R32G32_SINT; + case SG_VERTEXFORMAT_INT3: return DXGI_FORMAT_R32G32B32_SINT; + case SG_VERTEXFORMAT_INT4: return DXGI_FORMAT_R32G32B32A32_SINT; + case SG_VERTEXFORMAT_UINT: return DXGI_FORMAT_R32_UINT; + case SG_VERTEXFORMAT_UINT2: return DXGI_FORMAT_R32G32_UINT; + case SG_VERTEXFORMAT_UINT3: return DXGI_FORMAT_R32G32B32_UINT; + case SG_VERTEXFORMAT_UINT4: return DXGI_FORMAT_R32G32B32A32_UINT; + case SG_VERTEXFORMAT_BYTE4: return DXGI_FORMAT_R8G8B8A8_SINT; + case SG_VERTEXFORMAT_BYTE4N: return DXGI_FORMAT_R8G8B8A8_SNORM; + case SG_VERTEXFORMAT_UBYTE4: return DXGI_FORMAT_R8G8B8A8_UINT; + case SG_VERTEXFORMAT_UBYTE4N: return DXGI_FORMAT_R8G8B8A8_UNORM; + case SG_VERTEXFORMAT_SHORT2: return DXGI_FORMAT_R16G16_SINT; + case SG_VERTEXFORMAT_SHORT2N: return DXGI_FORMAT_R16G16_SNORM; + case SG_VERTEXFORMAT_USHORT2: return DXGI_FORMAT_R16G16_UINT; + case SG_VERTEXFORMAT_USHORT2N: return DXGI_FORMAT_R16G16_UNORM; + case SG_VERTEXFORMAT_SHORT4: return DXGI_FORMAT_R16G16B16A16_SINT; + case SG_VERTEXFORMAT_SHORT4N: return DXGI_FORMAT_R16G16B16A16_SNORM; + case SG_VERTEXFORMAT_USHORT4: return DXGI_FORMAT_R16G16B16A16_UINT; + case SG_VERTEXFORMAT_USHORT4N: return DXGI_FORMAT_R16G16B16A16_UNORM; + case SG_VERTEXFORMAT_UINT10_N2: return DXGI_FORMAT_R10G10B10A2_UNORM; + case SG_VERTEXFORMAT_HALF2: return DXGI_FORMAT_R16G16_FLOAT; + case SG_VERTEXFORMAT_HALF4: return DXGI_FORMAT_R16G16B16A16_FLOAT; + default: SOKOL_UNREACHABLE; return (DXGI_FORMAT) 0; + } +} + +_SOKOL_PRIVATE D3D11_INPUT_CLASSIFICATION _sg_d3d11_input_classification(sg_vertex_step step) { + switch (step) { + case SG_VERTEXSTEP_PER_VERTEX: return D3D11_INPUT_PER_VERTEX_DATA; + case SG_VERTEXSTEP_PER_INSTANCE: return D3D11_INPUT_PER_INSTANCE_DATA; + default: SOKOL_UNREACHABLE; return (D3D11_INPUT_CLASSIFICATION) 0; + } +} + +_SOKOL_PRIVATE D3D11_CULL_MODE _sg_d3d11_cull_mode(sg_cull_mode m) { + switch (m) { + case SG_CULLMODE_NONE: return D3D11_CULL_NONE; + case SG_CULLMODE_FRONT: return D3D11_CULL_FRONT; + case SG_CULLMODE_BACK: return D3D11_CULL_BACK; + default: SOKOL_UNREACHABLE; return (D3D11_CULL_MODE) 0; + } +} + +_SOKOL_PRIVATE D3D11_COMPARISON_FUNC _sg_d3d11_compare_func(sg_compare_func f) { + switch (f) { + case SG_COMPAREFUNC_NEVER: return D3D11_COMPARISON_NEVER; + case SG_COMPAREFUNC_LESS: return D3D11_COMPARISON_LESS; + case SG_COMPAREFUNC_EQUAL: return D3D11_COMPARISON_EQUAL; + case SG_COMPAREFUNC_LESS_EQUAL: return D3D11_COMPARISON_LESS_EQUAL; + case SG_COMPAREFUNC_GREATER: return D3D11_COMPARISON_GREATER; + case SG_COMPAREFUNC_NOT_EQUAL: return D3D11_COMPARISON_NOT_EQUAL; + case SG_COMPAREFUNC_GREATER_EQUAL: return D3D11_COMPARISON_GREATER_EQUAL; + case SG_COMPAREFUNC_ALWAYS: return D3D11_COMPARISON_ALWAYS; + default: SOKOL_UNREACHABLE; return (D3D11_COMPARISON_FUNC) 0; + } +} + +_SOKOL_PRIVATE D3D11_STENCIL_OP _sg_d3d11_stencil_op(sg_stencil_op op) { + switch (op) { + case SG_STENCILOP_KEEP: return D3D11_STENCIL_OP_KEEP; + case SG_STENCILOP_ZERO: return D3D11_STENCIL_OP_ZERO; + case SG_STENCILOP_REPLACE: return D3D11_STENCIL_OP_REPLACE; + case SG_STENCILOP_INCR_CLAMP: return D3D11_STENCIL_OP_INCR_SAT; + case SG_STENCILOP_DECR_CLAMP: return D3D11_STENCIL_OP_DECR_SAT; + case SG_STENCILOP_INVERT: return D3D11_STENCIL_OP_INVERT; + case SG_STENCILOP_INCR_WRAP: return D3D11_STENCIL_OP_INCR; + case SG_STENCILOP_DECR_WRAP: return D3D11_STENCIL_OP_DECR; + default: SOKOL_UNREACHABLE; return (D3D11_STENCIL_OP) 0; + } +} + +_SOKOL_PRIVATE D3D11_BLEND _sg_d3d11_blend_factor(sg_blend_factor f) { + switch (f) { + case SG_BLENDFACTOR_ZERO: return D3D11_BLEND_ZERO; + case SG_BLENDFACTOR_ONE: return D3D11_BLEND_ONE; + case SG_BLENDFACTOR_SRC_COLOR: return D3D11_BLEND_SRC_COLOR; + case SG_BLENDFACTOR_ONE_MINUS_SRC_COLOR: return D3D11_BLEND_INV_SRC_COLOR; + case SG_BLENDFACTOR_SRC_ALPHA: return D3D11_BLEND_SRC_ALPHA; + case SG_BLENDFACTOR_ONE_MINUS_SRC_ALPHA: return D3D11_BLEND_INV_SRC_ALPHA; + case SG_BLENDFACTOR_DST_COLOR: return D3D11_BLEND_DEST_COLOR; + case SG_BLENDFACTOR_ONE_MINUS_DST_COLOR: return D3D11_BLEND_INV_DEST_COLOR; + case SG_BLENDFACTOR_DST_ALPHA: return D3D11_BLEND_DEST_ALPHA; + case SG_BLENDFACTOR_ONE_MINUS_DST_ALPHA: return D3D11_BLEND_INV_DEST_ALPHA; + case SG_BLENDFACTOR_SRC_ALPHA_SATURATED: return D3D11_BLEND_SRC_ALPHA_SAT; + case SG_BLENDFACTOR_BLEND_COLOR: return D3D11_BLEND_BLEND_FACTOR; + case SG_BLENDFACTOR_ONE_MINUS_BLEND_COLOR: return D3D11_BLEND_INV_BLEND_FACTOR; + case SG_BLENDFACTOR_BLEND_ALPHA: return D3D11_BLEND_BLEND_FACTOR; + case SG_BLENDFACTOR_ONE_MINUS_BLEND_ALPHA: return D3D11_BLEND_INV_BLEND_FACTOR; + default: SOKOL_UNREACHABLE; return (D3D11_BLEND) 0; + } +} + +_SOKOL_PRIVATE D3D11_BLEND_OP _sg_d3d11_blend_op(sg_blend_op op) { + switch (op) { + case SG_BLENDOP_ADD: return D3D11_BLEND_OP_ADD; + case SG_BLENDOP_SUBTRACT: return D3D11_BLEND_OP_SUBTRACT; + case SG_BLENDOP_REVERSE_SUBTRACT: return D3D11_BLEND_OP_REV_SUBTRACT; + case SG_BLENDOP_MIN: return D3D11_BLEND_OP_MIN; + case SG_BLENDOP_MAX: return D3D11_BLEND_OP_MAX; + default: SOKOL_UNREACHABLE; return (D3D11_BLEND_OP) 0; + } +} + +_SOKOL_PRIVATE UINT8 _sg_d3d11_color_write_mask(sg_color_mask m) { + UINT8 res = 0; + if (m & SG_COLORMASK_R) { + res |= D3D11_COLOR_WRITE_ENABLE_RED; + } + if (m & SG_COLORMASK_G) { + res |= D3D11_COLOR_WRITE_ENABLE_GREEN; + } + if (m & SG_COLORMASK_B) { + res |= D3D11_COLOR_WRITE_ENABLE_BLUE; + } + if (m & SG_COLORMASK_A) { + res |= D3D11_COLOR_WRITE_ENABLE_ALPHA; + } + return res; +} + +_SOKOL_PRIVATE UINT _sg_d3d11_dxgi_fmt_caps(DXGI_FORMAT dxgi_fmt) { + UINT dxgi_fmt_caps = 0; + if (dxgi_fmt != DXGI_FORMAT_UNKNOWN) { + HRESULT hr = _sg_d3d11_CheckFormatSupport(_sg.d3d11.dev, dxgi_fmt, &dxgi_fmt_caps); + SOKOL_ASSERT(SUCCEEDED(hr) || (E_FAIL == hr)); + if (!SUCCEEDED(hr)) { + dxgi_fmt_caps = 0; + } + } + return dxgi_fmt_caps; +} + +// see: https://docs.microsoft.com/en-us/windows/win32/direct3d11/overviews-direct3d-11-resources-limits#resource-limits-for-feature-level-11-hardware +_SOKOL_PRIVATE void _sg_d3d11_init_caps(void) { + _sg.backend = SG_BACKEND_D3D11; + + _sg.features.origin_top_left = true; + _sg.features.image_clamp_to_border = true; + _sg.features.mrt_independent_blend_state = true; + _sg.features.mrt_independent_write_mask = true; + _sg.features.compute = true; + _sg.features.msaa_texture_bindings = true; + _sg.features.draw_base_vertex = true; + _sg.features.draw_base_instance = true; + + _sg.limits.max_image_size_2d = 16 * 1024; + _sg.limits.max_image_size_cube = 16 * 1024; + _sg.limits.max_image_size_3d = 2 * 1024; + _sg.limits.max_image_size_array = 16 * 1024; + _sg.limits.max_image_array_layers = _SG_D3D11_MAX_TEXTUREARRAY_LAYERS; + _sg.limits.max_vertex_attrs = SG_MAX_VERTEX_ATTRIBUTES; + _sg.limits.max_color_attachments = _sg_min(8, SG_MAX_COLOR_ATTACHMENTS); + _sg.limits.max_texture_bindings_per_stage = _sg_min(128, SG_MAX_VIEW_BINDSLOTS); + _sg.limits.max_storage_buffer_bindings_per_stage = _sg_min(64, SG_MAX_VIEW_BINDSLOTS); + if (_sg_d3d11_GetFeatureLevel(_sg.d3d11.dev) >= D3D_FEATURE_LEVEL_11_1) { + _sg.limits.d3d11_max_unordered_access_views = _sg_min(64, SG_MAX_VIEW_BINDSLOTS); + } else { + _sg.limits.d3d11_max_unordered_access_views = _sg_min(8, SG_MAX_VIEW_BINDSLOTS); + } + _sg.limits.max_storage_image_bindings_per_stage = _sg.limits.d3d11_max_unordered_access_views; + + // see: https://docs.microsoft.com/en-us/windows/win32/api/d3d11/ne-d3d11-d3d11_format_support + for (int fmt = (SG_PIXELFORMAT_NONE+1); fmt < _SG_PIXELFORMAT_NUM; fmt++) { + const UINT srv_dxgi_fmt_caps = _sg_d3d11_dxgi_fmt_caps(_sg_d3d11_srv_pixel_format((sg_pixel_format)fmt)); + const UINT rtv_uav_dxgi_fmt_caps = _sg_d3d11_dxgi_fmt_caps(_sg_d3d11_rtv_uav_pixel_format((sg_pixel_format)fmt)); + const UINT dsv_dxgi_fmt_caps = _sg_d3d11_dxgi_fmt_caps(_sg_d3d11_dsv_pixel_format((sg_pixel_format)fmt)); + _sg_pixelformat_info_t* info = &_sg.formats[fmt]; + const bool render = 0 != (rtv_uav_dxgi_fmt_caps & D3D11_FORMAT_SUPPORT_RENDER_TARGET); + const bool depth = 0 != (dsv_dxgi_fmt_caps & D3D11_FORMAT_SUPPORT_DEPTH_STENCIL); + info->sample = 0 != (srv_dxgi_fmt_caps & D3D11_FORMAT_SUPPORT_TEXTURE2D); + info->filter = 0 != (srv_dxgi_fmt_caps & D3D11_FORMAT_SUPPORT_SHADER_SAMPLE); + info->render = render || depth; + if (depth) { + info->blend = 0 != (dsv_dxgi_fmt_caps & D3D11_FORMAT_SUPPORT_BLENDABLE); + info->msaa = 0 != (dsv_dxgi_fmt_caps & D3D11_FORMAT_SUPPORT_MULTISAMPLE_RENDERTARGET); + } else { + info->blend = 0 != (rtv_uav_dxgi_fmt_caps & D3D11_FORMAT_SUPPORT_BLENDABLE); + info->msaa = 0 != (rtv_uav_dxgi_fmt_caps & D3D11_FORMAT_SUPPORT_MULTISAMPLE_RENDERTARGET); + } + info->depth = depth; + info->read = info->write = 0 != (rtv_uav_dxgi_fmt_caps & D3D11_FORMAT_SUPPORT_TYPED_UNORDERED_ACCESS_VIEW); + } +} + +_SOKOL_PRIVATE void _sg_d3d11_setup_backend(const sg_desc* desc) { + // assume _sg.d3d11 already is zero-initialized + SOKOL_ASSERT(desc); + SOKOL_ASSERT(desc->environment.d3d11.device); + SOKOL_ASSERT(desc->environment.d3d11.device_context); + _sg.d3d11.valid = true; + _sg.d3d11.dev = (ID3D11Device*) desc->environment.d3d11.device; + _sg.d3d11.ctx = (ID3D11DeviceContext*) desc->environment.d3d11.device_context; + _sg_d3d11_init_caps(); + if (_sg_d3d11_GetFeatureLevel(_sg.d3d11.dev) == D3D_FEATURE_LEVEL_11_0) { + _SG_WARN(D3D11_FEATURE_LEVEL_0_DETECTED); + } +} + +_SOKOL_PRIVATE void _sg_d3d11_discard_backend(void) { + SOKOL_ASSERT(_sg.d3d11.valid); + _sg.d3d11.valid = false; +} + +_SOKOL_PRIVATE void _sg_d3d11_clear_state(void) { + // clear all the device context state, so that resource refs don't keep stuck in the d3d device context + _sg_d3d11_ClearState(_sg.d3d11.ctx); +} + +_SOKOL_PRIVATE void _sg_d3d11_reset_state_cache(void) { + // there's currently no state cache in the D3D11 backend, so this is a no-op +} + +_SOKOL_PRIVATE sg_resource_state _sg_d3d11_create_buffer(_sg_buffer_t* buf, const sg_buffer_desc* desc) { + SOKOL_ASSERT(buf && desc); + SOKOL_ASSERT(!buf->d3d11.buf); + const bool injected = (0 != desc->d3d11_buffer); + if (injected) { + buf->d3d11.buf = (ID3D11Buffer*) desc->d3d11_buffer; + _sg_d3d11_AddRef(buf->d3d11.buf); + } else { + D3D11_BUFFER_DESC d3d11_buf_desc; + _sg_clear(&d3d11_buf_desc, sizeof(d3d11_buf_desc)); + d3d11_buf_desc.ByteWidth = (UINT)buf->cmn.size; + d3d11_buf_desc.Usage = _sg_d3d11_buffer_usage(&buf->cmn.usage); + d3d11_buf_desc.BindFlags = _sg_d3d11_buffer_bind_flags(&buf->cmn.usage); + d3d11_buf_desc.CPUAccessFlags = _sg_d3d11_buffer_cpu_access_flags(&buf->cmn.usage); + d3d11_buf_desc.MiscFlags = _sg_d3d11_buffer_misc_flags(&buf->cmn.usage); + D3D11_SUBRESOURCE_DATA* init_data_ptr = 0; + D3D11_SUBRESOURCE_DATA init_data; + _sg_clear(&init_data, sizeof(init_data)); + if (desc->data.ptr) { + init_data.pSysMem = desc->data.ptr; + init_data_ptr = &init_data; + } + HRESULT hr = _sg_d3d11_CreateBuffer(_sg.d3d11.dev, &d3d11_buf_desc, init_data_ptr, &buf->d3d11.buf); + if (!(SUCCEEDED(hr) && buf->d3d11.buf)) { + _SG_ERROR(D3D11_CREATE_BUFFER_FAILED); + return SG_RESOURCESTATE_FAILED; + } + _sg_d3d11_setlabel(buf->d3d11.buf, desc->label); + } + return SG_RESOURCESTATE_VALID; +} + +_SOKOL_PRIVATE void _sg_d3d11_discard_buffer(_sg_buffer_t* buf) { + SOKOL_ASSERT(buf); + if (buf->d3d11.buf) { + _sg_d3d11_Release(buf->d3d11.buf); + } +} + +_SOKOL_PRIVATE void _sg_d3d11_fill_subres_data(const _sg_image_t* img, const sg_image_data* data) { + const int num_slices = (img->cmn.type == SG_IMAGETYPE_3D) ? 1 : img->cmn.num_slices; + int subres_index = 0; + for (int slice_index = 0; slice_index < num_slices; slice_index++) { + for (int mip_index = 0; mip_index < img->cmn.num_mipmaps; mip_index++, subres_index++) { + SOKOL_ASSERT(subres_index < _SG_D3D11_MAX_TEXTURE_SUBRESOURCES); + D3D11_SUBRESOURCE_DATA* subres_data = &_sg.d3d11.subres_data[subres_index]; + const int mip_width = _sg_miplevel_dim(img->cmn.width, mip_index); + const int mip_height = _sg_miplevel_dim(img->cmn.height, mip_index); + const sg_range* miplevel_data = &(data->mip_levels[mip_index]); + const size_t slice_size = miplevel_data->size / (size_t)num_slices; + const size_t slice_offset = slice_size * (size_t)slice_index; + const uint8_t* ptr = (const uint8_t*) miplevel_data->ptr; + subres_data->pSysMem = ptr + slice_offset; + subres_data->SysMemPitch = (UINT)_sg_row_pitch(img->cmn.pixel_format, mip_width, 1); + if (img->cmn.type == SG_IMAGETYPE_3D) { + subres_data->SysMemSlicePitch = (UINT)_sg_surface_pitch(img->cmn.pixel_format, mip_width, mip_height, 1); + } else { + subres_data->SysMemSlicePitch = 0; + } + } + } +} + +_SOKOL_PRIVATE sg_resource_state _sg_d3d11_create_image(_sg_image_t* img, const sg_image_desc* desc) { + SOKOL_ASSERT(img && desc); + SOKOL_ASSERT((0 == img->d3d11.tex2d) && (0 == img->d3d11.tex3d) && (0 == img->d3d11.res)); + HRESULT hr; + + const bool injected = (0 != desc->d3d11_texture); + const bool msaa = (img->cmn.sample_count > 1); + SOKOL_ASSERT(!(msaa && (img->cmn.type == SG_IMAGETYPE_CUBE))); + img->d3d11.format = _sg_d3d11_texture_pixel_format(img->cmn.pixel_format); + if (img->d3d11.format == DXGI_FORMAT_UNKNOWN) { + _SG_ERROR(D3D11_CREATE_2D_TEXTURE_UNSUPPORTED_PIXEL_FORMAT); + return SG_RESOURCESTATE_FAILED; + } + + // prepare initial content pointers + D3D11_SUBRESOURCE_DATA* init_data = 0; + if (!injected && desc->data.mip_levels[0].ptr) { + _sg_d3d11_fill_subres_data(img, &desc->data); + init_data = _sg.d3d11.subres_data; + } + if (img->cmn.type != SG_IMAGETYPE_3D) { + // 2D-, cube- or array-texture + // first check for injected texture and/or resource view + if (injected) { + img->d3d11.tex2d = (ID3D11Texture2D*) desc->d3d11_texture; + _sg_d3d11_AddRef(img->d3d11.tex2d); + } else { + // if not injected, create 2D texture + D3D11_TEXTURE2D_DESC d3d11_tex_desc; + _sg_clear(&d3d11_tex_desc, sizeof(d3d11_tex_desc)); + d3d11_tex_desc.Width = (UINT)img->cmn.width; + d3d11_tex_desc.Height = (UINT)img->cmn.height; + d3d11_tex_desc.MipLevels = (UINT)img->cmn.num_mipmaps; + d3d11_tex_desc.ArraySize = (UINT)img->cmn.num_slices; + d3d11_tex_desc.Format = img->d3d11.format; + d3d11_tex_desc.BindFlags = _sg_d3d11_image_bind_flags(&img->cmn.usage); + d3d11_tex_desc.Usage = _sg_d3d11_image_usage(&img->cmn.usage); + d3d11_tex_desc.CPUAccessFlags = _sg_d3d11_image_cpu_access_flags(&img->cmn.usage); + d3d11_tex_desc.SampleDesc.Count = (UINT)img->cmn.sample_count; + d3d11_tex_desc.SampleDesc.Quality = (UINT) (msaa ? D3D11_STANDARD_MULTISAMPLE_PATTERN : 0); + d3d11_tex_desc.MiscFlags = (img->cmn.type == SG_IMAGETYPE_CUBE) ? D3D11_RESOURCE_MISC_TEXTURECUBE : 0; + hr = _sg_d3d11_CreateTexture2D(_sg.d3d11.dev, &d3d11_tex_desc, init_data, &img->d3d11.tex2d); + if (!(SUCCEEDED(hr) && img->d3d11.tex2d)) { + _SG_ERROR(D3D11_CREATE_2D_TEXTURE_FAILED); + return SG_RESOURCESTATE_FAILED; + } + _sg_d3d11_setlabel(img->d3d11.tex2d, desc->label); + } + SOKOL_ASSERT(img->d3d11.tex2d); + img->d3d11.res = (ID3D11Resource*)img->d3d11.tex2d; + _sg_d3d11_AddRef(img->d3d11.res); + } else { + // 3D texture - same procedure, first check if injected, than create non-injected + if (injected) { + img->d3d11.tex3d = (ID3D11Texture3D*) desc->d3d11_texture; + _sg_d3d11_AddRef(img->d3d11.tex3d); + } else { + // not injected, create 3d texture + D3D11_TEXTURE3D_DESC d3d11_tex_desc; + _sg_clear(&d3d11_tex_desc, sizeof(d3d11_tex_desc)); + d3d11_tex_desc.Width = (UINT)img->cmn.width; + d3d11_tex_desc.Height = (UINT)img->cmn.height; + d3d11_tex_desc.Depth = (UINT)img->cmn.num_slices; + d3d11_tex_desc.MipLevels = (UINT)img->cmn.num_mipmaps; + d3d11_tex_desc.Format = img->d3d11.format; + d3d11_tex_desc.BindFlags = _sg_d3d11_image_bind_flags(&img->cmn.usage); + d3d11_tex_desc.Usage = _sg_d3d11_image_usage(&img->cmn.usage); + d3d11_tex_desc.CPUAccessFlags = _sg_d3d11_image_cpu_access_flags(&img->cmn.usage); + if (img->d3d11.format == DXGI_FORMAT_UNKNOWN) { + _SG_ERROR(D3D11_CREATE_3D_TEXTURE_UNSUPPORTED_PIXEL_FORMAT); + return SG_RESOURCESTATE_FAILED; + } + hr = _sg_d3d11_CreateTexture3D(_sg.d3d11.dev, &d3d11_tex_desc, init_data, &img->d3d11.tex3d); + if (!(SUCCEEDED(hr) && img->d3d11.tex3d)) { + _SG_ERROR(D3D11_CREATE_3D_TEXTURE_FAILED); + return SG_RESOURCESTATE_FAILED; + } + _sg_d3d11_setlabel(img->d3d11.tex3d, desc->label); + } + SOKOL_ASSERT(img->d3d11.tex3d); + img->d3d11.res = (ID3D11Resource*)img->d3d11.tex3d; + _sg_d3d11_AddRef(img->d3d11.res); + } + return SG_RESOURCESTATE_VALID; +} + +_SOKOL_PRIVATE void _sg_d3d11_discard_image(_sg_image_t* img) { + SOKOL_ASSERT(img); + if (img->d3d11.tex2d) { + _sg_d3d11_Release(img->d3d11.tex2d); + } + if (img->d3d11.tex3d) { + _sg_d3d11_Release(img->d3d11.tex3d); + } + if (img->d3d11.res) { + _sg_d3d11_Release(img->d3d11.res); + } +} + +_SOKOL_PRIVATE sg_resource_state _sg_d3d11_create_sampler(_sg_sampler_t* smp, const sg_sampler_desc* desc) { + SOKOL_ASSERT(smp && desc); + SOKOL_ASSERT(0 == smp->d3d11.smp); + const bool injected = (0 != desc->d3d11_sampler); + if (injected) { + smp->d3d11.smp = (ID3D11SamplerState*)desc->d3d11_sampler; + _sg_d3d11_AddRef(smp->d3d11.smp); + } else { + D3D11_SAMPLER_DESC d3d11_smp_desc; + _sg_clear(&d3d11_smp_desc, sizeof(d3d11_smp_desc)); + d3d11_smp_desc.Filter = _sg_d3d11_filter(desc->min_filter, desc->mag_filter, desc->mipmap_filter, desc->compare != SG_COMPAREFUNC_NEVER, desc->max_anisotropy); + d3d11_smp_desc.AddressU = _sg_d3d11_address_mode(desc->wrap_u); + d3d11_smp_desc.AddressV = _sg_d3d11_address_mode(desc->wrap_v); + d3d11_smp_desc.AddressW = _sg_d3d11_address_mode(desc->wrap_w); + d3d11_smp_desc.MipLODBias = 0.0f; // FIXME? + switch (desc->border_color) { + case SG_BORDERCOLOR_TRANSPARENT_BLACK: + // all 0.0f + break; + case SG_BORDERCOLOR_OPAQUE_WHITE: + for (int i = 0; i < 4; i++) { + d3d11_smp_desc.BorderColor[i] = 1.0f; + } + break; + default: + // opaque black + d3d11_smp_desc.BorderColor[3] = 1.0f; + break; + } + d3d11_smp_desc.MaxAnisotropy = desc->max_anisotropy; + d3d11_smp_desc.ComparisonFunc = _sg_d3d11_compare_func(desc->compare); + d3d11_smp_desc.MinLOD = desc->min_lod; + d3d11_smp_desc.MaxLOD = desc->max_lod; + HRESULT hr = _sg_d3d11_CreateSamplerState(_sg.d3d11.dev, &d3d11_smp_desc, &smp->d3d11.smp); + if (!(SUCCEEDED(hr) && smp->d3d11.smp)) { + _SG_ERROR(D3D11_CREATE_SAMPLER_STATE_FAILED); + return SG_RESOURCESTATE_FAILED; + } + _sg_d3d11_setlabel(smp->d3d11.smp, desc->label); + } + return SG_RESOURCESTATE_VALID; +} + +_SOKOL_PRIVATE void _sg_d3d11_discard_sampler(_sg_sampler_t* smp) { + SOKOL_ASSERT(smp); + if (smp->d3d11.smp) { + _sg_d3d11_Release(smp->d3d11.smp); + } +} + +_SOKOL_PRIVATE bool _sg_d3d11_load_d3dcompiler_dll(void) { + if ((0 == _sg.d3d11.d3dcompiler_dll) && !_sg.d3d11.d3dcompiler_dll_load_failed) { + _sg.d3d11.d3dcompiler_dll = LoadLibraryA("d3dcompiler_47.dll"); + if (0 == _sg.d3d11.d3dcompiler_dll) { + // don't attempt to load missing DLL in the future + _SG_ERROR(D3D11_LOAD_D3DCOMPILER_47_DLL_FAILED); + _sg.d3d11.d3dcompiler_dll_load_failed = true; + return false; + } + // look up function pointers + _sg.d3d11.D3DCompile_func = (pD3DCompile)(void*) GetProcAddress(_sg.d3d11.d3dcompiler_dll, "D3DCompile"); + SOKOL_ASSERT(_sg.d3d11.D3DCompile_func); + } + return 0 != _sg.d3d11.d3dcompiler_dll; +} + +_SOKOL_PRIVATE ID3DBlob* _sg_d3d11_compile_shader(const sg_shader_function* shd_func) { + if (!_sg_d3d11_load_d3dcompiler_dll()) { + return NULL; + } + SOKOL_ASSERT(shd_func->d3d11_target); + UINT flags1 = D3DCOMPILE_PACK_MATRIX_COLUMN_MAJOR; + if (_sg.desc.d3d11_shader_debugging) { + flags1 |= D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION; + } else { + flags1 |= D3DCOMPILE_OPTIMIZATION_LEVEL3; + } + ID3DBlob* output = NULL; + ID3DBlob* errors_or_warnings = NULL; + HRESULT hr = _sg.d3d11.D3DCompile_func( + shd_func->source, // pSrcData + strlen(shd_func->source), // SrcDataSize + shd_func->d3d11_filepath, // pSourceName + NULL, // pDefines + D3D_COMPILE_STANDARD_FILE_INCLUDE, // pInclude + shd_func->entry ? shd_func->entry : "main", // pEntryPoint + shd_func->d3d11_target, // pTarget + flags1, // Flags1 + 0, // Flags2 + &output, // ppCode + &errors_or_warnings); // ppErrorMsgs + if (FAILED(hr)) { + _SG_ERROR(D3D11_SHADER_COMPILATION_FAILED); + } + if (errors_or_warnings) { + _SG_WARN(D3D11_SHADER_COMPILATION_OUTPUT); + _SG_LOGMSG(D3D11_SHADER_COMPILATION_OUTPUT, (LPCSTR)_sg_d3d11_GetBufferPointer(errors_or_warnings)); + _sg_d3d11_Release(errors_or_warnings); errors_or_warnings = NULL; + } + if (FAILED(hr)) { + // just in case, usually output is NULL here + if (output) { + _sg_d3d11_Release(output); + output = NULL; + } + } + return output; +} + +// NOTE: this is an out-of-range check for HLSL bindslots that's also active in release mode +_SOKOL_PRIVATE bool _sg_d3d11_ensure_hlsl_bindslot_ranges(const sg_shader_desc* desc) { + SOKOL_ASSERT(desc); + for (size_t i = 0; i < SG_MAX_UNIFORMBLOCK_BINDSLOTS; i++) { + const sg_shader_uniform_block* ub = &desc->uniform_blocks[i]; + if (ub->stage != SG_SHADERSTAGE_NONE) { + if (ub->hlsl_register_b_n >= _SG_D3D11_MAX_STAGE_UB_BINDINGS) { + _SG_ERROR(D3D11_UNIFORMBLOCK_HLSL_REGISTER_B_OUT_OF_RANGE); + return false; + } + } + } + for (size_t i = 0; i < SG_MAX_VIEW_BINDSLOTS; i++) { + const sg_shader_view* view = &desc->views[i]; + if (view->texture.stage != SG_SHADERSTAGE_NONE) { + if (view->texture.hlsl_register_t_n >= _SG_D3D11_MAX_STAGE_SRV_BINDINGS) { + _SG_ERROR(D3D11_IMAGE_HLSL_REGISTER_T_OUT_OF_RANGE); + return false; + } + } + if (view->storage_buffer.stage != SG_SHADERSTAGE_NONE) { + if (view->storage_buffer.hlsl_register_t_n >= _SG_D3D11_MAX_STAGE_SRV_BINDINGS) { + _SG_ERROR(D3D11_STORAGEBUFFER_HLSL_REGISTER_T_OUT_OF_RANGE); + return false; + } + if (view->storage_buffer.hlsl_register_u_n >= _SG_D3D11_MAX_STAGE_UAV_BINDINGS) { + _SG_ERROR(D3D11_STORAGEBUFFER_HLSL_REGISTER_U_OUT_OF_RANGE); + return false; + } + } + if (view->storage_image.stage != SG_SHADERSTAGE_NONE) { + if (view->storage_image.hlsl_register_u_n >= _SG_D3D11_MAX_STAGE_UAV_BINDINGS) { + _SG_ERROR(D3D11_STORAGEIMAGE_HLSL_REGISTER_U_OUT_OF_RANGE); + return false; + } + } + } + for (size_t i = 0; i < SG_MAX_SAMPLER_BINDSLOTS; i++) { + const sg_shader_sampler* smp = &desc->samplers[i]; + if (smp->stage != SG_SHADERSTAGE_NONE) { + if (smp->hlsl_register_s_n >= _SG_D3D11_MAX_STAGE_SMP_BINDINGS) { + _SG_ERROR(D3D11_SAMPLER_HLSL_REGISTER_S_OUT_OF_RANGE); + return false; + } + } + } + return true; +} + +_SOKOL_PRIVATE sg_resource_state _sg_d3d11_create_shader(_sg_shader_t* shd, const sg_shader_desc* desc) { + SOKOL_ASSERT(shd && desc); + SOKOL_ASSERT(!shd->d3d11.vs && !shd->d3d11.fs && !shd->d3d11.cs && !shd->d3d11.vs_blob); + HRESULT hr; + + // perform a range-check on HLSL bindslots that's also active in release + // mode to avoid potential out-of-bounds array accesses + if (!_sg_d3d11_ensure_hlsl_bindslot_ranges(desc)) { + return SG_RESOURCESTATE_FAILED; + } + + // copy vertex attribute semantic names and indices + for (size_t i = 0; i < SG_MAX_VERTEX_ATTRIBUTES; i++) { + _sg_strcpy(&shd->d3d11.attrs[i].sem_name, desc->attrs[i].hlsl_sem_name); + shd->d3d11.attrs[i].sem_index = desc->attrs[i].hlsl_sem_index; + } + + // copy HLSL bind slots + for (size_t i = 0; i < SG_MAX_UNIFORMBLOCK_BINDSLOTS; i++) { + SOKOL_ASSERT(0 == shd->d3d11.ub_register_b_n[i]); + shd->d3d11.ub_register_b_n[i] = desc->uniform_blocks[i].hlsl_register_b_n; + } + for (size_t i = 0; i < SG_MAX_VIEW_BINDSLOTS; i++) { + const sg_shader_view* view = &desc->views[i]; + SOKOL_ASSERT((0 == shd->d3d11.view_register_t_n[i]) && (0 == shd->d3d11.view_register_u_n[i])); + if (view->storage_buffer.stage != SG_SHADERSTAGE_NONE) { + shd->d3d11.view_register_t_n[i] = view->storage_buffer.hlsl_register_t_n; + shd->d3d11.view_register_u_n[i] = view->storage_buffer.hlsl_register_u_n; + } else if (view->texture.stage != SG_SHADERSTAGE_NONE) { + shd->d3d11.view_register_t_n[i] = view->texture.hlsl_register_t_n; + } else if (view->storage_image.stage != SG_SHADERSTAGE_NONE) { + shd->d3d11.view_register_u_n[i] = view->storage_image.hlsl_register_u_n; + } + } + for (size_t i = 0; i < SG_MAX_SAMPLER_BINDSLOTS; i++) { + SOKOL_ASSERT(0 == shd->d3d11.smp_register_s_n[i]); + shd->d3d11.smp_register_s_n[i] = desc->samplers[i].hlsl_register_s_n; + } + + // create a D3D constant buffer for each uniform block + for (size_t ub_index = 0; ub_index < SG_MAX_UNIFORMBLOCK_BINDSLOTS; ub_index++) { + const sg_shader_stage stage = desc->uniform_blocks[ub_index].stage; + if (stage == SG_SHADERSTAGE_NONE) { + continue; + } + const _sg_shader_uniform_block_t* ub = &shd->cmn.uniform_blocks[ub_index]; + ID3D11Buffer* cbuf = 0; + D3D11_BUFFER_DESC cb_desc; + _sg_clear(&cb_desc, sizeof(cb_desc)); + cb_desc.ByteWidth = (UINT)_sg_roundup((int)ub->size, 16); + cb_desc.Usage = D3D11_USAGE_DEFAULT; + cb_desc.BindFlags = D3D11_BIND_CONSTANT_BUFFER; + hr = _sg_d3d11_CreateBuffer(_sg.d3d11.dev, &cb_desc, NULL, &cbuf); + if (!(SUCCEEDED(hr) && cbuf)) { + _SG_ERROR(D3D11_CREATE_CONSTANT_BUFFER_FAILED); + return SG_RESOURCESTATE_FAILED; + } + _sg_d3d11_setlabel(cbuf, desc->label); + shd->d3d11.all_cbufs[ub_index] = cbuf; + + const uint8_t d3d11_slot = shd->d3d11.ub_register_b_n[ub_index]; + SOKOL_ASSERT(d3d11_slot < _SG_D3D11_MAX_STAGE_UB_BINDINGS); + if (stage == SG_SHADERSTAGE_VERTEX) { + SOKOL_ASSERT(0 == shd->d3d11.vs_cbufs[d3d11_slot]); + shd->d3d11.vs_cbufs[d3d11_slot] = cbuf; + } else if (stage == SG_SHADERSTAGE_FRAGMENT) { + SOKOL_ASSERT(0 == shd->d3d11.fs_cbufs[d3d11_slot]); + shd->d3d11.fs_cbufs[d3d11_slot] = cbuf; + } else if (stage == SG_SHADERSTAGE_COMPUTE) { + SOKOL_ASSERT(0 == shd->d3d11.cs_cbufs[d3d11_slot]); + shd->d3d11.cs_cbufs[d3d11_slot] = cbuf; + } else { + SOKOL_UNREACHABLE; + } + } + + // create shader functions + const bool has_vs = desc->vertex_func.bytecode.ptr || desc->vertex_func.source; + const bool has_fs = desc->fragment_func.bytecode.ptr || desc->fragment_func.source; + const bool has_cs = desc->compute_func.bytecode.ptr || desc->compute_func.source; + bool vs_valid = false; bool fs_valid = false; bool cs_valid = false; + if (has_vs) { + const void* vs_ptr = 0; SIZE_T vs_length = 0; + ID3DBlob* vs_blob = 0; + if (desc->vertex_func.bytecode.ptr) { + SOKOL_ASSERT(desc->vertex_func.bytecode.size > 0); + vs_ptr = desc->vertex_func.bytecode.ptr; + vs_length = desc->vertex_func.bytecode.size; + } else { + SOKOL_ASSERT(desc->vertex_func.source); + vs_blob = _sg_d3d11_compile_shader(&desc->vertex_func); + if (vs_blob) { + vs_ptr = _sg_d3d11_GetBufferPointer(vs_blob); + vs_length = _sg_d3d11_GetBufferSize(vs_blob); + } + } + if (vs_ptr && (vs_length > 0)) { + hr = _sg_d3d11_CreateVertexShader(_sg.d3d11.dev, vs_ptr, vs_length, NULL, &shd->d3d11.vs); + vs_valid = SUCCEEDED(hr) && shd->d3d11.vs; + } + // set label, and need to store a copy of the vertex shader blob for the pipeline creation + if (vs_valid) { + _sg_d3d11_setlabel(shd->d3d11.vs, desc->label); + shd->d3d11.vs_blob_length = vs_length; + shd->d3d11.vs_blob = _sg_malloc((size_t)vs_length); + SOKOL_ASSERT(shd->d3d11.vs_blob); + memcpy(shd->d3d11.vs_blob, vs_ptr, vs_length); + } + if (vs_blob) { + _sg_d3d11_Release(vs_blob); + } + } + if (has_fs) { + const void* fs_ptr = 0; SIZE_T fs_length = 0; + ID3DBlob* fs_blob = 0; + if (desc->fragment_func.bytecode.ptr) { + SOKOL_ASSERT(desc->fragment_func.bytecode.size > 0); + fs_ptr = desc->fragment_func.bytecode.ptr; + fs_length = desc->fragment_func.bytecode.size; + } else { + SOKOL_ASSERT(desc->fragment_func.source); + fs_blob = _sg_d3d11_compile_shader(&desc->fragment_func); + if (fs_blob) { + fs_ptr = _sg_d3d11_GetBufferPointer(fs_blob); + fs_length = _sg_d3d11_GetBufferSize(fs_blob); + } + } + if (fs_ptr && (fs_length > 0)) { + hr = _sg_d3d11_CreatePixelShader(_sg.d3d11.dev, fs_ptr, fs_length, NULL, &shd->d3d11.fs); + fs_valid = SUCCEEDED(hr) && shd->d3d11.fs; + } + if (fs_valid) { + _sg_d3d11_setlabel(shd->d3d11.fs, desc->label); + } + if (fs_blob) { + _sg_d3d11_Release(fs_blob); + } + } + if (has_cs) { + const void* cs_ptr = 0; SIZE_T cs_length = 0; + ID3DBlob* cs_blob = 0; + if (desc->compute_func.bytecode.ptr) { + SOKOL_ASSERT(desc->compute_func.bytecode.size > 0); + cs_ptr = desc->compute_func.bytecode.ptr; + cs_length = desc->compute_func.bytecode.size; + } else { + SOKOL_ASSERT(desc->compute_func.source); + cs_blob = _sg_d3d11_compile_shader(&desc->compute_func); + if (cs_blob) { + cs_ptr = _sg_d3d11_GetBufferPointer(cs_blob); + cs_length = _sg_d3d11_GetBufferSize(cs_blob); + } + } + if (cs_ptr && (cs_length > 0)) { + hr = _sg_d3d11_CreateComputeShader(_sg.d3d11.dev, cs_ptr, cs_length, NULL, &shd->d3d11.cs); + cs_valid = SUCCEEDED(hr) && shd->d3d11.cs; + } + if (cs_blob) { + _sg_d3d11_Release(cs_blob); + } + } + if ((vs_valid && fs_valid) || cs_valid) { + return SG_RESOURCESTATE_VALID; + } else { + return SG_RESOURCESTATE_FAILED; + } +} + +_SOKOL_PRIVATE void _sg_d3d11_discard_shader(_sg_shader_t* shd) { + SOKOL_ASSERT(shd); + if (shd->d3d11.vs) { + _sg_d3d11_Release(shd->d3d11.vs); + } + if (shd->d3d11.fs) { + _sg_d3d11_Release(shd->d3d11.fs); + } + if (shd->d3d11.cs) { + _sg_d3d11_Release(shd->d3d11.cs); + } + if (shd->d3d11.vs_blob) { + _sg_free(shd->d3d11.vs_blob); + } + for (size_t i = 0; i < SG_MAX_UNIFORMBLOCK_BINDSLOTS; i++) { + if (shd->d3d11.all_cbufs[i]) { + _sg_d3d11_Release(shd->d3d11.all_cbufs[i]); + } + } +} + +_SOKOL_PRIVATE sg_resource_state _sg_d3d11_create_pipeline(_sg_pipeline_t* pip, const sg_pipeline_desc* desc) { + SOKOL_ASSERT(pip && desc); + _sg_shader_t* shd = _sg_shader_ref_ptr(&pip->cmn.shader); + + // if this is a compute pipeline, we're done here + if (pip->cmn.is_compute) { + return SG_RESOURCESTATE_VALID; + } + + // a render pipeline... + SOKOL_ASSERT(shd->d3d11.vs_blob && shd->d3d11.vs_blob_length > 0); + SOKOL_ASSERT(!pip->d3d11.il && !pip->d3d11.rs && !pip->d3d11.dss && !pip->d3d11.bs); + + pip->d3d11.index_format = _sg_d3d11_index_format(pip->cmn.index_type); + pip->d3d11.topology = _sg_d3d11_primitive_topology(desc->primitive_type); + pip->d3d11.stencil_ref = desc->stencil.ref; + + // create input layout object + HRESULT hr; + D3D11_INPUT_ELEMENT_DESC d3d11_comps[SG_MAX_VERTEX_ATTRIBUTES]; + _sg_clear(d3d11_comps, sizeof(d3d11_comps)); + size_t attr_index = 0; + for (; attr_index < SG_MAX_VERTEX_ATTRIBUTES; attr_index++) { + const sg_vertex_attr_state* a_state = &desc->layout.attrs[attr_index]; + if (a_state->format == SG_VERTEXFORMAT_INVALID) { + break; + } + SOKOL_ASSERT(a_state->buffer_index < SG_MAX_VERTEXBUFFER_BINDSLOTS); + SOKOL_ASSERT(pip->cmn.vertex_buffer_layout_active[a_state->buffer_index]); + const sg_vertex_buffer_layout_state* l_state = &desc->layout.buffers[a_state->buffer_index]; + const sg_vertex_step step_func = l_state->step_func; + const int step_rate = l_state->step_rate; + D3D11_INPUT_ELEMENT_DESC* d3d11_comp = &d3d11_comps[attr_index]; + d3d11_comp->SemanticName = _sg_strptr(&shd->d3d11.attrs[attr_index].sem_name); + d3d11_comp->SemanticIndex = (UINT)shd->d3d11.attrs[attr_index].sem_index; + d3d11_comp->Format = _sg_d3d11_vertex_format(a_state->format); + d3d11_comp->InputSlot = (UINT)a_state->buffer_index; + d3d11_comp->AlignedByteOffset = (UINT)a_state->offset; + d3d11_comp->InputSlotClass = _sg_d3d11_input_classification(step_func); + if (SG_VERTEXSTEP_PER_INSTANCE == step_func) { + d3d11_comp->InstanceDataStepRate = (UINT)step_rate; + } + } + for (size_t layout_index = 0; layout_index < SG_MAX_VERTEXBUFFER_BINDSLOTS; layout_index++) { + if (pip->cmn.vertex_buffer_layout_active[layout_index]) { + const sg_vertex_buffer_layout_state* l_state = &desc->layout.buffers[layout_index]; + SOKOL_ASSERT(l_state->stride > 0); + pip->d3d11.vb_strides[layout_index] = (UINT)l_state->stride; + } else { + pip->d3d11.vb_strides[layout_index] = 0; + } + } + if (attr_index > 0) { + hr = _sg_d3d11_CreateInputLayout(_sg.d3d11.dev, + d3d11_comps, // pInputElementDesc + (UINT)attr_index, // NumElements + shd->d3d11.vs_blob, // pShaderByteCodeWithInputSignature + shd->d3d11.vs_blob_length, // BytecodeLength + &pip->d3d11.il); + if (!(SUCCEEDED(hr) && pip->d3d11.il)) { + _SG_ERROR(D3D11_CREATE_INPUT_LAYOUT_FAILED); + return SG_RESOURCESTATE_FAILED; + } + _sg_d3d11_setlabel(pip->d3d11.il, desc->label); + } + + // create rasterizer state + D3D11_RASTERIZER_DESC rs_desc; + _sg_clear(&rs_desc, sizeof(rs_desc)); + rs_desc.FillMode = D3D11_FILL_SOLID; + rs_desc.CullMode = _sg_d3d11_cull_mode(desc->cull_mode); + rs_desc.FrontCounterClockwise = desc->face_winding == SG_FACEWINDING_CCW; + rs_desc.DepthBias = (INT) pip->cmn.depth.bias; + rs_desc.DepthBiasClamp = pip->cmn.depth.bias_clamp; + rs_desc.SlopeScaledDepthBias = pip->cmn.depth.bias_slope_scale; + rs_desc.DepthClipEnable = TRUE; + rs_desc.ScissorEnable = TRUE; + rs_desc.MultisampleEnable = desc->sample_count > 1; + rs_desc.AntialiasedLineEnable = FALSE; + hr = _sg_d3d11_CreateRasterizerState(_sg.d3d11.dev, &rs_desc, &pip->d3d11.rs); + if (!(SUCCEEDED(hr) && pip->d3d11.rs)) { + _SG_ERROR(D3D11_CREATE_RASTERIZER_STATE_FAILED); + return SG_RESOURCESTATE_FAILED; + } + _sg_d3d11_setlabel(pip->d3d11.rs, desc->label); + + // create depth-stencil state + D3D11_DEPTH_STENCIL_DESC dss_desc; + _sg_clear(&dss_desc, sizeof(dss_desc)); + dss_desc.DepthEnable = TRUE; + dss_desc.DepthWriteMask = desc->depth.write_enabled ? D3D11_DEPTH_WRITE_MASK_ALL : D3D11_DEPTH_WRITE_MASK_ZERO; + dss_desc.DepthFunc = _sg_d3d11_compare_func(desc->depth.compare); + dss_desc.StencilEnable = desc->stencil.enabled; + dss_desc.StencilReadMask = desc->stencil.read_mask; + dss_desc.StencilWriteMask = desc->stencil.write_mask; + const sg_stencil_face_state* sf = &desc->stencil.front; + dss_desc.FrontFace.StencilFailOp = _sg_d3d11_stencil_op(sf->fail_op); + dss_desc.FrontFace.StencilDepthFailOp = _sg_d3d11_stencil_op(sf->depth_fail_op); + dss_desc.FrontFace.StencilPassOp = _sg_d3d11_stencil_op(sf->pass_op); + dss_desc.FrontFace.StencilFunc = _sg_d3d11_compare_func(sf->compare); + const sg_stencil_face_state* sb = &desc->stencil.back; + dss_desc.BackFace.StencilFailOp = _sg_d3d11_stencil_op(sb->fail_op); + dss_desc.BackFace.StencilDepthFailOp = _sg_d3d11_stencil_op(sb->depth_fail_op); + dss_desc.BackFace.StencilPassOp = _sg_d3d11_stencil_op(sb->pass_op); + dss_desc.BackFace.StencilFunc = _sg_d3d11_compare_func(sb->compare); + hr = _sg_d3d11_CreateDepthStencilState(_sg.d3d11.dev, &dss_desc, &pip->d3d11.dss); + if (!(SUCCEEDED(hr) && pip->d3d11.dss)) { + _SG_ERROR(D3D11_CREATE_DEPTH_STENCIL_STATE_FAILED); + return SG_RESOURCESTATE_FAILED; + } + _sg_d3d11_setlabel(pip->d3d11.dss, desc->label); + + // create blend state + D3D11_BLEND_DESC bs_desc; + _sg_clear(&bs_desc, sizeof(bs_desc)); + bs_desc.AlphaToCoverageEnable = desc->alpha_to_coverage_enabled; + bs_desc.IndependentBlendEnable = TRUE; + { + size_t i = 0; + for (i = 0; i < (size_t)desc->color_count; i++) { + const sg_blend_state* src = &desc->colors[i].blend; + D3D11_RENDER_TARGET_BLEND_DESC* dst = &bs_desc.RenderTarget[i]; + dst->BlendEnable = src->enabled; + dst->SrcBlend = _sg_d3d11_blend_factor(src->src_factor_rgb); + dst->DestBlend = _sg_d3d11_blend_factor(src->dst_factor_rgb); + dst->BlendOp = _sg_d3d11_blend_op(src->op_rgb); + dst->SrcBlendAlpha = _sg_d3d11_blend_factor(src->src_factor_alpha); + dst->DestBlendAlpha = _sg_d3d11_blend_factor(src->dst_factor_alpha); + dst->BlendOpAlpha = _sg_d3d11_blend_op(src->op_alpha); + dst->RenderTargetWriteMask = _sg_d3d11_color_write_mask(desc->colors[i].write_mask); + } + for (; i < 8; i++) { + D3D11_RENDER_TARGET_BLEND_DESC* dst = &bs_desc.RenderTarget[i]; + dst->BlendEnable = FALSE; + dst->SrcBlend = dst->SrcBlendAlpha = D3D11_BLEND_ONE; + dst->DestBlend = dst->DestBlendAlpha = D3D11_BLEND_ZERO; + dst->BlendOp = dst->BlendOpAlpha = D3D11_BLEND_OP_ADD; + dst->RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL; + } + } + hr = _sg_d3d11_CreateBlendState(_sg.d3d11.dev, &bs_desc, &pip->d3d11.bs); + if (!(SUCCEEDED(hr) && pip->d3d11.bs)) { + _SG_ERROR(D3D11_CREATE_BLEND_STATE_FAILED); + return SG_RESOURCESTATE_FAILED; + } + _sg_d3d11_setlabel(pip->d3d11.bs, desc->label); + return SG_RESOURCESTATE_VALID; +} + +_SOKOL_PRIVATE void _sg_d3d11_discard_pipeline(_sg_pipeline_t* pip) { + SOKOL_ASSERT(pip); + if (pip->d3d11.il) { + _sg_d3d11_Release(pip->d3d11.il); + } + if (pip->d3d11.rs) { + _sg_d3d11_Release(pip->d3d11.rs); + } + if (pip->d3d11.dss) { + _sg_d3d11_Release(pip->d3d11.dss); + } + if (pip->d3d11.bs) { + _sg_d3d11_Release(pip->d3d11.bs); + } +} + +_SOKOL_PRIVATE sg_resource_state _sg_d3d11_create_view(_sg_view_t* view, const sg_view_desc* desc) { + SOKOL_ASSERT(view && desc); + _SOKOL_UNUSED(desc); + HRESULT hr; + if (view->cmn.type == SG_VIEWTYPE_STORAGEBUFFER) { + const _sg_buffer_t* buf = _sg_buffer_ref_ptr(&view->cmn.buf.ref); + SOKOL_ASSERT(buf->d3d11.buf); + const UINT size = (UINT) buf->cmn.size; + SOKOL_ASSERT(_sg_multiple_u64(size, 4)); + const UINT offset = (UINT) view->cmn.buf.offset; + SOKOL_ASSERT(_sg_multiple_u64(offset, 4)); + SOKOL_ASSERT(offset < size); + const UINT first_element = offset / 4; + const UINT num_elements = (size - offset) / 4; + D3D11_SHADER_RESOURCE_VIEW_DESC d3d11_srv_desc; + _sg_clear(&d3d11_srv_desc, sizeof(d3d11_srv_desc)); + d3d11_srv_desc.Format = DXGI_FORMAT_R32_TYPELESS; + d3d11_srv_desc.ViewDimension = D3D11_SRV_DIMENSION_BUFFEREX; + d3d11_srv_desc.BufferEx.FirstElement = first_element; + d3d11_srv_desc.BufferEx.NumElements = num_elements; + d3d11_srv_desc.BufferEx.Flags = D3D11_BUFFEREX_SRV_FLAG_RAW; + SOKOL_ASSERT(!view->d3d11.srv); + hr = _sg_d3d11_CreateShaderResourceView(_sg.d3d11.dev, (ID3D11Resource*)buf->d3d11.buf, &d3d11_srv_desc, &view->d3d11.srv); + if (!(SUCCEEDED(hr) && view->d3d11.srv)) { + _SG_ERROR(D3D11_CREATE_BUFFER_SRV_FAILED); + return SG_RESOURCESTATE_FAILED; + } + _sg_d3d11_setlabel(view->d3d11.srv, desc->label); + if (buf->cmn.usage.immutable) { + D3D11_UNORDERED_ACCESS_VIEW_DESC d3d11_uav_desc; + _sg_clear(&d3d11_uav_desc, sizeof(d3d11_uav_desc)); + d3d11_uav_desc.Format = DXGI_FORMAT_R32_TYPELESS; + d3d11_uav_desc.ViewDimension = D3D11_UAV_DIMENSION_BUFFER; + d3d11_uav_desc.Buffer.FirstElement = first_element; + d3d11_uav_desc.Buffer.NumElements = num_elements; + d3d11_uav_desc.Buffer.Flags = D3D11_BUFFER_UAV_FLAG_RAW; + SOKOL_ASSERT(!view->d3d11.uav); + hr = _sg_d3d11_CreateUnorderedAccessView(_sg.d3d11.dev, (ID3D11Resource*)buf->d3d11.buf, &d3d11_uav_desc, &view->d3d11.uav); + if (!(SUCCEEDED(hr) && view->d3d11.uav)) { + _SG_ERROR(D3D11_CREATE_BUFFER_UAV_FAILED); + return SG_RESOURCESTATE_FAILED; + } + _sg_d3d11_setlabel(view->d3d11.uav, desc->label); + } + } else { + // it's an image view + const _sg_image_t* img = _sg_image_ref_ptr(&view->cmn.img.ref); + SOKOL_ASSERT(img->d3d11.res); + const bool msaa = img->cmn.sample_count > 1; + SOKOL_ASSERT(view->cmn.img.mip_level_count >= 1); + SOKOL_ASSERT(view->cmn.img.slice_count >= 1); + const UINT mip_level = (UINT)view->cmn.img.mip_level; + const UINT mip_count = (UINT)view->cmn.img.mip_level_count; + const UINT slice = (UINT)view->cmn.img.slice; + const UINT slice_count = (UINT)view->cmn.img.slice_count; + + if (view->cmn.type == SG_VIEWTYPE_STORAGEIMAGE) { + SOKOL_ASSERT(!msaa); + D3D11_UNORDERED_ACCESS_VIEW_DESC d3d11_uav_desc; + _sg_clear(&d3d11_uav_desc, sizeof(d3d11_uav_desc)); + d3d11_uav_desc.Format = _sg_d3d11_rtv_uav_pixel_format(img->cmn.pixel_format); + switch (img->cmn.type) { + case SG_IMAGETYPE_2D: + d3d11_uav_desc.ViewDimension = D3D11_UAV_DIMENSION_TEXTURE2D; + d3d11_uav_desc.Texture2D.MipSlice = mip_level; + break; + case SG_IMAGETYPE_CUBE: + case SG_IMAGETYPE_ARRAY: + d3d11_uav_desc.ViewDimension = D3D11_UAV_DIMENSION_TEXTURE2DARRAY; + d3d11_uav_desc.Texture2DArray.MipSlice = mip_level; + d3d11_uav_desc.Texture2DArray.FirstArraySlice = slice; + d3d11_uav_desc.Texture2DArray.ArraySize = 1; + break; + case SG_IMAGETYPE_3D: + d3d11_uav_desc.ViewDimension = D3D11_UAV_DIMENSION_TEXTURE3D; + d3d11_uav_desc.Texture3D.MipSlice = mip_level; + d3d11_uav_desc.Texture3D.FirstWSlice = slice; + d3d11_uav_desc.Texture3D.WSize = 1; + break; + default: SOKOL_UNREACHABLE; break; + } + hr = _sg_d3d11_CreateUnorderedAccessView(_sg.d3d11.dev, img->d3d11.res, &d3d11_uav_desc, &view->d3d11.uav); + if (!(SUCCEEDED(hr) && view->d3d11.uav)) { + _SG_ERROR(D3D11_CREATE_UAV_FAILED); + return SG_RESOURCESTATE_FAILED; + } + _sg_d3d11_setlabel(view->d3d11.uav, desc->label); + + } else if (view->cmn.type == SG_VIEWTYPE_TEXTURE) { + + D3D11_SHADER_RESOURCE_VIEW_DESC d3d11_srv_desc; + _sg_clear(&d3d11_srv_desc, sizeof(&d3d11_srv_desc)); + d3d11_srv_desc.Format = _sg_d3d11_srv_pixel_format(img->cmn.pixel_format); + switch (img->cmn.type) { + case SG_IMAGETYPE_2D: + if (msaa) { + d3d11_srv_desc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2DMS; + } else { + d3d11_srv_desc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D; + d3d11_srv_desc.Texture2D.MostDetailedMip = mip_level; + d3d11_srv_desc.Texture2D.MipLevels = mip_count; + } + break; + case SG_IMAGETYPE_CUBE: + SOKOL_ASSERT(!msaa); + d3d11_srv_desc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURECUBE; + d3d11_srv_desc.TextureCube.MostDetailedMip = mip_level; + d3d11_srv_desc.TextureCube.MipLevels = mip_count; + break; + case SG_IMAGETYPE_ARRAY: + if (msaa) { + // NOTE: _sg_validate_image_desc() currently disallows MSAA array textures + d3d11_srv_desc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2DMSARRAY; + d3d11_srv_desc.Texture2DMSArray.FirstArraySlice = slice; + d3d11_srv_desc.Texture2DMSArray.ArraySize = slice_count; + } else { + d3d11_srv_desc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2DARRAY; + d3d11_srv_desc.Texture2DArray.MostDetailedMip = mip_level; + d3d11_srv_desc.Texture2DArray.MipLevels = mip_count; + d3d11_srv_desc.Texture2DArray.FirstArraySlice = slice; + d3d11_srv_desc.Texture2DArray.ArraySize = slice_count; + } + break; + case SG_IMAGETYPE_3D: + SOKOL_ASSERT(!msaa); + d3d11_srv_desc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE3D; + d3d11_srv_desc.Texture3D.MostDetailedMip = mip_level; + d3d11_srv_desc.Texture3D.MipLevels = mip_count; + break; + default: + SOKOL_UNREACHABLE; break; + } + hr = _sg_d3d11_CreateShaderResourceView(_sg.d3d11.dev, img->d3d11.res, &d3d11_srv_desc, &view->d3d11.srv); + if (!(SUCCEEDED(hr) && view->d3d11.srv)) { + _SG_ERROR(D3D11_CREATE_2D_SRV_FAILED); + return SG_RESOURCESTATE_FAILED; + } + _sg_d3d11_setlabel(view->d3d11.srv, desc->label); + + } else if (view->cmn.type == SG_VIEWTYPE_COLORATTACHMENT) { + + D3D11_RENDER_TARGET_VIEW_DESC d3d11_rtv_desc; + _sg_clear(&d3d11_rtv_desc, sizeof(d3d11_rtv_desc)); + d3d11_rtv_desc.Format = _sg_d3d11_rtv_uav_pixel_format(img->cmn.pixel_format); + switch (img->cmn.type) { + case SG_IMAGETYPE_2D: + if (msaa) { + d3d11_rtv_desc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2DMS; + } else { + d3d11_rtv_desc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2D; + d3d11_rtv_desc.Texture2D.MipSlice = mip_level; + } + break; + case SG_IMAGETYPE_CUBE: + case SG_IMAGETYPE_ARRAY: + if (msaa) { + d3d11_rtv_desc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2DMSARRAY; + d3d11_rtv_desc.Texture2DMSArray.FirstArraySlice = slice; + d3d11_rtv_desc.Texture2DMSArray.ArraySize = 1; + } else { + d3d11_rtv_desc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2DARRAY; + d3d11_rtv_desc.Texture2DArray.MipSlice = mip_level; + d3d11_rtv_desc.Texture2DArray.FirstArraySlice = slice; + d3d11_rtv_desc.Texture2DArray.ArraySize = 1; + } + break; + case SG_IMAGETYPE_3D: + SOKOL_ASSERT(!msaa); + d3d11_rtv_desc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE3D; + d3d11_rtv_desc.Texture3D.MipSlice = mip_level; + d3d11_rtv_desc.Texture3D.FirstWSlice = slice; + d3d11_rtv_desc.Texture3D.WSize = 1; + break; + default: SOKOL_UNREACHABLE; break; + } + hr = _sg_d3d11_CreateRenderTargetView(_sg.d3d11.dev, img->d3d11.res, &d3d11_rtv_desc, &view->d3d11.rtv); + if (!(SUCCEEDED(hr) && view->d3d11.rtv)) { + _SG_ERROR(D3D11_CREATE_RTV_FAILED); + return SG_RESOURCESTATE_FAILED; + } + _sg_d3d11_setlabel(view->d3d11.rtv, desc->label); + + } else if (view->cmn.type == SG_VIEWTYPE_DEPTHSTENCILATTACHMENT) { + + SOKOL_ASSERT(img->cmn.type != SG_IMAGETYPE_3D); + D3D11_DEPTH_STENCIL_VIEW_DESC d3d11_dsv_desc; + _sg_clear(&d3d11_dsv_desc, sizeof(d3d11_dsv_desc)); + d3d11_dsv_desc.Format = _sg_d3d11_dsv_pixel_format(img->cmn.pixel_format); + switch (img->cmn.type) { + case SG_IMAGETYPE_2D: + if (msaa) { + d3d11_dsv_desc.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2DMS; + } else { + d3d11_dsv_desc.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2D; + d3d11_dsv_desc.Texture2D.MipSlice = mip_level; + } + break; + case SG_IMAGETYPE_CUBE: + case SG_IMAGETYPE_ARRAY: + if (msaa) { + d3d11_dsv_desc.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2DMSARRAY; + d3d11_dsv_desc.Texture2DMSArray.FirstArraySlice = slice; + d3d11_dsv_desc.Texture2DMSArray.ArraySize = 1; + } else { + d3d11_dsv_desc.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2DARRAY; + d3d11_dsv_desc.Texture2DArray.MipSlice = mip_level; + d3d11_dsv_desc.Texture2DArray.FirstArraySlice = slice; + d3d11_dsv_desc.Texture2DArray.ArraySize = 1; + } + break; + default: SOKOL_UNREACHABLE; break; + } + hr = _sg_d3d11_CreateDepthStencilView(_sg.d3d11.dev, img->d3d11.res, &d3d11_dsv_desc, &view->d3d11.dsv); + if (!(SUCCEEDED(hr) && view->d3d11.dsv)) { + _SG_ERROR(D3D11_CREATE_DSV_FAILED); + return SG_RESOURCESTATE_FAILED; + } + _sg_d3d11_setlabel(view->d3d11.dsv, desc->label); + } + } + return SG_RESOURCESTATE_VALID; +} + +_SOKOL_PRIVATE void _sg_d3d11_discard_view(_sg_view_t* view) { + SOKOL_ASSERT(view); + if (view->d3d11.srv) { + _sg_d3d11_Release(view->d3d11.srv); + } + if (view->d3d11.uav) { + _sg_d3d11_Release(view->d3d11.uav); + } + if (view->d3d11.rtv) { + _sg_d3d11_Release(view->d3d11.rtv); + } + if (view->d3d11.dsv) { + _sg_d3d11_Release(view->d3d11.dsv); + } +} + +_SOKOL_PRIVATE void _sg_d3d11_begin_pass(const sg_pass* pass, const _sg_attachments_ptrs_t* atts) { + SOKOL_ASSERT(_sg.d3d11.ctx && pass && atts); + if (_sg.cur_pass.is_compute) { + // nothing to do in compute passes + return; + } + int num_rtvs = 0; + ID3D11RenderTargetView* rtvs[SG_MAX_COLOR_ATTACHMENTS] = { 0 }; + ID3D11DepthStencilView* dsv = 0; + _sg.d3d11.cur_swapchain.render_view = 0; + _sg.d3d11.cur_swapchain.resolve_view = 0; + if (!atts->empty) { + SOKOL_ASSERT(atts->num_color_views <= SG_MAX_COLOR_ATTACHMENTS); + num_rtvs = atts->num_color_views; + for (int i = 0; i < num_rtvs; i++) { + SOKOL_ASSERT(atts->color_views[i]); + SOKOL_ASSERT(atts->color_views[i]->d3d11.rtv); + rtvs[i] = atts->color_views[i]->d3d11.rtv; + } + if (atts->ds_view) { + SOKOL_ASSERT(atts->ds_view->d3d11.dsv); + dsv = atts->ds_view->d3d11.dsv; + } + } else { + // NOTE: swapchain depth-stencil-view is optional + const sg_swapchain* swapchain = &pass->swapchain; + SOKOL_ASSERT(swapchain->d3d11.render_view); + num_rtvs = 1; + rtvs[0] = (ID3D11RenderTargetView*) swapchain->d3d11.render_view; + dsv = (ID3D11DepthStencilView*) swapchain->d3d11.depth_stencil_view; + _sg.d3d11.cur_swapchain.render_view = (ID3D11RenderTargetView*) swapchain->d3d11.render_view; + _sg.d3d11.cur_swapchain.resolve_view = (ID3D11RenderTargetView*) swapchain->d3d11.resolve_view; + } + // apply the render-target- and depth-stencil-views + _sg_d3d11_OMSetRenderTargets(_sg.d3d11.ctx, SG_MAX_COLOR_ATTACHMENTS, rtvs, dsv); + _sg_stats_add(d3d11.pass.num_om_set_render_targets, 1); + + // set viewport and scissor rect to cover whole screen + D3D11_VIEWPORT vp; + _sg_clear(&vp, sizeof(vp)); + vp.Width = (FLOAT) _sg.cur_pass.dim.width; + vp.Height = (FLOAT) _sg.cur_pass.dim.height; + vp.MaxDepth = 1.0f; + _sg_d3d11_RSSetViewports(_sg.d3d11.ctx, 1, &vp); + D3D11_RECT rect; + rect.left = 0; + rect.top = 0; + rect.right = _sg.cur_pass.dim.width; + rect.bottom = _sg.cur_pass.dim.height; + _sg_d3d11_RSSetScissorRects(_sg.d3d11.ctx, 1, &rect); + + // perform clear action + const sg_pass_action* action = &pass->action; + for (size_t i = 0; i < (size_t)num_rtvs; i++) { + if (action->colors[i].load_action == SG_LOADACTION_CLEAR) { + _sg_d3d11_ClearRenderTargetView(_sg.d3d11.ctx, rtvs[i], (float*)&action->colors[i].clear_value); + _sg_stats_add(d3d11.pass.num_clear_render_target_view, 1); + } + } + UINT ds_flags = 0; + if (action->depth.load_action == SG_LOADACTION_CLEAR) { + ds_flags |= D3D11_CLEAR_DEPTH; + } + if (action->stencil.load_action == SG_LOADACTION_CLEAR) { + ds_flags |= D3D11_CLEAR_STENCIL; + } + if ((0 != ds_flags) && dsv) { + _sg_d3d11_ClearDepthStencilView(_sg.d3d11.ctx, dsv, ds_flags, action->depth.clear_value, action->stencil.clear_value); + _sg_stats_add(d3d11.pass.num_clear_depth_stencil_view, 1); + } +} + +// D3D11CalcSubresource only exists for C++ +_SOKOL_PRIVATE UINT _sg_d3d11_calcsubresource(UINT mip_slice, UINT array_slice, UINT mip_levels) { + return mip_slice + array_slice * mip_levels; +} + +_SOKOL_PRIVATE void _sg_d3d11_end_pass(const _sg_attachments_ptrs_t* atts) { + SOKOL_ASSERT(_sg.d3d11.ctx && atts); + + if (!_sg.cur_pass.is_compute) { + // need to resolve MSAA render attachments into texture? + if (!atts->empty) { + // ...for offscreen pass... + for (int i = 0; i < atts->num_color_views; i++) { + const _sg_view_t* resolve_view = atts->resolve_views[i]; + if (resolve_view) { + const _sg_image_t* resolve_img = _sg_image_ref_ptr(&resolve_view->cmn.img.ref); + const _sg_view_t* color_view = atts->color_views[i]; + SOKOL_ASSERT(color_view); + const _sg_image_t* color_img = _sg_image_ref_ptr(&color_view->cmn.img.ref); + SOKOL_ASSERT(color_img->cmn.sample_count > 1); + SOKOL_ASSERT(resolve_img->cmn.sample_count == 1); + const UINT src_subres = _sg_d3d11_calcsubresource( + (UINT)color_view->cmn.img.mip_level, + (UINT)color_view->cmn.img.slice, + (UINT)color_img->cmn.num_mipmaps); + const UINT dst_subres = _sg_d3d11_calcsubresource( + (UINT)resolve_view->cmn.img.mip_level, + (UINT)resolve_view->cmn.img.slice, + (UINT)resolve_img->cmn.num_mipmaps); + _sg_d3d11_ResolveSubresource(_sg.d3d11.ctx, + resolve_img->d3d11.res, + dst_subres, + color_img->d3d11.res, + src_subres, + color_img->d3d11.format); + _sg_stats_add(d3d11.pass.num_resolve_subresource, 1); + } + } + } else { + // ...for swapchain pass... + if (_sg.d3d11.cur_swapchain.resolve_view) { + SOKOL_ASSERT(_sg.d3d11.cur_swapchain.render_view); + SOKOL_ASSERT(_sg.cur_pass.swapchain.sample_count > 1); + SOKOL_ASSERT(_sg.cur_pass.swapchain.color_fmt > SG_PIXELFORMAT_NONE); + ID3D11Resource* d3d11_render_res = 0; + ID3D11Resource* d3d11_resolve_res = 0; + _sg_d3d11_GetResource((ID3D11View*)_sg.d3d11.cur_swapchain.render_view, &d3d11_render_res); + _sg_d3d11_GetResource((ID3D11View*)_sg.d3d11.cur_swapchain.resolve_view, &d3d11_resolve_res); + SOKOL_ASSERT(d3d11_render_res); + SOKOL_ASSERT(d3d11_resolve_res); + const sg_pixel_format color_fmt = _sg.cur_pass.swapchain.color_fmt; + _sg_d3d11_ResolveSubresource(_sg.d3d11.ctx, d3d11_resolve_res, 0, d3d11_render_res, 0, _sg_d3d11_rtv_uav_pixel_format(color_fmt)); + _sg_d3d11_Release(d3d11_render_res); + _sg_d3d11_Release(d3d11_resolve_res); + _sg_stats_add(d3d11.pass.num_resolve_subresource, 1); + } + } + } + _sg.d3d11.cur_swapchain.render_view = 0; + _sg.d3d11.cur_swapchain.resolve_view = 0; + _sg_d3d11_clear_state(); +} + +_SOKOL_PRIVATE void _sg_d3d11_apply_viewport(int x, int y, int w, int h, bool origin_top_left) { + SOKOL_ASSERT(_sg.d3d11.ctx); + D3D11_VIEWPORT vp; + vp.TopLeftX = (FLOAT) x; + vp.TopLeftY = (FLOAT) (origin_top_left ? y : (_sg.cur_pass.dim.height - (y + h))); + vp.Width = (FLOAT) w; + vp.Height = (FLOAT) h; + vp.MinDepth = 0.0f; + vp.MaxDepth = 1.0f; + _sg_d3d11_RSSetViewports(_sg.d3d11.ctx, 1, &vp); +} + +_SOKOL_PRIVATE void _sg_d3d11_apply_scissor_rect(int x, int y, int w, int h, bool origin_top_left) { + SOKOL_ASSERT(_sg.d3d11.ctx); + D3D11_RECT rect; + rect.left = x; + rect.top = (origin_top_left ? y : (_sg.cur_pass.dim.height - (y + h))); + rect.right = x + w; + rect.bottom = origin_top_left ? (y + h) : (_sg.cur_pass.dim.height - y); + _sg_d3d11_RSSetScissorRects(_sg.d3d11.ctx, 1, &rect); +} + +_SOKOL_PRIVATE void _sg_d3d11_apply_pipeline(_sg_pipeline_t* pip) { + SOKOL_ASSERT(pip); + SOKOL_ASSERT(_sg.d3d11.ctx); + + const _sg_shader_t* shd = _sg_shader_ref_ptr(&pip->cmn.shader); + if (pip->cmn.is_compute) { + // a compute pipeline + SOKOL_ASSERT(shd->d3d11.cs); + _sg_d3d11_CSSetShader(_sg.d3d11.ctx, shd->d3d11.cs, NULL, 0); + _sg_d3d11_CSSetConstantBuffers(_sg.d3d11.ctx, 0, _SG_D3D11_MAX_STAGE_UB_BINDINGS, shd->d3d11.cs_cbufs); + _sg_stats_add(d3d11.pipeline.num_cs_set_shader, 1); + _sg_stats_add(d3d11.pipeline.num_cs_set_constant_buffers, 1); + } else { + // a render pipeline + SOKOL_ASSERT(pip->d3d11.rs && pip->d3d11.bs && pip->d3d11.dss); + SOKOL_ASSERT(shd->d3d11.vs); + SOKOL_ASSERT(shd->d3d11.fs); + + _sg_d3d11_RSSetState(_sg.d3d11.ctx, pip->d3d11.rs); + _sg_d3d11_OMSetDepthStencilState(_sg.d3d11.ctx, pip->d3d11.dss, pip->d3d11.stencil_ref); + _sg_d3d11_OMSetBlendState(_sg.d3d11.ctx, pip->d3d11.bs, (float*)&pip->cmn.blend_color, 0xFFFFFFFF); + _sg_d3d11_IASetPrimitiveTopology(_sg.d3d11.ctx, pip->d3d11.topology); + _sg_d3d11_IASetInputLayout(_sg.d3d11.ctx, pip->d3d11.il); + _sg_d3d11_VSSetShader(_sg.d3d11.ctx, shd->d3d11.vs, NULL, 0); + _sg_d3d11_VSSetConstantBuffers(_sg.d3d11.ctx, 0, _SG_D3D11_MAX_STAGE_UB_BINDINGS, shd->d3d11.vs_cbufs); + _sg_d3d11_PSSetShader(_sg.d3d11.ctx, shd->d3d11.fs, NULL, 0); + _sg_d3d11_PSSetConstantBuffers(_sg.d3d11.ctx, 0, _SG_D3D11_MAX_STAGE_UB_BINDINGS, shd->d3d11.fs_cbufs); + _sg_stats_add(d3d11.pipeline.num_rs_set_state, 1); + _sg_stats_add(d3d11.pipeline.num_om_set_depth_stencil_state, 1); + _sg_stats_add(d3d11.pipeline.num_om_set_blend_state, 1); + _sg_stats_add(d3d11.pipeline.num_ia_set_primitive_topology, 1); + _sg_stats_add(d3d11.pipeline.num_ia_set_input_layout, 1); + _sg_stats_add(d3d11.pipeline.num_vs_set_shader, 1); + _sg_stats_add(d3d11.pipeline.num_vs_set_constant_buffers, 1); + _sg_stats_add(d3d11.pipeline.num_ps_set_shader, 1); + _sg_stats_add(d3d11.pipeline.num_ps_set_constant_buffers, 1); + } +} + +_SOKOL_PRIVATE bool _sg_d3d11_apply_bindings(_sg_bindings_ptrs_t* bnd) { + SOKOL_ASSERT(bnd); + SOKOL_ASSERT(bnd->pip); + SOKOL_ASSERT(_sg.d3d11.ctx); + const _sg_shader_t* shd = _sg_shader_ref_ptr(&bnd->pip->cmn.shader); + const bool is_compute = bnd->pip->cmn.is_compute; + + if (is_compute) { + _sg_clear(&_sg.d3d11.bnd.cs_srvs, sizeof(_sg.d3d11.bnd.cs_srvs)); + _sg_clear(&_sg.d3d11.bnd.cs_uavs, sizeof(_sg.d3d11.bnd.cs_uavs)); + _sg_clear(&_sg.d3d11.bnd.cs_smps, sizeof(_sg.d3d11.bnd.cs_smps)); + } else { + _sg_clear(&_sg.d3d11.bnd.vbs, sizeof(_sg.d3d11.bnd.vbs)); + _sg_clear(&_sg.d3d11.bnd.vb_offsets, sizeof(_sg.d3d11.bnd.vb_offsets)); + _sg_clear(&_sg.d3d11.bnd.vs_srvs, sizeof(_sg.d3d11.bnd.vs_srvs)); + _sg_clear(&_sg.d3d11.bnd.fs_srvs, sizeof(_sg.d3d11.bnd.fs_srvs)); + _sg_clear(&_sg.d3d11.bnd.vs_smps, sizeof(_sg.d3d11.bnd.vs_smps)); + _sg_clear(&_sg.d3d11.bnd.fs_smps, sizeof(_sg.d3d11.bnd.fs_smps)); + } + + // gather all the D3D11 resources into arrays + ID3D11Buffer* d3d11_ib = bnd->ib ? bnd->ib->d3d11.buf : 0; + + if (is_compute) { + // on D3D11 we need to break a chicken-egg-situation where a resource + // may still be set as shader resource view, but is going to be set + // as unordered-access-view, so first clear all shader resource view bindings + _sg_d3d11_CSSetShaderResources(_sg.d3d11.ctx, 0, _SG_D3D11_MAX_STAGE_SRV_BINDINGS, _sg.d3d11.bnd.cs_srvs); + } else { + for (size_t i = 0; i < SG_MAX_VERTEXBUFFER_BINDSLOTS; i++) { + const _sg_buffer_t* vb = bnd->vbs[i]; + if (vb == 0) { + continue; + } + SOKOL_ASSERT(vb->d3d11.buf); + _sg.d3d11.bnd.vbs[i] = vb->d3d11.buf; + _sg.d3d11.bnd.vb_offsets[i] = (UINT)bnd->vb_offsets[i]; + } + } + for (size_t i = 0; i < SG_MAX_VIEW_BINDSLOTS; i++) { + const _sg_view_t* view = bnd->views[i]; + if (0 == view) { + continue; + } + const _sg_shader_view_t* shd_view = &shd->cmn.views[i]; + const sg_shader_stage stage = shd_view->stage; + SOKOL_ASSERT((stage == SG_SHADERSTAGE_VERTEX) + || (stage == SG_SHADERSTAGE_FRAGMENT) + || (stage == SG_SHADERSTAGE_COMPUTE)); + SOKOL_ASSERT((shd_view->view_type == SG_VIEWTYPE_TEXTURE) + || (shd_view->view_type == SG_VIEWTYPE_STORAGEBUFFER) + || (shd_view->view_type == SG_VIEWTYPE_STORAGEIMAGE)); + if (shd_view->view_type == SG_VIEWTYPE_TEXTURE) { + const uint8_t d3d11_slot = shd->d3d11.view_register_t_n[i]; + SOKOL_ASSERT(d3d11_slot < _SG_D3D11_MAX_STAGE_SRV_BINDINGS); + ID3D11ShaderResourceView* d3d11_srv = view->d3d11.srv; + SOKOL_ASSERT(d3d11_srv); + switch (stage) { + case SG_SHADERSTAGE_VERTEX: _sg.d3d11.bnd.vs_srvs[d3d11_slot] = d3d11_srv; break; + case SG_SHADERSTAGE_FRAGMENT: _sg.d3d11.bnd.fs_srvs[d3d11_slot] = d3d11_srv; break; + case SG_SHADERSTAGE_COMPUTE: _sg.d3d11.bnd.cs_srvs[d3d11_slot] = d3d11_srv; break; + default: SOKOL_UNREACHABLE; + } + } else if (shd_view->view_type == SG_VIEWTYPE_STORAGEBUFFER) { + if (shd->cmn.views[i].sbuf_readonly) { + const uint8_t d3d11_slot = shd->d3d11.view_register_t_n[i]; + SOKOL_ASSERT(d3d11_slot < _SG_D3D11_MAX_STAGE_SRV_BINDINGS); + ID3D11ShaderResourceView* d3d11_srv = view->d3d11.srv; + SOKOL_ASSERT(d3d11_srv); + switch (stage) { + case SG_SHADERSTAGE_VERTEX: _sg.d3d11.bnd.vs_srvs[d3d11_slot] = d3d11_srv; break; + case SG_SHADERSTAGE_FRAGMENT: _sg.d3d11.bnd.fs_srvs[d3d11_slot] = d3d11_srv; break; + case SG_SHADERSTAGE_COMPUTE: _sg.d3d11.bnd.cs_srvs[d3d11_slot] = d3d11_srv; break; + default: SOKOL_UNREACHABLE; + } + } else { + SOKOL_ASSERT(stage == SG_SHADERSTAGE_COMPUTE); + const uint8_t d3d11_slot = shd->d3d11.view_register_u_n[i]; + SOKOL_ASSERT(d3d11_slot < _sg.limits.d3d11_max_unordered_access_views); + ID3D11UnorderedAccessView* d3d11_uav = view->d3d11.uav; + SOKOL_ASSERT(d3d11_uav); + _sg.d3d11.bnd.cs_uavs[d3d11_slot] = d3d11_uav; + } + } else if (shd_view->view_type == SG_VIEWTYPE_STORAGEIMAGE) { + SOKOL_ASSERT(stage == SG_SHADERSTAGE_COMPUTE); + const uint8_t d3d11_slot = shd->d3d11.view_register_u_n[i]; + SOKOL_ASSERT(d3d11_slot < _sg.limits.d3d11_max_unordered_access_views); + ID3D11UnorderedAccessView* d3d11_uav = view->d3d11.uav; + SOKOL_ASSERT(d3d11_uav); + _sg.d3d11.bnd.cs_uavs[d3d11_slot] = d3d11_uav; + } else SOKOL_UNREACHABLE; + } + for (size_t i = 0; i < SG_MAX_SAMPLER_BINDSLOTS; i++) { + const _sg_sampler_t* smp = bnd->smps[i]; + if (smp == 0) { + continue; + } + const sg_shader_stage stage = shd->cmn.samplers[i].stage; + SOKOL_ASSERT(stage != SG_SHADERSTAGE_NONE); + const uint8_t d3d11_slot = shd->d3d11.smp_register_s_n[i]; + SOKOL_ASSERT(d3d11_slot < _SG_D3D11_MAX_STAGE_SMP_BINDINGS); + SOKOL_ASSERT(smp->d3d11.smp); + ID3D11SamplerState* d3d11_smp = smp->d3d11.smp; + switch (stage) { + case SG_SHADERSTAGE_VERTEX: _sg.d3d11.bnd.vs_smps[d3d11_slot] = d3d11_smp; break; + case SG_SHADERSTAGE_FRAGMENT: _sg.d3d11.bnd.fs_smps[d3d11_slot] = d3d11_smp; break; + case SG_SHADERSTAGE_COMPUTE: _sg.d3d11.bnd.cs_smps[d3d11_slot] = d3d11_smp; break; + default: SOKOL_UNREACHABLE; + } + } + if (is_compute) { + SOKOL_ASSERT(_sg.limits.d3d11_max_unordered_access_views <= _SG_D3D11_MAX_STAGE_UAV_BINDINGS); + _sg_d3d11_CSSetUnorderedAccessViews(_sg.d3d11.ctx, 0, _sg.limits.d3d11_max_unordered_access_views, _sg.d3d11.bnd.cs_uavs, NULL); + _sg_d3d11_CSSetShaderResources(_sg.d3d11.ctx, 0, _SG_D3D11_MAX_STAGE_SRV_BINDINGS, _sg.d3d11.bnd.cs_srvs); + _sg_d3d11_CSSetSamplers(_sg.d3d11.ctx, 0, _SG_D3D11_MAX_STAGE_SMP_BINDINGS, _sg.d3d11.bnd.cs_smps); + _sg_stats_add(d3d11.bindings.num_cs_set_shader_resources, 1); + _sg_stats_add(d3d11.bindings.num_cs_set_samplers, 1); + _sg_stats_add(d3d11.bindings.num_cs_set_unordered_access_views, 1); + } else { + _sg_d3d11_IASetVertexBuffers(_sg.d3d11.ctx, 0, SG_MAX_VERTEXBUFFER_BINDSLOTS, _sg.d3d11.bnd.vbs, bnd->pip->d3d11.vb_strides, _sg.d3d11.bnd.vb_offsets); + _sg_d3d11_IASetIndexBuffer(_sg.d3d11.ctx, d3d11_ib, bnd->pip->d3d11.index_format, (UINT)bnd->ib_offset); + _sg_d3d11_VSSetShaderResources(_sg.d3d11.ctx, 0, _SG_D3D11_MAX_STAGE_SRV_BINDINGS, _sg.d3d11.bnd.vs_srvs); + _sg_d3d11_PSSetShaderResources(_sg.d3d11.ctx, 0, _SG_D3D11_MAX_STAGE_SRV_BINDINGS, _sg.d3d11.bnd.fs_srvs); + _sg_d3d11_VSSetSamplers(_sg.d3d11.ctx, 0, _SG_D3D11_MAX_STAGE_SMP_BINDINGS, _sg.d3d11.bnd.vs_smps); + _sg_d3d11_PSSetSamplers(_sg.d3d11.ctx, 0, _SG_D3D11_MAX_STAGE_SMP_BINDINGS, _sg.d3d11.bnd.fs_smps); + _sg_stats_add(d3d11.bindings.num_ia_set_vertex_buffers, 1); + _sg_stats_add(d3d11.bindings.num_ia_set_index_buffer, 1); + _sg_stats_add(d3d11.bindings.num_vs_set_shader_resources, 1); + _sg_stats_add(d3d11.bindings.num_ps_set_shader_resources, 1); + _sg_stats_add(d3d11.bindings.num_vs_set_samplers, 1); + _sg_stats_add(d3d11.bindings.num_ps_set_samplers, 1); + } + return true; +} + +_SOKOL_PRIVATE void _sg_d3d11_apply_uniforms(int ub_slot, const sg_range* data) { + SOKOL_ASSERT(_sg.d3d11.ctx); + SOKOL_ASSERT((ub_slot >= 0) && (ub_slot < SG_MAX_UNIFORMBLOCK_BINDSLOTS)); + const _sg_pipeline_t* pip = _sg_pipeline_ref_ptr(&_sg.cur_pip); + const _sg_shader_t* shd = _sg_shader_ref_ptr(&pip->cmn.shader); + SOKOL_ASSERT(data->size == shd->cmn.uniform_blocks[ub_slot].size); + + ID3D11Buffer* cbuf = shd->d3d11.all_cbufs[ub_slot]; + SOKOL_ASSERT(cbuf); + _sg_d3d11_UpdateSubresource(_sg.d3d11.ctx, (ID3D11Resource*)cbuf, 0, NULL, data->ptr, 0, 0); + _sg_stats_add(d3d11.uniforms.num_update_subresource, 1); +} + +_SOKOL_PRIVATE void _sg_d3d11_draw(int base_element, int num_elements, int num_instances, int base_vertex, int base_instance) { + const bool use_instanced_draw = (num_instances > 1) || (_sg.use_instanced_draw); + if (_sg.use_indexed_draw) { + if (use_instanced_draw) { + _sg_d3d11_DrawIndexedInstanced(_sg.d3d11.ctx, + (UINT)num_elements, + (UINT)num_instances, + (UINT)base_element, + base_vertex, + (UINT)base_instance); + _sg_stats_add(d3d11.draw.num_draw_indexed_instanced, 1); + } else { + _sg_d3d11_DrawIndexed(_sg.d3d11.ctx, (UINT)num_elements, (UINT)base_element, base_vertex); + _sg_stats_add(d3d11.draw.num_draw_indexed, 1); + } + } else { + if (use_instanced_draw) { + _sg_d3d11_DrawInstanced(_sg.d3d11.ctx, + (UINT)num_elements, + (UINT)num_instances, + (UINT)base_element, + (UINT)base_instance); + _sg_stats_add(d3d11.draw.num_draw_instanced, 1); + } else { + _sg_d3d11_Draw(_sg.d3d11.ctx, (UINT)num_elements, (UINT)base_element); + _sg_stats_add(d3d11.draw.num_draw, 1); + } + } +} + +_SOKOL_PRIVATE void _sg_d3d11_dispatch(int num_groups_x, int num_groups_y, int num_groups_z) { + _sg_d3d11_Dispatch(_sg.d3d11.ctx, (UINT)num_groups_x, (UINT)num_groups_y, (UINT)num_groups_z); +} + +_SOKOL_PRIVATE void _sg_d3d11_commit(void) { + // empty +} + +_SOKOL_PRIVATE void _sg_d3d11_update_buffer(_sg_buffer_t* buf, const sg_range* data) { + SOKOL_ASSERT(buf && data && data->ptr && (data->size > 0)); + SOKOL_ASSERT(_sg.d3d11.ctx); + SOKOL_ASSERT(buf->d3d11.buf); + D3D11_MAPPED_SUBRESOURCE d3d11_msr; + HRESULT hr = _sg_d3d11_Map(_sg.d3d11.ctx, (ID3D11Resource*)buf->d3d11.buf, 0, D3D11_MAP_WRITE_DISCARD, 0, &d3d11_msr); + _sg_stats_add(d3d11.num_map, 1); + if (SUCCEEDED(hr)) { + memcpy(d3d11_msr.pData, data->ptr, data->size); + _sg_d3d11_Unmap(_sg.d3d11.ctx, (ID3D11Resource*)buf->d3d11.buf, 0); + _sg_stats_add(d3d11.num_unmap, 1); + } else { + _SG_ERROR(D3D11_MAP_FOR_UPDATE_BUFFER_FAILED); + } +} + +_SOKOL_PRIVATE void _sg_d3d11_append_buffer(_sg_buffer_t* buf, const sg_range* data, bool new_frame) { + SOKOL_ASSERT(buf && data && data->ptr && (data->size > 0)); + SOKOL_ASSERT(_sg.d3d11.ctx); + SOKOL_ASSERT(buf->d3d11.buf); + D3D11_MAP map_type = new_frame ? D3D11_MAP_WRITE_DISCARD : D3D11_MAP_WRITE_NO_OVERWRITE; + D3D11_MAPPED_SUBRESOURCE d3d11_msr; + HRESULT hr = _sg_d3d11_Map(_sg.d3d11.ctx, (ID3D11Resource*)buf->d3d11.buf, 0, map_type, 0, &d3d11_msr); + _sg_stats_add(d3d11.num_map, 1); + if (SUCCEEDED(hr)) { + uint8_t* dst_ptr = (uint8_t*)d3d11_msr.pData + buf->cmn.append_pos; + memcpy(dst_ptr, data->ptr, data->size); + _sg_d3d11_Unmap(_sg.d3d11.ctx, (ID3D11Resource*)buf->d3d11.buf, 0); + _sg_stats_add(d3d11.num_unmap, 1); + } else { + _SG_ERROR(D3D11_MAP_FOR_APPEND_BUFFER_FAILED); + } +} + +// see: https://learn.microsoft.com/en-us/windows/win32/direct3d11/overviews-direct3d-11-resources-subresources +// also see: https://learn.microsoft.com/en-us/windows/win32/api/d3d11/nf-d3d11-d3d11calcsubresource +_SOKOL_PRIVATE void _sg_d3d11_update_image(_sg_image_t* img, const sg_image_data* data) { + SOKOL_ASSERT(img && data); + SOKOL_ASSERT(_sg.d3d11.ctx); + SOKOL_ASSERT(img->d3d11.res); + const int num_slices = (img->cmn.type == SG_IMAGETYPE_3D) ? 1 : img->cmn.num_slices; + const int num_depth_slices = (img->cmn.type == SG_IMAGETYPE_3D) ? img->cmn.num_slices : 1; + UINT subres_index = 0; + HRESULT hr; + D3D11_MAPPED_SUBRESOURCE d3d11_msr; + for (int slice_index = 0; slice_index < num_slices; slice_index++) { + for (int mip_index = 0; mip_index < img->cmn.num_mipmaps; mip_index++, subres_index++) { + SOKOL_ASSERT(subres_index < _SG_D3D11_MAX_TEXTURE_SUBRESOURCES); + const int mip_width = _sg_miplevel_dim(img->cmn.width, mip_index); + const int mip_height = _sg_miplevel_dim(img->cmn.height, mip_index); + const int src_row_pitch = _sg_row_pitch(img->cmn.pixel_format, mip_width, 1); + const int src_depth_pitch = _sg_surface_pitch(img->cmn.pixel_format, mip_width, mip_height, 1); + const sg_range* miplevel_data = &(data->mip_levels[mip_index]); + const size_t slice_size = miplevel_data->size / (size_t)num_slices; + SOKOL_ASSERT(slice_size == (size_t)(src_depth_pitch * num_depth_slices)); + const size_t slice_offset = slice_size * (size_t)slice_index; + const uint8_t* slice_ptr = ((const uint8_t*)miplevel_data->ptr) + slice_offset; + hr = _sg_d3d11_Map(_sg.d3d11.ctx, img->d3d11.res, subres_index, D3D11_MAP_WRITE_DISCARD, 0, &d3d11_msr); + _sg_stats_add(d3d11.num_map, 1); + if (SUCCEEDED(hr)) { + const uint8_t* src_ptr = slice_ptr; + uint8_t* dst_ptr = (uint8_t*)d3d11_msr.pData; + for (int depth_index = 0; depth_index < num_depth_slices; depth_index++) { + if (src_row_pitch == (int)d3d11_msr.RowPitch) { + const size_t copy_size = slice_size / (size_t)num_depth_slices; + SOKOL_ASSERT((copy_size * (size_t)num_depth_slices) == slice_size); + memcpy(dst_ptr, src_ptr, copy_size); + } else { + SOKOL_ASSERT(src_row_pitch < (int)d3d11_msr.RowPitch); + const uint8_t* src_row_ptr = src_ptr; + uint8_t* dst_row_ptr = dst_ptr; + for (int row_index = 0; row_index < mip_height; row_index++) { + memcpy(dst_row_ptr, src_row_ptr, (size_t)src_row_pitch); + src_row_ptr += src_row_pitch; + dst_row_ptr += d3d11_msr.RowPitch; + } + } + src_ptr += src_depth_pitch; + dst_ptr += d3d11_msr.DepthPitch; + } + _sg_d3d11_Unmap(_sg.d3d11.ctx, img->d3d11.res, subres_index); + _sg_stats_add(d3d11.num_unmap, 1); + } else { + _SG_ERROR(D3D11_MAP_FOR_UPDATE_IMAGE_FAILED); + } + } + } +} + +// ███ ███ ███████ ████████ █████ ██ ██████ █████ ██████ ██ ██ ███████ ███ ██ ██████ +// ████ ████ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██ ██ ██ +// ██ ████ ██ █████ ██ ███████ ██ ██████ ███████ ██ █████ █████ ██ ██ ██ ██ ██ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██ ██ ███████ ██ ██ ██ ███████ ██████ ██ ██ ██████ ██ ██ ███████ ██ ████ ██████ +// +// >>metal backend +#elif defined(SOKOL_METAL) + +#if __has_feature(objc_arc) +#define _SG_OBJC_RETAIN(obj) { } +#define _SG_OBJC_RELEASE(obj) { obj = nil; } +#else +#define _SG_OBJC_RETAIN(obj) { [obj retain]; } +#define _SG_OBJC_RELEASE(obj) { [obj release]; obj = nil; } +#endif + +//-- enum translation functions ------------------------------------------------ +_SOKOL_PRIVATE MTLLoadAction _sg_mtl_load_action(sg_load_action a) { + switch (a) { + case SG_LOADACTION_CLEAR: return MTLLoadActionClear; + case SG_LOADACTION_LOAD: return MTLLoadActionLoad; + case SG_LOADACTION_DONTCARE: return MTLLoadActionDontCare; + default: SOKOL_UNREACHABLE; return (MTLLoadAction)0; + } +} + +_SOKOL_PRIVATE MTLStoreAction _sg_mtl_store_action(sg_store_action a, bool resolve) { + switch (a) { + case SG_STOREACTION_STORE: + if (resolve) { + return MTLStoreActionStoreAndMultisampleResolve; + } else { + return MTLStoreActionStore; + } + break; + case SG_STOREACTION_DONTCARE: + if (resolve) { + return MTLStoreActionMultisampleResolve; + } else { + return MTLStoreActionDontCare; + } + break; + default: SOKOL_UNREACHABLE; return (MTLStoreAction)0; + } +} + +_SOKOL_PRIVATE MTLResourceOptions _sg_mtl_resource_options_storage_mode_managed_or_shared(void) { + #if defined(_SG_TARGET_MACOS) + if (_sg.mtl.use_shared_storage_mode) { + return MTLResourceStorageModeShared; + } else { + return MTLResourceStorageModeManaged; + } + #else + // MTLResourceStorageModeManaged is not even defined on iOS SDK + return MTLResourceStorageModeShared; + #endif +} + +_SOKOL_PRIVATE MTLResourceOptions _sg_mtl_buffer_resource_options(const sg_buffer_usage* usage) { + if (usage->immutable) { + return _sg_mtl_resource_options_storage_mode_managed_or_shared(); + } else { + return MTLResourceCPUCacheModeWriteCombined | _sg_mtl_resource_options_storage_mode_managed_or_shared(); + } +} + +_SOKOL_PRIVATE MTLVertexStepFunction _sg_mtl_step_function(sg_vertex_step step) { + switch (step) { + case SG_VERTEXSTEP_PER_VERTEX: return MTLVertexStepFunctionPerVertex; + case SG_VERTEXSTEP_PER_INSTANCE: return MTLVertexStepFunctionPerInstance; + default: SOKOL_UNREACHABLE; return (MTLVertexStepFunction)0; + } +} + +_SOKOL_PRIVATE MTLVertexFormat _sg_mtl_vertex_format(sg_vertex_format fmt) { + switch (fmt) { + case SG_VERTEXFORMAT_FLOAT: return MTLVertexFormatFloat; + case SG_VERTEXFORMAT_FLOAT2: return MTLVertexFormatFloat2; + case SG_VERTEXFORMAT_FLOAT3: return MTLVertexFormatFloat3; + case SG_VERTEXFORMAT_FLOAT4: return MTLVertexFormatFloat4; + case SG_VERTEXFORMAT_INT: return MTLVertexFormatInt; + case SG_VERTEXFORMAT_INT2: return MTLVertexFormatInt2; + case SG_VERTEXFORMAT_INT3: return MTLVertexFormatInt3; + case SG_VERTEXFORMAT_INT4: return MTLVertexFormatInt4; + case SG_VERTEXFORMAT_UINT: return MTLVertexFormatUInt; + case SG_VERTEXFORMAT_UINT2: return MTLVertexFormatUInt2; + case SG_VERTEXFORMAT_UINT3: return MTLVertexFormatUInt3; + case SG_VERTEXFORMAT_UINT4: return MTLVertexFormatUInt4; + case SG_VERTEXFORMAT_BYTE4: return MTLVertexFormatChar4; + case SG_VERTEXFORMAT_BYTE4N: return MTLVertexFormatChar4Normalized; + case SG_VERTEXFORMAT_UBYTE4: return MTLVertexFormatUChar4; + case SG_VERTEXFORMAT_UBYTE4N: return MTLVertexFormatUChar4Normalized; + case SG_VERTEXFORMAT_SHORT2: return MTLVertexFormatShort2; + case SG_VERTEXFORMAT_SHORT2N: return MTLVertexFormatShort2Normalized; + case SG_VERTEXFORMAT_USHORT2: return MTLVertexFormatUShort2; + case SG_VERTEXFORMAT_USHORT2N: return MTLVertexFormatUShort2Normalized; + case SG_VERTEXFORMAT_SHORT4: return MTLVertexFormatShort4; + case SG_VERTEXFORMAT_SHORT4N: return MTLVertexFormatShort4Normalized; + case SG_VERTEXFORMAT_USHORT4: return MTLVertexFormatUShort4; + case SG_VERTEXFORMAT_USHORT4N: return MTLVertexFormatUShort4Normalized; + case SG_VERTEXFORMAT_UINT10_N2: return MTLVertexFormatUInt1010102Normalized; + case SG_VERTEXFORMAT_HALF2: return MTLVertexFormatHalf2; + case SG_VERTEXFORMAT_HALF4: return MTLVertexFormatHalf4; + default: SOKOL_UNREACHABLE; return (MTLVertexFormat)0; + } +} + +_SOKOL_PRIVATE MTLPrimitiveType _sg_mtl_primitive_type(sg_primitive_type t) { + switch (t) { + case SG_PRIMITIVETYPE_POINTS: return MTLPrimitiveTypePoint; + case SG_PRIMITIVETYPE_LINES: return MTLPrimitiveTypeLine; + case SG_PRIMITIVETYPE_LINE_STRIP: return MTLPrimitiveTypeLineStrip; + case SG_PRIMITIVETYPE_TRIANGLES: return MTLPrimitiveTypeTriangle; + case SG_PRIMITIVETYPE_TRIANGLE_STRIP: return MTLPrimitiveTypeTriangleStrip; + default: SOKOL_UNREACHABLE; return (MTLPrimitiveType)0; + } +} + +_SOKOL_PRIVATE MTLPixelFormat _sg_mtl_pixel_format(sg_pixel_format fmt) { + switch (fmt) { + case SG_PIXELFORMAT_R8: return MTLPixelFormatR8Unorm; + case SG_PIXELFORMAT_R8SN: return MTLPixelFormatR8Snorm; + case SG_PIXELFORMAT_R8UI: return MTLPixelFormatR8Uint; + case SG_PIXELFORMAT_R8SI: return MTLPixelFormatR8Sint; + case SG_PIXELFORMAT_R16: return MTLPixelFormatR16Unorm; + case SG_PIXELFORMAT_R16SN: return MTLPixelFormatR16Snorm; + case SG_PIXELFORMAT_R16UI: return MTLPixelFormatR16Uint; + case SG_PIXELFORMAT_R16SI: return MTLPixelFormatR16Sint; + case SG_PIXELFORMAT_R16F: return MTLPixelFormatR16Float; + case SG_PIXELFORMAT_RG8: return MTLPixelFormatRG8Unorm; + case SG_PIXELFORMAT_RG8SN: return MTLPixelFormatRG8Snorm; + case SG_PIXELFORMAT_RG8UI: return MTLPixelFormatRG8Uint; + case SG_PIXELFORMAT_RG8SI: return MTLPixelFormatRG8Sint; + case SG_PIXELFORMAT_R32UI: return MTLPixelFormatR32Uint; + case SG_PIXELFORMAT_R32SI: return MTLPixelFormatR32Sint; + case SG_PIXELFORMAT_R32F: return MTLPixelFormatR32Float; + case SG_PIXELFORMAT_RG16: return MTLPixelFormatRG16Unorm; + case SG_PIXELFORMAT_RG16SN: return MTLPixelFormatRG16Snorm; + case SG_PIXELFORMAT_RG16UI: return MTLPixelFormatRG16Uint; + case SG_PIXELFORMAT_RG16SI: return MTLPixelFormatRG16Sint; + case SG_PIXELFORMAT_RG16F: return MTLPixelFormatRG16Float; + case SG_PIXELFORMAT_RGBA8: return MTLPixelFormatRGBA8Unorm; + case SG_PIXELFORMAT_SRGB8A8: return MTLPixelFormatRGBA8Unorm_sRGB; + case SG_PIXELFORMAT_RGBA8SN: return MTLPixelFormatRGBA8Snorm; + case SG_PIXELFORMAT_RGBA8UI: return MTLPixelFormatRGBA8Uint; + case SG_PIXELFORMAT_RGBA8SI: return MTLPixelFormatRGBA8Sint; + case SG_PIXELFORMAT_BGRA8: return MTLPixelFormatBGRA8Unorm; + case SG_PIXELFORMAT_RGB10A2: return MTLPixelFormatRGB10A2Unorm; + case SG_PIXELFORMAT_RG11B10F: return MTLPixelFormatRG11B10Float; + case SG_PIXELFORMAT_RGB9E5: return MTLPixelFormatRGB9E5Float; + case SG_PIXELFORMAT_RG32UI: return MTLPixelFormatRG32Uint; + case SG_PIXELFORMAT_RG32SI: return MTLPixelFormatRG32Sint; + case SG_PIXELFORMAT_RG32F: return MTLPixelFormatRG32Float; + case SG_PIXELFORMAT_RGBA16: return MTLPixelFormatRGBA16Unorm; + case SG_PIXELFORMAT_RGBA16SN: return MTLPixelFormatRGBA16Snorm; + case SG_PIXELFORMAT_RGBA16UI: return MTLPixelFormatRGBA16Uint; + case SG_PIXELFORMAT_RGBA16SI: return MTLPixelFormatRGBA16Sint; + case SG_PIXELFORMAT_RGBA16F: return MTLPixelFormatRGBA16Float; + case SG_PIXELFORMAT_RGBA32UI: return MTLPixelFormatRGBA32Uint; + case SG_PIXELFORMAT_RGBA32SI: return MTLPixelFormatRGBA32Sint; + case SG_PIXELFORMAT_RGBA32F: return MTLPixelFormatRGBA32Float; + case SG_PIXELFORMAT_DEPTH: return MTLPixelFormatDepth32Float; + case SG_PIXELFORMAT_DEPTH_STENCIL: return MTLPixelFormatDepth32Float_Stencil8; + #if defined(_SG_TARGET_MACOS) + case SG_PIXELFORMAT_BC1_RGBA: return MTLPixelFormatBC1_RGBA; + case SG_PIXELFORMAT_BC2_RGBA: return MTLPixelFormatBC2_RGBA; + case SG_PIXELFORMAT_BC3_RGBA: return MTLPixelFormatBC3_RGBA; + case SG_PIXELFORMAT_BC3_SRGBA: return MTLPixelFormatBC3_RGBA_sRGB; + case SG_PIXELFORMAT_BC4_R: return MTLPixelFormatBC4_RUnorm; + case SG_PIXELFORMAT_BC4_RSN: return MTLPixelFormatBC4_RSnorm; + case SG_PIXELFORMAT_BC5_RG: return MTLPixelFormatBC5_RGUnorm; + case SG_PIXELFORMAT_BC5_RGSN: return MTLPixelFormatBC5_RGSnorm; + case SG_PIXELFORMAT_BC6H_RGBF: return MTLPixelFormatBC6H_RGBFloat; + case SG_PIXELFORMAT_BC6H_RGBUF: return MTLPixelFormatBC6H_RGBUfloat; + case SG_PIXELFORMAT_BC7_RGBA: return MTLPixelFormatBC7_RGBAUnorm; + case SG_PIXELFORMAT_BC7_SRGBA: return MTLPixelFormatBC7_RGBAUnorm_sRGB; + #else + case SG_PIXELFORMAT_ETC2_RGB8: return MTLPixelFormatETC2_RGB8; + case SG_PIXELFORMAT_ETC2_SRGB8: return MTLPixelFormatETC2_RGB8_sRGB; + case SG_PIXELFORMAT_ETC2_RGB8A1: return MTLPixelFormatETC2_RGB8A1; + case SG_PIXELFORMAT_ETC2_RGBA8: return MTLPixelFormatEAC_RGBA8; + case SG_PIXELFORMAT_ETC2_SRGB8A8: return MTLPixelFormatEAC_RGBA8_sRGB; + case SG_PIXELFORMAT_EAC_R11: return MTLPixelFormatEAC_R11Unorm; + case SG_PIXELFORMAT_EAC_R11SN: return MTLPixelFormatEAC_R11Snorm; + case SG_PIXELFORMAT_EAC_RG11: return MTLPixelFormatEAC_RG11Unorm; + case SG_PIXELFORMAT_EAC_RG11SN: return MTLPixelFormatEAC_RG11Snorm; + case SG_PIXELFORMAT_ASTC_4x4_RGBA: return MTLPixelFormatASTC_4x4_LDR; + case SG_PIXELFORMAT_ASTC_4x4_SRGBA: return MTLPixelFormatASTC_4x4_sRGB; + #endif + default: return MTLPixelFormatInvalid; + } +} + +_SOKOL_PRIVATE MTLColorWriteMask _sg_mtl_color_write_mask(sg_color_mask m) { + MTLColorWriteMask mtl_mask = MTLColorWriteMaskNone; + if (m & SG_COLORMASK_R) { + mtl_mask |= MTLColorWriteMaskRed; + } + if (m & SG_COLORMASK_G) { + mtl_mask |= MTLColorWriteMaskGreen; + } + if (m & SG_COLORMASK_B) { + mtl_mask |= MTLColorWriteMaskBlue; + } + if (m & SG_COLORMASK_A) { + mtl_mask |= MTLColorWriteMaskAlpha; + } + return mtl_mask; +} + +_SOKOL_PRIVATE MTLBlendOperation _sg_mtl_blend_op(sg_blend_op op) { + switch (op) { + case SG_BLENDOP_ADD: return MTLBlendOperationAdd; + case SG_BLENDOP_SUBTRACT: return MTLBlendOperationSubtract; + case SG_BLENDOP_REVERSE_SUBTRACT: return MTLBlendOperationReverseSubtract; + case SG_BLENDOP_MIN: return MTLBlendOperationMin; + case SG_BLENDOP_MAX: return MTLBlendOperationMax; + default: SOKOL_UNREACHABLE; return (MTLBlendOperation)0; + } +} + +_SOKOL_PRIVATE MTLBlendFactor _sg_mtl_blend_factor(sg_blend_factor f) { + switch (f) { + case SG_BLENDFACTOR_ZERO: return MTLBlendFactorZero; + case SG_BLENDFACTOR_ONE: return MTLBlendFactorOne; + case SG_BLENDFACTOR_SRC_COLOR: return MTLBlendFactorSourceColor; + case SG_BLENDFACTOR_ONE_MINUS_SRC_COLOR: return MTLBlendFactorOneMinusSourceColor; + case SG_BLENDFACTOR_SRC_ALPHA: return MTLBlendFactorSourceAlpha; + case SG_BLENDFACTOR_ONE_MINUS_SRC_ALPHA: return MTLBlendFactorOneMinusSourceAlpha; + case SG_BLENDFACTOR_DST_COLOR: return MTLBlendFactorDestinationColor; + case SG_BLENDFACTOR_ONE_MINUS_DST_COLOR: return MTLBlendFactorOneMinusDestinationColor; + case SG_BLENDFACTOR_DST_ALPHA: return MTLBlendFactorDestinationAlpha; + case SG_BLENDFACTOR_ONE_MINUS_DST_ALPHA: return MTLBlendFactorOneMinusDestinationAlpha; + case SG_BLENDFACTOR_SRC_ALPHA_SATURATED: return MTLBlendFactorSourceAlphaSaturated; + case SG_BLENDFACTOR_BLEND_COLOR: return MTLBlendFactorBlendColor; + case SG_BLENDFACTOR_ONE_MINUS_BLEND_COLOR: return MTLBlendFactorOneMinusBlendColor; + case SG_BLENDFACTOR_BLEND_ALPHA: return MTLBlendFactorBlendAlpha; + case SG_BLENDFACTOR_ONE_MINUS_BLEND_ALPHA: return MTLBlendFactorOneMinusBlendAlpha; + default: SOKOL_UNREACHABLE; return (MTLBlendFactor)0; + } +} + +_SOKOL_PRIVATE MTLCompareFunction _sg_mtl_compare_func(sg_compare_func f) { + switch (f) { + case SG_COMPAREFUNC_NEVER: return MTLCompareFunctionNever; + case SG_COMPAREFUNC_LESS: return MTLCompareFunctionLess; + case SG_COMPAREFUNC_EQUAL: return MTLCompareFunctionEqual; + case SG_COMPAREFUNC_LESS_EQUAL: return MTLCompareFunctionLessEqual; + case SG_COMPAREFUNC_GREATER: return MTLCompareFunctionGreater; + case SG_COMPAREFUNC_NOT_EQUAL: return MTLCompareFunctionNotEqual; + case SG_COMPAREFUNC_GREATER_EQUAL: return MTLCompareFunctionGreaterEqual; + case SG_COMPAREFUNC_ALWAYS: return MTLCompareFunctionAlways; + default: SOKOL_UNREACHABLE; return (MTLCompareFunction)0; + } +} + +_SOKOL_PRIVATE MTLStencilOperation _sg_mtl_stencil_op(sg_stencil_op op) { + switch (op) { + case SG_STENCILOP_KEEP: return MTLStencilOperationKeep; + case SG_STENCILOP_ZERO: return MTLStencilOperationZero; + case SG_STENCILOP_REPLACE: return MTLStencilOperationReplace; + case SG_STENCILOP_INCR_CLAMP: return MTLStencilOperationIncrementClamp; + case SG_STENCILOP_DECR_CLAMP: return MTLStencilOperationDecrementClamp; + case SG_STENCILOP_INVERT: return MTLStencilOperationInvert; + case SG_STENCILOP_INCR_WRAP: return MTLStencilOperationIncrementWrap; + case SG_STENCILOP_DECR_WRAP: return MTLStencilOperationDecrementWrap; + default: SOKOL_UNREACHABLE; return (MTLStencilOperation)0; + } +} + +_SOKOL_PRIVATE MTLCullMode _sg_mtl_cull_mode(sg_cull_mode m) { + switch (m) { + case SG_CULLMODE_NONE: return MTLCullModeNone; + case SG_CULLMODE_FRONT: return MTLCullModeFront; + case SG_CULLMODE_BACK: return MTLCullModeBack; + default: SOKOL_UNREACHABLE; return (MTLCullMode)0; + } +} + +_SOKOL_PRIVATE MTLWinding _sg_mtl_winding(sg_face_winding w) { + switch (w) { + case SG_FACEWINDING_CW: return MTLWindingClockwise; + case SG_FACEWINDING_CCW: return MTLWindingCounterClockwise; + default: SOKOL_UNREACHABLE; return (MTLWinding)0; + } +} + +_SOKOL_PRIVATE MTLIndexType _sg_mtl_index_type(sg_index_type t) { + switch (t) { + case SG_INDEXTYPE_UINT16: return MTLIndexTypeUInt16; + case SG_INDEXTYPE_UINT32: return MTLIndexTypeUInt32; + default: SOKOL_UNREACHABLE; return (MTLIndexType)0; + } +} + +_SOKOL_PRIVATE int _sg_mtl_index_size(sg_index_type t) { + switch (t) { + case SG_INDEXTYPE_NONE: return 0; + case SG_INDEXTYPE_UINT16: return 2; + case SG_INDEXTYPE_UINT32: return 4; + default: SOKOL_UNREACHABLE; return 0; + } +} + +_SOKOL_PRIVATE MTLTextureType _sg_mtl_texture_type(sg_image_type t, bool msaa) { + switch (t) { + case SG_IMAGETYPE_2D: return msaa ? MTLTextureType2DMultisample : MTLTextureType2D; + case SG_IMAGETYPE_CUBE: return MTLTextureTypeCube; + case SG_IMAGETYPE_3D: return MTLTextureType3D; + // NOTE: MTLTextureType2DMultisampleArray requires macOS 10.14+, iOS 14.0+ + case SG_IMAGETYPE_ARRAY: return MTLTextureType2DArray; + default: SOKOL_UNREACHABLE; return (MTLTextureType)0; + } +} + +_SOKOL_PRIVATE MTLSamplerAddressMode _sg_mtl_address_mode(sg_wrap w) { + if (_sg.features.image_clamp_to_border) { + if (@available(macOS 12.0, iOS 14.0, *)) { + // border color feature available + switch (w) { + case SG_WRAP_REPEAT: return MTLSamplerAddressModeRepeat; + case SG_WRAP_CLAMP_TO_EDGE: return MTLSamplerAddressModeClampToEdge; + case SG_WRAP_CLAMP_TO_BORDER: return MTLSamplerAddressModeClampToBorderColor; + case SG_WRAP_MIRRORED_REPEAT: return MTLSamplerAddressModeMirrorRepeat; + default: SOKOL_UNREACHABLE; return (MTLSamplerAddressMode)0; + } + } + } + // fallthrough: clamp to border no supported + switch (w) { + case SG_WRAP_REPEAT: return MTLSamplerAddressModeRepeat; + case SG_WRAP_CLAMP_TO_EDGE: return MTLSamplerAddressModeClampToEdge; + case SG_WRAP_CLAMP_TO_BORDER: return MTLSamplerAddressModeClampToEdge; + case SG_WRAP_MIRRORED_REPEAT: return MTLSamplerAddressModeMirrorRepeat; + default: SOKOL_UNREACHABLE; return (MTLSamplerAddressMode)0; + } +} + +_SOKOL_PRIVATE API_AVAILABLE(ios(14.0), macos(12.0)) MTLSamplerBorderColor _sg_mtl_border_color(sg_border_color c) { + switch (c) { + case SG_BORDERCOLOR_TRANSPARENT_BLACK: return MTLSamplerBorderColorTransparentBlack; + case SG_BORDERCOLOR_OPAQUE_BLACK: return MTLSamplerBorderColorOpaqueBlack; + case SG_BORDERCOLOR_OPAQUE_WHITE: return MTLSamplerBorderColorOpaqueWhite; + default: SOKOL_UNREACHABLE; return (MTLSamplerBorderColor)0; + } +} + +_SOKOL_PRIVATE MTLSamplerMinMagFilter _sg_mtl_minmag_filter(sg_filter f) { + switch (f) { + case SG_FILTER_NEAREST: + return MTLSamplerMinMagFilterNearest; + case SG_FILTER_LINEAR: + return MTLSamplerMinMagFilterLinear; + default: + SOKOL_UNREACHABLE; return (MTLSamplerMinMagFilter)0; + } +} + +_SOKOL_PRIVATE MTLSamplerMipFilter _sg_mtl_mipmap_filter(sg_filter f) { + switch (f) { + case SG_FILTER_NEAREST: + return MTLSamplerMipFilterNearest; + case SG_FILTER_LINEAR: + return MTLSamplerMipFilterLinear; + default: + SOKOL_UNREACHABLE; return (MTLSamplerMipFilter)0; + } +} + +_SOKOL_PRIVATE size_t _sg_mtl_vertexbuffer_bindslot(size_t sokol_bindslot) { + return sokol_bindslot + _SG_MTL_MAX_STAGE_UB_SBUF_BINDINGS; +} + +//-- a pool for all Metal resource objects, with deferred release queue --------- +_SOKOL_PRIVATE void _sg_mtl_init_pool(const sg_desc* desc) { + _sg.mtl.idpool.num_slots = 2 * + ( + 2 * desc->buffer_pool_size + + 4 * desc->image_pool_size + + 1 * desc->sampler_pool_size + + 4 * desc->shader_pool_size + + 2 * desc->pipeline_pool_size + + desc->view_pool_size + + 128 + ); + _sg.mtl.idpool.pool = [NSMutableArray arrayWithCapacity:(NSUInteger)_sg.mtl.idpool.num_slots]; + _SG_OBJC_RETAIN(_sg.mtl.idpool.pool); + NSNull* null = [NSNull null]; + for (int i = 0; i < _sg.mtl.idpool.num_slots; i++) { + [_sg.mtl.idpool.pool addObject:null]; + } + SOKOL_ASSERT([_sg.mtl.idpool.pool count] == (NSUInteger)_sg.mtl.idpool.num_slots); + // a queue of currently free slot indices + _sg.mtl.idpool.free_queue_top = 0; + _sg.mtl.idpool.free_queue = (int*)_sg_malloc_clear((size_t)_sg.mtl.idpool.num_slots * sizeof(int)); + // pool slot 0 is reserved! + for (int i = _sg.mtl.idpool.num_slots-1; i >= 1; i--) { + _sg.mtl.idpool.free_queue[_sg.mtl.idpool.free_queue_top++] = i; + } + // a circular queue which holds release items (frame index when a resource is to be released, and the resource's pool index + _sg.mtl.idpool.release_queue_front = 0; + _sg.mtl.idpool.release_queue_back = 0; + _sg.mtl.idpool.release_queue = (_sg_mtl_release_item_t*)_sg_malloc_clear((size_t)_sg.mtl.idpool.num_slots * sizeof(_sg_mtl_release_item_t)); + for (int i = 0; i < _sg.mtl.idpool.num_slots; i++) { + _sg.mtl.idpool.release_queue[i].frame_index = 0; + _sg.mtl.idpool.release_queue[i].slot_index = _SG_MTL_INVALID_SLOT_INDEX; + } +} + +_SOKOL_PRIVATE void _sg_mtl_destroy_pool(void) { + _sg_free(_sg.mtl.idpool.release_queue); _sg.mtl.idpool.release_queue = 0; + _sg_free(_sg.mtl.idpool.free_queue); _sg.mtl.idpool.free_queue = 0; + _SG_OBJC_RELEASE(_sg.mtl.idpool.pool); +} + +// get a new free resource pool slot +_SOKOL_PRIVATE int _sg_mtl_alloc_pool_slot(void) { + SOKOL_ASSERT(_sg.mtl.idpool.free_queue_top > 0); + const int slot_index = _sg.mtl.idpool.free_queue[--_sg.mtl.idpool.free_queue_top]; + SOKOL_ASSERT((slot_index > 0) && (slot_index < _sg.mtl.idpool.num_slots)); + return slot_index; +} + +// put a free resource pool slot back into the free-queue +_SOKOL_PRIVATE void _sg_mtl_free_pool_slot(int slot_index) { + SOKOL_ASSERT(_sg.mtl.idpool.free_queue_top < _sg.mtl.idpool.num_slots); + SOKOL_ASSERT((slot_index > 0) && (slot_index < _sg.mtl.idpool.num_slots)); + _sg.mtl.idpool.free_queue[_sg.mtl.idpool.free_queue_top++] = slot_index; +} + +// add an MTLResource to the pool, return pool index or 0 if input was 'nil' +_SOKOL_PRIVATE int _sg_mtl_add_resource(id res) { + if (nil == res) { + return _SG_MTL_INVALID_SLOT_INDEX; + } + _sg_stats_add(metal.idpool.num_added, 1); + const int slot_index = _sg_mtl_alloc_pool_slot(); + // NOTE: the NSMutableArray will take ownership of its items + SOKOL_ASSERT([NSNull null] == _sg.mtl.idpool.pool[(NSUInteger)slot_index]); + _sg.mtl.idpool.pool[(NSUInteger)slot_index] = res; + return slot_index; +} + +/* mark an MTLResource for release, this will put the resource into the + deferred-release queue, and the resource will then be released N frames later, + the special pool index 0 will be ignored (this means that a nil + value was provided to _sg_mtl_add_resource() +*/ +_SOKOL_PRIVATE void _sg_mtl_release_resource(uint32_t frame_index, int slot_index) { + if (slot_index == _SG_MTL_INVALID_SLOT_INDEX) { + return; + } + _sg_stats_add(metal.idpool.num_released, 1); + SOKOL_ASSERT((slot_index > 0) && (slot_index < _sg.mtl.idpool.num_slots)); + SOKOL_ASSERT([NSNull null] != _sg.mtl.idpool.pool[(NSUInteger)slot_index]); + int release_index = _sg.mtl.idpool.release_queue_front++; + if (_sg.mtl.idpool.release_queue_front >= _sg.mtl.idpool.num_slots) { + // wrap-around + _sg.mtl.idpool.release_queue_front = 0; + } + // release queue full? + SOKOL_ASSERT(_sg.mtl.idpool.release_queue_front != _sg.mtl.idpool.release_queue_back); + SOKOL_ASSERT(0 == _sg.mtl.idpool.release_queue[release_index].frame_index); + const uint32_t safe_to_release_frame_index = frame_index + SG_NUM_INFLIGHT_FRAMES + 1; + _sg.mtl.idpool.release_queue[release_index].frame_index = safe_to_release_frame_index; + _sg.mtl.idpool.release_queue[release_index].slot_index = slot_index; +} + +// run garbage-collection pass on all resources in the release-queue +_SOKOL_PRIVATE void _sg_mtl_garbage_collect(uint32_t frame_index) { + while (_sg.mtl.idpool.release_queue_back != _sg.mtl.idpool.release_queue_front) { + if (frame_index < _sg.mtl.idpool.release_queue[_sg.mtl.idpool.release_queue_back].frame_index) { + // don't need to check further, release-items past this are too young + break; + } + _sg_stats_add(metal.idpool.num_garbage_collected, 1); + // safe to release this resource + const int slot_index = _sg.mtl.idpool.release_queue[_sg.mtl.idpool.release_queue_back].slot_index; + SOKOL_ASSERT((slot_index > 0) && (slot_index < _sg.mtl.idpool.num_slots)); + // note: the NSMutableArray takes ownership of its items, assigning an NSNull object will + // release the object, no matter if using ARC or not + SOKOL_ASSERT(_sg.mtl.idpool.pool[(NSUInteger)slot_index] != [NSNull null]); + _sg.mtl.idpool.pool[(NSUInteger)slot_index] = [NSNull null]; + // put the now free pool index back on the free queue + _sg_mtl_free_pool_slot(slot_index); + // reset the release queue slot and advance the back index + _sg.mtl.idpool.release_queue[_sg.mtl.idpool.release_queue_back].frame_index = 0; + _sg.mtl.idpool.release_queue[_sg.mtl.idpool.release_queue_back].slot_index = _SG_MTL_INVALID_SLOT_INDEX; + _sg.mtl.idpool.release_queue_back++; + if (_sg.mtl.idpool.release_queue_back >= _sg.mtl.idpool.num_slots) { + // wrap-around + _sg.mtl.idpool.release_queue_back = 0; + } + } +} + +_SOKOL_PRIVATE id _sg_mtl_id(int slot_index) { + return _sg.mtl.idpool.pool[(NSUInteger)slot_index]; +} + +_SOKOL_PRIVATE void _sg_mtl_clear_state_cache(void) { + _sg_clear(&_sg.mtl.cache, sizeof(_sg.mtl.cache)); +} + +// https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf +_SOKOL_PRIVATE void _sg_mtl_init_caps(void) { + #if defined(_SG_TARGET_MACOS) + _sg.backend = SG_BACKEND_METAL_MACOS; + #elif defined(_SG_TARGET_IOS) + #if defined(_SG_TARGET_IOS_SIMULATOR) + _sg.backend = SG_BACKEND_METAL_SIMULATOR; + #else + _sg.backend = SG_BACKEND_METAL_IOS; + #endif + #endif + _sg.features.origin_top_left = true; + _sg.features.mrt_independent_blend_state = true; + _sg.features.mrt_independent_write_mask = true; + _sg.features.compute = true; + _sg.features.msaa_texture_bindings = true; + _sg.features.draw_base_vertex = true; + _sg.features.draw_base_instance = true; + + _sg.features.image_clamp_to_border = false; + #if (MAC_OS_X_VERSION_MAX_ALLOWED >= 120000) || (__IPHONE_OS_VERSION_MAX_ALLOWED >= 140000) + if (@available(macOS 12.0, iOS 14.0, *)) { + _sg.features.image_clamp_to_border = [_sg.mtl.device supportsFamily:MTLGPUFamilyApple7] + || [_sg.mtl.device supportsFamily:MTLGPUFamilyMac2]; + #if (MAC_OS_X_VERSION_MAX_ALLOWED >= 130000) || (__IPHONE_OS_VERSION_MAX_ALLOWED >= 160000) + if (!_sg.features.image_clamp_to_border) { + if (@available(macOS 13.0, iOS 16.0, *)) { + _sg.features.image_clamp_to_border = [_sg.mtl.device supportsFamily:MTLGPUFamilyMetal3]; + } + } + #endif + } + #endif + + #if defined(_SG_TARGET_MACOS) + _sg.limits.max_image_size_2d = 16 * 1024; + _sg.limits.max_image_size_cube = 16 * 1024; + _sg.limits.max_image_size_3d = 2 * 1024; + _sg.limits.max_image_size_array = 16 * 1024; + _sg.limits.max_image_array_layers = 2 * 1024; + _sg.limits.max_texture_bindings_per_stage = _sg_min(128, SG_MAX_VIEW_BINDSLOTS); + #else + // FIXME: newer iOS devices support 16k textures + _sg.limits.max_image_size_2d = 8 * 1024; + _sg.limits.max_image_size_cube = 8 * 1024; + _sg.limits.max_image_size_3d = 2 * 1024; + _sg.limits.max_image_size_array = 8 * 1024; + _sg.limits.max_image_array_layers = 2 * 1024; + _sg.limits.max_texture_bindings_per_stage = _sg_min(96, SG_MAX_VIEW_BINDSLOTS); // since iPhone8 + #endif + _sg.limits.max_storage_image_bindings_per_stage = _sg.limits.max_texture_bindings_per_stage; // shared with texture bindings + _sg.limits.max_storage_buffer_bindings_per_stage = _sg_min(_SG_MTL_MAX_STAGE_BUFFER_BINDINGS - (SG_MAX_VERTEXBUFFER_BINDSLOTS + SG_MAX_UNIFORMBLOCK_BINDSLOTS), SG_MAX_VIEW_BINDSLOTS); + _sg.limits.max_color_attachments = _sg_min(8, SG_MAX_COLOR_ATTACHMENTS); + _sg.limits.max_vertex_attrs = SG_MAX_VERTEX_ATTRIBUTES; + + _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_R8]); + _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_R8SN]); + _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_R8UI]); + _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_R8SI]); + #if defined(_SG_TARGET_MACOS) + _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_R16]); + _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_R16SN]); + #else + _sg_pixelformat_sfbr(&_sg.formats[SG_PIXELFORMAT_R16]); + _sg_pixelformat_sfbr(&_sg.formats[SG_PIXELFORMAT_R16SN]); + #endif + _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_R16UI]); + _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_R16SI]); + _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_R16F]); + _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RG8]); + _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RG8SN]); + _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RG8UI]); + _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RG8SI]); + _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_R32UI]); + _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_R32SI]); + #if defined(_SG_TARGET_MACOS) + _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_R32F]); + #else + _sg_pixelformat_sbr(&_sg.formats[SG_PIXELFORMAT_R32F]); + #endif + #if defined(_SG_TARGET_MACOS) + _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RG16]); + _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RG16SN]); + #else + _sg_pixelformat_sfbr(&_sg.formats[SG_PIXELFORMAT_RG16]); + _sg_pixelformat_sfbr(&_sg.formats[SG_PIXELFORMAT_RG16SN]); + #endif + _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RG16UI]); + _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RG16SI]); + _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RG16F]); + _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RGBA8]); + _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_SRGB8A8]); + _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RGBA8SN]); + _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RGBA8UI]); + _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RGBA8SI]); + _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_BGRA8]); + _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RGB10A2]); + _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RG11B10F]); + #if defined(_SG_TARGET_MACOS) + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_RGB9E5]); + _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RG32UI]); + _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RG32SI]); + #else + _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RGB9E5]); + _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_RG32UI]); + _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_RG32SI]); + #endif + #if defined(_SG_TARGET_MACOS) + _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RG32F]); + #else + _sg_pixelformat_sbr(&_sg.formats[SG_PIXELFORMAT_RG32F]); + #endif + #if defined(_SG_TARGET_MACOS) + _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RGBA16]); + _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RGBA16SN]); + #else + _sg_pixelformat_sfbr(&_sg.formats[SG_PIXELFORMAT_RGBA16]); + _sg_pixelformat_sfbr(&_sg.formats[SG_PIXELFORMAT_RGBA16SN]); + #endif + _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RGBA16UI]); + _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RGBA16SI]); + _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RGBA16F]); + #if defined(_SG_TARGET_MACOS) + _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RGBA32UI]); + _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RGBA32SI]); + _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RGBA32F]); + #else + _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_RGBA32UI]); + _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_RGBA32SI]); + _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_RGBA32F]); + #endif + _sg_pixelformat_srmd(&_sg.formats[SG_PIXELFORMAT_DEPTH]); + _sg_pixelformat_srmd(&_sg.formats[SG_PIXELFORMAT_DEPTH_STENCIL]); + #if defined(_SG_TARGET_MACOS) + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC1_RGBA]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC2_RGBA]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC3_RGBA]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC3_SRGBA]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC4_R]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC4_RSN]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC5_RG]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC5_RGSN]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC6H_RGBF]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC6H_RGBUF]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC7_RGBA]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC7_SRGBA]); + #else + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_ETC2_RGB8]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_ETC2_SRGB8]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_ETC2_RGB8A1]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_ETC2_RGBA8]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_ETC2_SRGB8A8]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_EAC_R11]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_EAC_R11SN]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_EAC_RG11]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_EAC_RG11SN]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_ASTC_4x4_RGBA]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_ASTC_4x4_SRGBA]); + #endif + + // compute shader access (see: https://github.com/gpuweb/gpuweb/issues/513) + // for now let's use the same conservative set on all backends even though + // some backends are less restrictive + _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_RGBA8]); + _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_RGBA8SN]); + _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_RGBA8UI]); + _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_RGBA8SI]); + _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_RGBA16UI]); + _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_RGBA16SI]); + _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_RGBA16F]); + _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_R32UI]); + _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_R32SI]); + _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_R32F]); + _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_RG32UI]); + _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_RG32SI]); + _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_RG32F]); + _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_RGBA32UI]); + _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_RGBA32SI]); + _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_RGBA32F]); +} + +//-- main Metal backend state and functions ------------------------------------ +_SOKOL_PRIVATE void _sg_mtl_setup_backend(const sg_desc* desc) { + // assume already zero-initialized + SOKOL_ASSERT(desc); + SOKOL_ASSERT(desc->environment.metal.device); + SOKOL_ASSERT(desc->uniform_buffer_size > 0); + _sg_mtl_init_pool(desc); + _sg_mtl_clear_state_cache(); + _sg.mtl.valid = true; + _sg.mtl.ub_size = desc->uniform_buffer_size; + _sg.mtl.sem = dispatch_semaphore_create(SG_NUM_INFLIGHT_FRAMES); + _sg.mtl.device = (__bridge id) desc->environment.metal.device; + _sg.mtl.cmd_queue = [_sg.mtl.device newCommandQueue]; + + for (int i = 0; i < SG_NUM_INFLIGHT_FRAMES; i++) { + _sg.mtl.uniform_buffers[i] = [_sg.mtl.device + newBufferWithLength:(NSUInteger)_sg.mtl.ub_size + options:MTLResourceCPUCacheModeWriteCombined|MTLResourceStorageModeShared + ]; + #if defined(SOKOL_DEBUG) + _sg.mtl.uniform_buffers[i].label = [NSString stringWithFormat:@"sg-uniform-buffer.%d", i]; + #endif + } + + if (desc->mtl_force_managed_storage_mode) { + _sg.mtl.use_shared_storage_mode = false; + } else if (@available(macOS 10.15, iOS 13.0, *)) { + // on Intel Macs, always use managed resources even though the + // device says it supports unified memory (because of texture restrictions) + const bool is_apple_gpu = [_sg.mtl.device supportsFamily:MTLGPUFamilyApple1]; + if (!is_apple_gpu) { + _sg.mtl.use_shared_storage_mode = false; + } else { + _sg.mtl.use_shared_storage_mode = true; + } + } else { + #if defined(_SG_TARGET_MACOS) + _sg.mtl.use_shared_storage_mode = false; + #else + _sg.mtl.use_shared_storage_mode = true; + #endif + } + _sg_mtl_init_caps(); +} + +_SOKOL_PRIVATE void _sg_mtl_discard_backend(void) { + SOKOL_ASSERT(_sg.mtl.valid); + // wait for the last frame to finish + for (int i = 0; i < SG_NUM_INFLIGHT_FRAMES; i++) { + dispatch_semaphore_wait(_sg.mtl.sem, DISPATCH_TIME_FOREVER); + } + // semaphore must be "relinquished" before destruction + for (int i = 0; i < SG_NUM_INFLIGHT_FRAMES; i++) { + dispatch_semaphore_signal(_sg.mtl.sem); + } + _sg_mtl_garbage_collect(_sg.frame_index + SG_NUM_INFLIGHT_FRAMES + 2); + _sg_mtl_destroy_pool(); + _sg.mtl.valid = false; + + _SG_OBJC_RELEASE(_sg.mtl.sem); + _SG_OBJC_RELEASE(_sg.mtl.device); + _SG_OBJC_RELEASE(_sg.mtl.cmd_queue); + for (int i = 0; i < SG_NUM_INFLIGHT_FRAMES; i++) { + _SG_OBJC_RELEASE(_sg.mtl.uniform_buffers[i]); + } + // NOTE: MTLCommandBuffer, MTLRenderCommandEncoder and MTLComputeCommandEncoder are auto-released + _sg.mtl.cmd_buffer = nil; + _sg.mtl.render_cmd_encoder = nil; + _sg.mtl.compute_cmd_encoder = nil; +} + +_SOKOL_PRIVATE void _sg_mtl_reset_state_cache(void) { + _sg_mtl_clear_state_cache(); +} + +_SOKOL_PRIVATE sg_resource_state _sg_mtl_create_buffer(_sg_buffer_t* buf, const sg_buffer_desc* desc) { + SOKOL_ASSERT(buf && desc); + SOKOL_ASSERT(buf->cmn.size > 0); + const bool injected = (0 != desc->mtl_buffers[0]); + MTLResourceOptions mtl_options = _sg_mtl_buffer_resource_options(&buf->cmn.usage); + for (int slot = 0; slot < buf->cmn.num_slots; slot++) { + id mtl_buf; + if (injected) { + SOKOL_ASSERT(desc->mtl_buffers[slot]); + mtl_buf = (__bridge id) desc->mtl_buffers[slot]; + } else { + if (desc->data.ptr) { + SOKOL_ASSERT(desc->data.size > 0); + mtl_buf = [_sg.mtl.device newBufferWithBytes:desc->data.ptr length:(NSUInteger)buf->cmn.size options:mtl_options]; + } else { + mtl_buf = [_sg.mtl.device newBufferWithLength:(NSUInteger)buf->cmn.size options:mtl_options]; + } + if (nil == mtl_buf) { + _SG_ERROR(METAL_CREATE_BUFFER_FAILED); + return SG_RESOURCESTATE_FAILED; + } + } + #if defined(SOKOL_DEBUG) + if (desc->label) { + mtl_buf.label = [NSString stringWithFormat:@"%s.%d", desc->label, slot]; + } + #endif + buf->mtl.buf[slot] = _sg_mtl_add_resource(mtl_buf); + _SG_OBJC_RELEASE(mtl_buf); + } + return SG_RESOURCESTATE_VALID; +} + +_SOKOL_PRIVATE void _sg_mtl_discard_buffer(_sg_buffer_t* buf) { + SOKOL_ASSERT(buf); + for (int slot = 0; slot < buf->cmn.num_slots; slot++) { + // it's valid to call release resource with '0' + _sg_mtl_release_resource(_sg.frame_index, buf->mtl.buf[slot]); + } +} + +_SOKOL_PRIVATE void _sg_mtl_copy_image_data(const _sg_image_t* img, __unsafe_unretained id mtl_tex, const sg_image_data* data) { + const int num_slices = (img->cmn.type == SG_IMAGETYPE_3D) ? 1 : img->cmn.num_slices; + for (int mip_index = 0; mip_index < img->cmn.num_mipmaps; mip_index++) { + SOKOL_ASSERT(data->mip_levels[mip_index].ptr); + SOKOL_ASSERT(data->mip_levels[mip_index].size > 0); + const uint8_t* data_ptr = (const uint8_t*)data->mip_levels[mip_index].ptr; + const int mip_width = _sg_miplevel_dim(img->cmn.width, mip_index); + const int mip_height = _sg_miplevel_dim(img->cmn.height, mip_index); + int bytes_per_row = _sg_row_pitch(img->cmn.pixel_format, mip_width, 1); + int bytes_per_slice = _sg_surface_pitch(img->cmn.pixel_format, mip_width, mip_height, 1); + /* bytesPerImage special case: https://developer.apple.com/documentation/metal/mtltexture/1515679-replaceregion + + "Supply a nonzero value only when you copy data to a MTLTextureType3D type texture" + */ + MTLRegion region; + int bytes_per_image; + if (img->cmn.type == SG_IMAGETYPE_3D) { + const int mip_depth = _sg_miplevel_dim(img->cmn.num_slices, mip_index); + region = MTLRegionMake3D(0, 0, 0, (NSUInteger)mip_width, (NSUInteger)mip_height, (NSUInteger)mip_depth); + bytes_per_image = bytes_per_slice; + // FIXME: apparently the minimal bytes_per_image size for 3D texture is 4 KByte... somehow need to handle this + } else { + region = MTLRegionMake2D(0, 0, (NSUInteger)mip_width, (NSUInteger)mip_height); + bytes_per_image = 0; + } + + for (int slice_index = 0; slice_index < num_slices; slice_index++) { + const int slice_offset = slice_index * bytes_per_slice; + SOKOL_ASSERT((slice_offset + bytes_per_slice) <= (int)data->mip_levels[mip_index].size); + [mtl_tex replaceRegion:region + mipmapLevel:(NSUInteger)mip_index + slice:(NSUInteger)slice_index + withBytes:data_ptr + slice_offset + bytesPerRow:(NSUInteger)bytes_per_row + bytesPerImage:(NSUInteger)bytes_per_image]; + } + } +} + +_SOKOL_PRIVATE bool _sg_mtl_init_texdesc(MTLTextureDescriptor* mtl_desc, _sg_image_t* img) { + mtl_desc.textureType = _sg_mtl_texture_type(img->cmn.type, img->cmn.sample_count > 1); + mtl_desc.pixelFormat = _sg_mtl_pixel_format(img->cmn.pixel_format); + if (MTLPixelFormatInvalid == mtl_desc.pixelFormat) { + _SG_ERROR(METAL_TEXTURE_FORMAT_NOT_SUPPORTED); + return false; + } + mtl_desc.width = (NSUInteger)img->cmn.width; + mtl_desc.height = (NSUInteger)img->cmn.height; + if (SG_IMAGETYPE_3D == img->cmn.type) { + mtl_desc.depth = (NSUInteger)img->cmn.num_slices; + } else { + mtl_desc.depth = 1; + } + mtl_desc.mipmapLevelCount = (NSUInteger)img->cmn.num_mipmaps; + if (SG_IMAGETYPE_ARRAY == img->cmn.type) { + mtl_desc.arrayLength = (NSUInteger)img->cmn.num_slices; + } else { + mtl_desc.arrayLength = 1; + } + mtl_desc.sampleCount = (NSUInteger)img->cmn.sample_count; + + const sg_image_usage* usg = &img->cmn.usage; + const bool any_attachment = usg->color_attachment || usg->resolve_attachment || usg->depth_stencil_attachment; + MTLTextureUsage mtl_tex_usage = MTLTextureUsageShaderRead; + if (any_attachment) { + mtl_tex_usage |= MTLTextureUsageRenderTarget; + } + if (img->cmn.usage.storage_image) { + mtl_tex_usage |= MTLTextureUsageShaderWrite; + } + mtl_desc.usage = mtl_tex_usage; + + MTLResourceOptions mtl_res_options = 0; + if (any_attachment || img->cmn.usage.storage_image) { + mtl_res_options |= MTLResourceStorageModePrivate; + } else { + mtl_res_options |= _sg_mtl_resource_options_storage_mode_managed_or_shared(); + if (!img->cmn.usage.immutable) { + mtl_res_options |= MTLResourceCPUCacheModeWriteCombined; + } + } + mtl_desc.resourceOptions = mtl_res_options; + return true; +} + +_SOKOL_PRIVATE sg_resource_state _sg_mtl_create_image(_sg_image_t* img, const sg_image_desc* desc) { + SOKOL_ASSERT(img && desc); + const bool injected = (0 != desc->mtl_textures[0]); + + // first initialize all Metal resource pool slots to 'empty' + for (int i = 0; i < SG_NUM_INFLIGHT_FRAMES; i++) { + img->mtl.tex[i] = _sg_mtl_add_resource(nil); + } + + // initialize a Metal texture descriptor + MTLTextureDescriptor* mtl_desc = [[MTLTextureDescriptor alloc] init]; + if (!_sg_mtl_init_texdesc(mtl_desc, img)) { + _SG_OBJC_RELEASE(mtl_desc); + return SG_RESOURCESTATE_FAILED; + } + for (int slot = 0; slot < img->cmn.num_slots; slot++) { + id mtl_tex; + if (injected) { + SOKOL_ASSERT(desc->mtl_textures[slot]); + mtl_tex = (__bridge id) desc->mtl_textures[slot]; + } else { + mtl_tex = [_sg.mtl.device newTextureWithDescriptor:mtl_desc]; + if (nil == mtl_tex) { + _SG_OBJC_RELEASE(mtl_desc); + _SG_ERROR(METAL_CREATE_TEXTURE_FAILED); + return SG_RESOURCESTATE_FAILED; + } + if (desc->data.mip_levels[0].ptr) { + _sg_mtl_copy_image_data(img, mtl_tex, &desc->data); + } + } + #if defined(SOKOL_DEBUG) + if (desc->label) { + mtl_tex.label = [NSString stringWithFormat:@"%s.%d", desc->label, slot]; + } + #endif + img->mtl.tex[slot] = _sg_mtl_add_resource(mtl_tex); + _SG_OBJC_RELEASE(mtl_tex); + } + _SG_OBJC_RELEASE(mtl_desc); + return SG_RESOURCESTATE_VALID; +} + +_SOKOL_PRIVATE void _sg_mtl_discard_image(_sg_image_t* img) { + SOKOL_ASSERT(img); + // it's valid to call release resource with a 'null resource' + for (int slot = 0; slot < img->cmn.num_slots; slot++) { + _sg_mtl_release_resource(_sg.frame_index, img->mtl.tex[slot]); + } +} + +_SOKOL_PRIVATE sg_resource_state _sg_mtl_create_sampler(_sg_sampler_t* smp, const sg_sampler_desc* desc) { + SOKOL_ASSERT(smp && desc); + id mtl_smp; + const bool injected = (0 != desc->mtl_sampler); + if (injected) { + SOKOL_ASSERT(desc->mtl_sampler); + mtl_smp = (__bridge id) desc->mtl_sampler; + } else { + MTLSamplerDescriptor* mtl_desc = [[MTLSamplerDescriptor alloc] init]; + mtl_desc.sAddressMode = _sg_mtl_address_mode(desc->wrap_u); + mtl_desc.tAddressMode = _sg_mtl_address_mode(desc->wrap_v); + mtl_desc.rAddressMode = _sg_mtl_address_mode(desc->wrap_w); + if (_sg.features.image_clamp_to_border) { + if (@available(macOS 12.0, iOS 14.0, *)) { + mtl_desc.borderColor = _sg_mtl_border_color(desc->border_color); + } + } + mtl_desc.minFilter = _sg_mtl_minmag_filter(desc->min_filter); + mtl_desc.magFilter = _sg_mtl_minmag_filter(desc->mag_filter); + mtl_desc.mipFilter = _sg_mtl_mipmap_filter(desc->mipmap_filter); + mtl_desc.lodMinClamp = desc->min_lod; + mtl_desc.lodMaxClamp = desc->max_lod; + // FIXME: lodAverage? + mtl_desc.maxAnisotropy = desc->max_anisotropy; + mtl_desc.normalizedCoordinates = YES; + mtl_desc.compareFunction = _sg_mtl_compare_func(desc->compare); + #if defined(SOKOL_DEBUG) + if (desc->label) { + mtl_desc.label = [NSString stringWithUTF8String:desc->label]; + } + #endif + mtl_smp = [_sg.mtl.device newSamplerStateWithDescriptor:mtl_desc]; + _SG_OBJC_RELEASE(mtl_desc); + if (nil == mtl_smp) { + _SG_ERROR(METAL_CREATE_SAMPLER_FAILED); + return SG_RESOURCESTATE_FAILED; + } + } + smp->mtl.sampler_state = _sg_mtl_add_resource(mtl_smp); + _SG_OBJC_RELEASE(mtl_smp); + return SG_RESOURCESTATE_VALID; +} + +_SOKOL_PRIVATE void _sg_mtl_discard_sampler(_sg_sampler_t* smp) { + SOKOL_ASSERT(smp); + // it's valid to call release resource with a 'null resource' + _sg_mtl_release_resource(_sg.frame_index, smp->mtl.sampler_state); +} + +_SOKOL_PRIVATE id _sg_mtl_compile_library(const char* src) { + NSError* err = NULL; + id lib = [_sg.mtl.device + newLibraryWithSource:[NSString stringWithUTF8String:src] + options:nil + error:&err + ]; + if (err) { + _SG_ERROR(METAL_SHADER_COMPILATION_FAILED); + _SG_LOGMSG(METAL_SHADER_COMPILATION_OUTPUT, [err.localizedDescription UTF8String]); + } + return lib; +} + +_SOKOL_PRIVATE id _sg_mtl_library_from_bytecode(const void* ptr, size_t num_bytes) { + NSError* err = NULL; + dispatch_data_t lib_data = dispatch_data_create(ptr, num_bytes, NULL, DISPATCH_DATA_DESTRUCTOR_DEFAULT); + id lib = [_sg.mtl.device newLibraryWithData:lib_data error:&err]; + if (err) { + _SG_ERROR(METAL_SHADER_CREATION_FAILED); + _SG_LOGMSG(METAL_SHADER_COMPILATION_OUTPUT, [err.localizedDescription UTF8String]); + } + _SG_OBJC_RELEASE(lib_data); + return lib; +} + +_SOKOL_PRIVATE bool _sg_mtl_create_shader_func(const sg_shader_function* func, const char* label, const char* label_ext, _sg_mtl_shader_func_t* res) { + SOKOL_ASSERT(res->mtl_lib == _SG_MTL_INVALID_SLOT_INDEX); + SOKOL_ASSERT(res->mtl_func == _SG_MTL_INVALID_SLOT_INDEX); + id mtl_lib = nil; + if (func->bytecode.ptr) { + SOKOL_ASSERT(func->bytecode.size > 0); + mtl_lib = _sg_mtl_library_from_bytecode(func->bytecode.ptr, func->bytecode.size); + } else if (func->source) { + mtl_lib = _sg_mtl_compile_library(func->source); + } + if (mtl_lib == nil) { + return false; + } + #if defined(SOKOL_DEBUG) + if (label) { + SOKOL_ASSERT(label_ext); + mtl_lib.label = [NSString stringWithFormat:@"%s.%s", label, label_ext]; + } + #else + _SOKOL_UNUSED(label); + _SOKOL_UNUSED(label_ext); + #endif + SOKOL_ASSERT(func->entry); + id mtl_func = [mtl_lib newFunctionWithName:[NSString stringWithUTF8String:func->entry]]; + if (mtl_func == nil) { + _SG_ERROR(METAL_SHADER_ENTRY_NOT_FOUND); + _SG_OBJC_RELEASE(mtl_lib); + return false; + } + res->mtl_lib = _sg_mtl_add_resource(mtl_lib); + res->mtl_func = _sg_mtl_add_resource(mtl_func); + _SG_OBJC_RELEASE(mtl_lib); + _SG_OBJC_RELEASE(mtl_func); + return true; +} + +_SOKOL_PRIVATE void _sg_mtl_discard_shader_func(const _sg_mtl_shader_func_t* func) { + // it is valid to call _sg_mtl_release_resource with a 'null resource' + _sg_mtl_release_resource(_sg.frame_index, func->mtl_func); + _sg_mtl_release_resource(_sg.frame_index, func->mtl_lib); +} + +// NOTE: this is an out-of-range check for MSL bindslots that's also active in release mode +_SOKOL_PRIVATE bool _sg_mtl_ensure_msl_bindslot_ranges(const sg_shader_desc* desc) { + SOKOL_ASSERT(desc); + for (size_t i = 0; i < SG_MAX_UNIFORMBLOCK_BINDSLOTS; i++) { + const sg_shader_uniform_block* ub = &desc->uniform_blocks[i]; + if (ub->stage != SG_SHADERSTAGE_NONE) { + if (ub->msl_buffer_n >= _SG_MTL_MAX_STAGE_UB_BINDINGS) { + _SG_ERROR(METAL_UNIFORMBLOCK_MSL_BUFFER_SLOT_OUT_OF_RANGE); + return false; + } + } + } + for (size_t i = 0; i < SG_MAX_VIEW_BINDSLOTS; i++) { + const sg_shader_view* view = &desc->views[i]; + if (view->texture.stage != SG_SHADERSTAGE_NONE) { + if (view->texture.msl_texture_n >= _SG_MTL_MAX_STAGE_TEXTURE_BINDINGS) { + _SG_ERROR(METAL_IMAGE_MSL_TEXTURE_SLOT_OUT_OF_RANGE); + return false; + } + } + if (view->storage_buffer.stage != SG_SHADERSTAGE_NONE) { + if ((view->storage_buffer.msl_buffer_n < _SG_MTL_MAX_STAGE_UB_BINDINGS) || + (view->storage_buffer.msl_buffer_n >= _SG_MTL_MAX_STAGE_UB_SBUF_BINDINGS)) + { + _SG_ERROR(METAL_STORAGEBUFFER_MSL_BUFFER_SLOT_OUT_OF_RANGE); + return false; + } + } + if (view->storage_image.stage != SG_SHADERSTAGE_NONE) { + if (view->storage_image.msl_texture_n >= _SG_MTL_MAX_STAGE_TEXTURE_BINDINGS) { + _SG_ERROR(METAL_STORAGEIMAGE_MSL_TEXTURE_SLOT_OUT_OF_RANGE); + return false; + } + } + } + for (size_t i = 0; i < SG_MAX_SAMPLER_BINDSLOTS; i++) { + const sg_shader_sampler* smp = &desc->samplers[i]; + if (smp->stage != SG_SHADERSTAGE_NONE) { + if (smp->msl_sampler_n >= _SG_MTL_MAX_STAGE_SAMPLER_BINDINGS) { + _SG_ERROR(METAL_SAMPLER_MSL_SAMPLER_SLOT_OUT_OF_RANGE); + return false; + } + } + } + return true; +} + +_SOKOL_PRIVATE sg_resource_state _sg_mtl_create_shader(_sg_shader_t* shd, const sg_shader_desc* desc) { + SOKOL_ASSERT(shd && desc); + + // do a MSL bindslot range check also in release mode, and if that fails, + // also fail shader creation + if (!_sg_mtl_ensure_msl_bindslot_ranges(desc)) { + return SG_RESOURCESTATE_FAILED; + } + + shd->mtl.threads_per_threadgroup = MTLSizeMake( + (NSUInteger)desc->mtl_threads_per_threadgroup.x, + (NSUInteger)desc->mtl_threads_per_threadgroup.y, + (NSUInteger)desc->mtl_threads_per_threadgroup.z); + + // copy resource bindslot mappings + for (size_t i = 0; i < SG_MAX_UNIFORMBLOCK_BINDSLOTS; i++) { + shd->mtl.ub_buffer_n[i] = desc->uniform_blocks[i].msl_buffer_n; + } + for (size_t i = 0; i < SG_MAX_VIEW_BINDSLOTS; i++) { + const sg_shader_view* view = &desc->views[i]; + SOKOL_ASSERT(0 == shd->mtl.view_buffer_texture_n[i]); + if (view->storage_buffer.stage != SG_SHADERSTAGE_NONE) { + shd->mtl.view_buffer_texture_n[i] = view->storage_buffer.msl_buffer_n; + } else if (view->texture.stage != SG_SHADERSTAGE_NONE) { + shd->mtl.view_buffer_texture_n[i] = view->texture.msl_texture_n; + } else if (view->storage_image.stage != SG_SHADERSTAGE_NONE) { + shd->mtl.view_buffer_texture_n[i] = view->storage_image.msl_texture_n; + } + } + for (size_t i = 0; i < SG_MAX_SAMPLER_BINDSLOTS; i++) { + shd->mtl.smp_sampler_n[i] = desc->samplers[i].msl_sampler_n; + } + + // create metal library and function objects + bool shd_valid = true; + if (desc->vertex_func.source || desc->vertex_func.bytecode.ptr) { + shd_valid &= _sg_mtl_create_shader_func(&desc->vertex_func, desc->label, "vs", &shd->mtl.vertex_func); + } + if (desc->fragment_func.source || desc->fragment_func.bytecode.ptr) { + shd_valid &= _sg_mtl_create_shader_func(&desc->fragment_func, desc->label, "fs", &shd->mtl.fragment_func); + } + if (desc->compute_func.source || desc->compute_func.bytecode.ptr) { + shd_valid &= _sg_mtl_create_shader_func(&desc->compute_func, desc->label, "cs", &shd->mtl.compute_func); + } + if (!shd_valid) { + _sg_mtl_discard_shader_func(&shd->mtl.vertex_func); + _sg_mtl_discard_shader_func(&shd->mtl.fragment_func); + _sg_mtl_discard_shader_func(&shd->mtl.compute_func); + } + return shd_valid ? SG_RESOURCESTATE_VALID : SG_RESOURCESTATE_FAILED; +} + +_SOKOL_PRIVATE void _sg_mtl_discard_shader(_sg_shader_t* shd) { + SOKOL_ASSERT(shd); + _sg_mtl_discard_shader_func(&shd->mtl.vertex_func); + _sg_mtl_discard_shader_func(&shd->mtl.fragment_func); + _sg_mtl_discard_shader_func(&shd->mtl.compute_func); +} + +_SOKOL_PRIVATE sg_resource_state _sg_mtl_create_pipeline(_sg_pipeline_t* pip, const sg_pipeline_desc* desc) { + SOKOL_ASSERT(pip && desc); + _sg_shader_t* shd = _sg_shader_ref_ptr(&pip->cmn.shader); + if (pip->cmn.is_compute) { + NSError* err = NULL; + MTLComputePipelineDescriptor* cp_desc = [[MTLComputePipelineDescriptor alloc] init]; + cp_desc.computeFunction = _sg_mtl_id(shd->mtl.compute_func.mtl_func); + cp_desc.threadGroupSizeIsMultipleOfThreadExecutionWidth = true; + for (size_t i = 0; i < SG_MAX_VIEW_BINDSLOTS; i++) { + const _sg_shader_view_t* view = &shd->cmn.views[i]; + if (view->view_type != SG_VIEWTYPE_STORAGEBUFFER) { + continue; + } + if (!view->sbuf_readonly) { + continue; + } + SOKOL_ASSERT(view->stage == SG_SHADERSTAGE_COMPUTE); + const NSUInteger mtl_slot = shd->mtl.view_buffer_texture_n[i]; + SOKOL_ASSERT(mtl_slot < _SG_MTL_MAX_STAGE_BUFFER_BINDINGS); + cp_desc.buffers[mtl_slot].mutability = MTLMutabilityImmutable; + } + #if defined(SOKOL_DEBUG) + if (desc->label) { + cp_desc.label = [NSString stringWithFormat:@"%s", desc->label]; + } + #endif + id mtl_cps = [_sg.mtl.device + newComputePipelineStateWithDescriptor:cp_desc + options:MTLPipelineOptionNone + reflection:nil + error:&err]; + _SG_OBJC_RELEASE(cp_desc); + if (nil == mtl_cps) { + SOKOL_ASSERT(err); + _SG_ERROR(METAL_CREATE_CPS_FAILED); + _SG_LOGMSG(METAL_CREATE_CPS_OUTPUT, [err.localizedDescription UTF8String]); + return SG_RESOURCESTATE_FAILED; + } + pip->mtl.cps = _sg_mtl_add_resource(mtl_cps); + _SG_OBJC_RELEASE(mtl_cps); + pip->mtl.threads_per_threadgroup = shd->mtl.threads_per_threadgroup; + } else { + sg_primitive_type prim_type = desc->primitive_type; + pip->mtl.prim_type = _sg_mtl_primitive_type(prim_type); + pip->mtl.index_size = _sg_mtl_index_size(pip->cmn.index_type); + if (SG_INDEXTYPE_NONE != pip->cmn.index_type) { + pip->mtl.index_type = _sg_mtl_index_type(pip->cmn.index_type); + } + pip->mtl.cull_mode = _sg_mtl_cull_mode(desc->cull_mode); + pip->mtl.winding = _sg_mtl_winding(desc->face_winding); + pip->mtl.stencil_ref = desc->stencil.ref; + + // create vertex-descriptor + MTLVertexDescriptor* vtx_desc = [MTLVertexDescriptor vertexDescriptor]; + for (NSUInteger attr_index = 0; attr_index < SG_MAX_VERTEX_ATTRIBUTES; attr_index++) { + const sg_vertex_attr_state* a_state = &desc->layout.attrs[attr_index]; + if (a_state->format == SG_VERTEXFORMAT_INVALID) { + break; + } + SOKOL_ASSERT(a_state->buffer_index < SG_MAX_VERTEXBUFFER_BINDSLOTS); + SOKOL_ASSERT(pip->cmn.vertex_buffer_layout_active[a_state->buffer_index]); + vtx_desc.attributes[attr_index].format = _sg_mtl_vertex_format(a_state->format); + vtx_desc.attributes[attr_index].offset = (NSUInteger)a_state->offset; + vtx_desc.attributes[attr_index].bufferIndex = _sg_mtl_vertexbuffer_bindslot((size_t)a_state->buffer_index); + } + for (NSUInteger layout_index = 0; layout_index < SG_MAX_VERTEXBUFFER_BINDSLOTS; layout_index++) { + if (pip->cmn.vertex_buffer_layout_active[layout_index]) { + const sg_vertex_buffer_layout_state* l_state = &desc->layout.buffers[layout_index]; + const NSUInteger mtl_vb_slot = _sg_mtl_vertexbuffer_bindslot(layout_index); + SOKOL_ASSERT(l_state->stride > 0); + vtx_desc.layouts[mtl_vb_slot].stride = (NSUInteger)l_state->stride; + vtx_desc.layouts[mtl_vb_slot].stepFunction = _sg_mtl_step_function(l_state->step_func); + vtx_desc.layouts[mtl_vb_slot].stepRate = (NSUInteger)l_state->step_rate; + } + } + + // render-pipeline descriptor + MTLRenderPipelineDescriptor* rp_desc = [[MTLRenderPipelineDescriptor alloc] init]; + rp_desc.vertexDescriptor = vtx_desc; + SOKOL_ASSERT(shd->mtl.vertex_func.mtl_func != _SG_MTL_INVALID_SLOT_INDEX); + rp_desc.vertexFunction = _sg_mtl_id(shd->mtl.vertex_func.mtl_func); + SOKOL_ASSERT(shd->mtl.fragment_func.mtl_func != _SG_MTL_INVALID_SLOT_INDEX); + rp_desc.fragmentFunction = _sg_mtl_id(shd->mtl.fragment_func.mtl_func); + rp_desc.rasterSampleCount = (NSUInteger)desc->sample_count; + rp_desc.alphaToCoverageEnabled = desc->alpha_to_coverage_enabled; + rp_desc.alphaToOneEnabled = NO; + rp_desc.rasterizationEnabled = YES; + rp_desc.depthAttachmentPixelFormat = _sg_mtl_pixel_format(desc->depth.pixel_format); + if (desc->depth.pixel_format == SG_PIXELFORMAT_DEPTH_STENCIL) { + rp_desc.stencilAttachmentPixelFormat = _sg_mtl_pixel_format(desc->depth.pixel_format); + } + for (NSUInteger i = 0; i < (NSUInteger)desc->color_count; i++) { + SOKOL_ASSERT(i < SG_MAX_COLOR_ATTACHMENTS); + const sg_color_target_state* cs = &desc->colors[i]; + rp_desc.colorAttachments[i].pixelFormat = _sg_mtl_pixel_format(cs->pixel_format); + rp_desc.colorAttachments[i].writeMask = _sg_mtl_color_write_mask(cs->write_mask); + rp_desc.colorAttachments[i].blendingEnabled = cs->blend.enabled; + rp_desc.colorAttachments[i].alphaBlendOperation = _sg_mtl_blend_op(cs->blend.op_alpha); + rp_desc.colorAttachments[i].rgbBlendOperation = _sg_mtl_blend_op(cs->blend.op_rgb); + rp_desc.colorAttachments[i].destinationAlphaBlendFactor = _sg_mtl_blend_factor(cs->blend.dst_factor_alpha); + rp_desc.colorAttachments[i].destinationRGBBlendFactor = _sg_mtl_blend_factor(cs->blend.dst_factor_rgb); + rp_desc.colorAttachments[i].sourceAlphaBlendFactor = _sg_mtl_blend_factor(cs->blend.src_factor_alpha); + rp_desc.colorAttachments[i].sourceRGBBlendFactor = _sg_mtl_blend_factor(cs->blend.src_factor_rgb); + } + // Set buffer mutability for all buffers (vertex buffers and storage buffers). + // For vertex buffer it is guaranteed that neither the GPU nor CPU will update their content + // as long as it is in flight (since dynamic buffers are double-buffered, and vertex-buffers + // are not updated by the GPU). + // For storage buffer the same double-buffering applies, and if they are applied + // to the vertex- or fragment-stage must be declared as readonly in the shader. + for (size_t i = 0; i < SG_MAX_VERTEXBUFFER_BINDSLOTS; i++) { + if (pip->cmn.vertex_buffer_layout_active[i]) { + const NSUInteger mtl_slot = _sg_mtl_vertexbuffer_bindslot(i); + rp_desc.vertexBuffers[mtl_slot].mutability = MTLMutabilityImmutable; + } + } + for (size_t i = 0; i < SG_MAX_VIEW_BINDSLOTS; i++) { + const _sg_shader_view_t* view = &shd->cmn.views[i]; + if (view->view_type != SG_VIEWTYPE_STORAGEBUFFER) { + continue; + } + const sg_shader_stage stage = view->stage; + SOKOL_ASSERT(view->stage != SG_SHADERSTAGE_COMPUTE); + SOKOL_ASSERT(view->sbuf_readonly); + const NSUInteger mtl_slot = shd->mtl.view_buffer_texture_n[i]; + SOKOL_ASSERT(mtl_slot < _SG_MTL_MAX_STAGE_BUFFER_BINDINGS); + if (stage == SG_SHADERSTAGE_VERTEX) { + rp_desc.vertexBuffers[mtl_slot].mutability = MTLMutabilityImmutable; + } else if (stage == SG_SHADERSTAGE_FRAGMENT) { + rp_desc.fragmentBuffers[mtl_slot].mutability = MTLMutabilityImmutable; + } + } + #if defined(SOKOL_DEBUG) + if (desc->label) { + rp_desc.label = [NSString stringWithFormat:@"%s", desc->label]; + } + #endif + NSError* err = NULL; + id mtl_rps = [_sg.mtl.device newRenderPipelineStateWithDescriptor:rp_desc error:&err]; + _SG_OBJC_RELEASE(rp_desc); + if (nil == mtl_rps) { + SOKOL_ASSERT(err); + _SG_ERROR(METAL_CREATE_RPS_FAILED); + _SG_LOGMSG(METAL_CREATE_RPS_OUTPUT, [err.localizedDescription UTF8String]); + return SG_RESOURCESTATE_FAILED; + } + pip->mtl.rps = _sg_mtl_add_resource(mtl_rps); + _SG_OBJC_RELEASE(mtl_rps); + + // depth-stencil-state + MTLDepthStencilDescriptor* ds_desc = [[MTLDepthStencilDescriptor alloc] init]; + ds_desc.depthCompareFunction = _sg_mtl_compare_func(desc->depth.compare); + ds_desc.depthWriteEnabled = desc->depth.write_enabled; + if (desc->stencil.enabled) { + const sg_stencil_face_state* sb = &desc->stencil.back; + ds_desc.backFaceStencil = [[MTLStencilDescriptor alloc] init]; + ds_desc.backFaceStencil.stencilFailureOperation = _sg_mtl_stencil_op(sb->fail_op); + ds_desc.backFaceStencil.depthFailureOperation = _sg_mtl_stencil_op(sb->depth_fail_op); + ds_desc.backFaceStencil.depthStencilPassOperation = _sg_mtl_stencil_op(sb->pass_op); + ds_desc.backFaceStencil.stencilCompareFunction = _sg_mtl_compare_func(sb->compare); + ds_desc.backFaceStencil.readMask = desc->stencil.read_mask; + ds_desc.backFaceStencil.writeMask = desc->stencil.write_mask; + const sg_stencil_face_state* sf = &desc->stencil.front; + ds_desc.frontFaceStencil = [[MTLStencilDescriptor alloc] init]; + ds_desc.frontFaceStencil.stencilFailureOperation = _sg_mtl_stencil_op(sf->fail_op); + ds_desc.frontFaceStencil.depthFailureOperation = _sg_mtl_stencil_op(sf->depth_fail_op); + ds_desc.frontFaceStencil.depthStencilPassOperation = _sg_mtl_stencil_op(sf->pass_op); + ds_desc.frontFaceStencil.stencilCompareFunction = _sg_mtl_compare_func(sf->compare); + ds_desc.frontFaceStencil.readMask = desc->stencil.read_mask; + ds_desc.frontFaceStencil.writeMask = desc->stencil.write_mask; + } + #if defined(SOKOL_DEBUG) + if (desc->label) { + ds_desc.label = [NSString stringWithFormat:@"%s.dss", desc->label]; + } + #endif + id mtl_dss = [_sg.mtl.device newDepthStencilStateWithDescriptor:ds_desc]; + _SG_OBJC_RELEASE(ds_desc); + if (nil == mtl_dss) { + _SG_ERROR(METAL_CREATE_DSS_FAILED); + return SG_RESOURCESTATE_FAILED; + } + pip->mtl.dss = _sg_mtl_add_resource(mtl_dss); + _SG_OBJC_RELEASE(mtl_dss); + } + return SG_RESOURCESTATE_VALID; +} + +_SOKOL_PRIVATE void _sg_mtl_discard_pipeline(_sg_pipeline_t* pip) { + SOKOL_ASSERT(pip); + // it's valid to call release resource with a 'null resource' + _sg_mtl_release_resource(_sg.frame_index, pip->mtl.cps); + _sg_mtl_release_resource(_sg.frame_index, pip->mtl.rps); + _sg_mtl_release_resource(_sg.frame_index, pip->mtl.dss); +} + +_SOKOL_PRIVATE sg_resource_state _sg_mtl_create_view(_sg_view_t* view, const sg_view_desc* desc) { + SOKOL_ASSERT(view && desc); + _SOKOL_UNUSED(desc); + if ((SG_VIEWTYPE_TEXTURE == view->cmn.type) || (SG_VIEWTYPE_STORAGEIMAGE == view->cmn.type)) { + const _sg_image_view_common_t* cmn = &view->cmn.img; + const _sg_image_t* img = _sg_image_ref_ptr(&cmn->ref); + SOKOL_ASSERT(cmn->mip_level_count >= 1); + SOKOL_ASSERT(cmn->slice_count >= 1); + for (int slot = 0; slot < img->cmn.num_slots; slot++) { + SOKOL_ASSERT(img->mtl.tex[slot] != _SG_MTL_INVALID_SLOT_INDEX); + id mtl_tex_view = [_sg_mtl_id(img->mtl.tex[slot]) + newTextureViewWithPixelFormat: _sg_mtl_pixel_format(img->cmn.pixel_format) + textureType: _sg_mtl_texture_type(img->cmn.type, img->cmn.sample_count > 1) + levels: NSMakeRange((NSUInteger)cmn->mip_level, (NSUInteger)cmn->mip_level_count) + slices: NSMakeRange((NSUInteger)cmn->slice, (NSUInteger)cmn->slice_count)]; + #if defined(SOKOL_DEBUG) + if (desc->label) { + mtl_tex_view.label = [NSString stringWithFormat:@"%s.%d", desc->label, slot]; + } + #endif + view->mtl.tex_view[slot] = _sg_mtl_add_resource(mtl_tex_view); + _SG_OBJC_RELEASE(mtl_tex_view); + } + } + return SG_RESOURCESTATE_VALID; +} + +_SOKOL_PRIVATE void _sg_mtl_discard_view(_sg_view_t* view) { + SOKOL_ASSERT(view); + for (size_t i = 0; i < SG_NUM_INFLIGHT_FRAMES; i++) { + // it's valid to call _sg_mtl_release_resource with a null handle + _sg_mtl_release_resource(_sg.frame_index, view->mtl.tex_view[i]); + } +} + +_SOKOL_PRIVATE void _sg_mtl_bind_uniform_buffers(void) { + // In the Metal backend, uniform buffer bindings happen once in sg_begin_pass() and + // remain valid for the entire pass. Only binding offsets will be updated + // in sg_apply_uniforms() + if (_sg.cur_pass.is_compute) { + SOKOL_ASSERT(nil != _sg.mtl.compute_cmd_encoder); + for (size_t slot = 0; slot < SG_MAX_UNIFORMBLOCK_BINDSLOTS; slot++) { + [_sg.mtl.compute_cmd_encoder + setBuffer:_sg.mtl.uniform_buffers[_sg.mtl.cur_frame_rotate_index] + offset:0 + atIndex:slot]; + } + } else { + SOKOL_ASSERT(nil != _sg.mtl.render_cmd_encoder); + for (size_t slot = 0; slot < SG_MAX_UNIFORMBLOCK_BINDSLOTS; slot++) { + [_sg.mtl.render_cmd_encoder + setVertexBuffer:_sg.mtl.uniform_buffers[_sg.mtl.cur_frame_rotate_index] + offset:0 + atIndex:slot]; + [_sg.mtl.render_cmd_encoder + setFragmentBuffer:_sg.mtl.uniform_buffers[_sg.mtl.cur_frame_rotate_index] + offset:0 + atIndex:slot]; + } + } +} + +_SOKOL_PRIVATE void _sg_mtl_begin_compute_pass(const sg_pass* pass) { + SOKOL_ASSERT(pass); (void)pass; + SOKOL_ASSERT(nil != _sg.mtl.cmd_buffer); + SOKOL_ASSERT(nil == _sg.mtl.compute_cmd_encoder); + SOKOL_ASSERT(nil == _sg.mtl.render_cmd_encoder); + + _sg.mtl.compute_cmd_encoder = [_sg.mtl.cmd_buffer computeCommandEncoder]; + if (nil == _sg.mtl.compute_cmd_encoder) { + _sg.cur_pass.valid = false; + return; + } + + #if defined(SOKOL_DEBUG) + if (pass->label) { + _sg.mtl.compute_cmd_encoder.label = [NSString stringWithUTF8String:pass->label]; + } + #endif +} + +_SOKOL_PRIVATE void _sg_mtl_begin_render_pass(const sg_pass* pass, const _sg_attachments_ptrs_t* atts) { + SOKOL_ASSERT(pass && atts); + SOKOL_ASSERT(nil != _sg.mtl.cmd_buffer); + SOKOL_ASSERT(nil == _sg.mtl.render_cmd_encoder); + SOKOL_ASSERT(nil == _sg.mtl.compute_cmd_encoder); + + const sg_swapchain* swapchain = &pass->swapchain; + const sg_pass_action* action = &pass->action; + + MTLRenderPassDescriptor* pass_desc = [MTLRenderPassDescriptor renderPassDescriptor]; + SOKOL_ASSERT(pass_desc); + if (!atts->empty) { + // setup pass descriptor for offscreen rendering + for (NSUInteger i = 0; i < (NSUInteger)atts->num_color_views; i++) { + const _sg_view_t* color_view = atts->color_views[i]; + SOKOL_ASSERT(color_view); + const _sg_view_t* resolve_view = atts->resolve_views[i]; + const _sg_image_t* color_img = _sg_image_ref_ptr(&color_view->cmn.img.ref); + SOKOL_ASSERT(color_img->slot.state == SG_RESOURCESTATE_VALID); + SOKOL_ASSERT(color_img->cmn.active_slot == 0); + SOKOL_ASSERT(color_img->mtl.tex[0] != _SG_MTL_INVALID_SLOT_INDEX); + pass_desc.colorAttachments[i].loadAction = _sg_mtl_load_action(action->colors[i].load_action); + pass_desc.colorAttachments[i].storeAction = _sg_mtl_store_action(action->colors[i].store_action, resolve_view != 0); + sg_color c = action->colors[i].clear_value; + pass_desc.colorAttachments[i].clearColor = MTLClearColorMake(c.r, c.g, c.b, c.a); + pass_desc.colorAttachments[i].texture = _sg_mtl_id(color_img->mtl.tex[0]); + pass_desc.colorAttachments[i].level = (NSUInteger)color_view->cmn.img.mip_level; + switch (color_img->cmn.type) { + case SG_IMAGETYPE_CUBE: + case SG_IMAGETYPE_ARRAY: + pass_desc.colorAttachments[i].slice = (NSUInteger)color_view->cmn.img.slice; + break; + case SG_IMAGETYPE_3D: + pass_desc.colorAttachments[i].depthPlane = (NSUInteger)color_view->cmn.img.slice; + break; + default: break; + } + if (resolve_view) { + const _sg_image_t* resolve_img = _sg_image_ref_ptr(&resolve_view->cmn.img.ref); + SOKOL_ASSERT(resolve_img->slot.state == SG_RESOURCESTATE_VALID); + SOKOL_ASSERT(resolve_img->cmn.active_slot == 0); + SOKOL_ASSERT(resolve_img->mtl.tex[0] != _SG_MTL_INVALID_SLOT_INDEX); + pass_desc.colorAttachments[i].resolveTexture = _sg_mtl_id(resolve_img->mtl.tex[0]); + pass_desc.colorAttachments[i].resolveLevel = (NSUInteger)resolve_view->cmn.img.mip_level; + switch (resolve_img->cmn.type) { + case SG_IMAGETYPE_CUBE: + case SG_IMAGETYPE_ARRAY: + pass_desc.colorAttachments[i].resolveSlice = (NSUInteger)resolve_view->cmn.img.slice; + break; + case SG_IMAGETYPE_3D: + pass_desc.colorAttachments[i].resolveDepthPlane = (NSUInteger)resolve_view->cmn.img.slice; + break; + default: break; + } + } + } + if (atts->ds_view) { + const _sg_view_t* ds_view = atts->ds_view; + const _sg_image_t* ds_img = _sg_image_ref_ptr(&ds_view->cmn.img.ref); + SOKOL_ASSERT(ds_img->slot.state == SG_RESOURCESTATE_VALID); + SOKOL_ASSERT(ds_img->cmn.active_slot == 0); + SOKOL_ASSERT(ds_img->mtl.tex[0] != _SG_MTL_INVALID_SLOT_INDEX); + pass_desc.depthAttachment.texture = _sg_mtl_id(ds_img->mtl.tex[0]); + pass_desc.depthAttachment.loadAction = _sg_mtl_load_action(action->depth.load_action); + pass_desc.depthAttachment.storeAction = _sg_mtl_store_action(action->depth.store_action, false); + pass_desc.depthAttachment.clearDepth = action->depth.clear_value; + switch (ds_img->cmn.type) { + case SG_IMAGETYPE_CUBE: + case SG_IMAGETYPE_ARRAY: + pass_desc.depthAttachment.slice = (NSUInteger)ds_view->cmn.img.slice; + break; + case SG_IMAGETYPE_3D: + pass_desc.depthAttachment.resolveDepthPlane = (NSUInteger)ds_view->cmn.img.slice; + break; + default: break; + } + if (_sg_is_depth_stencil_format(ds_img->cmn.pixel_format)) { + pass_desc.stencilAttachment.texture = _sg_mtl_id(ds_img->mtl.tex[0]); + pass_desc.stencilAttachment.loadAction = _sg_mtl_load_action(action->stencil.load_action); + pass_desc.stencilAttachment.storeAction = _sg_mtl_store_action(action->depth.store_action, false); + pass_desc.stencilAttachment.clearStencil = action->stencil.clear_value; + switch (ds_img->cmn.type) { + case SG_IMAGETYPE_CUBE: + case SG_IMAGETYPE_ARRAY: + pass_desc.stencilAttachment.slice = (NSUInteger)ds_view->cmn.img.slice; + break; + case SG_IMAGETYPE_3D: + pass_desc.stencilAttachment.resolveDepthPlane = (NSUInteger)ds_view->cmn.img.slice; + break; + default: break; + } + } + } + } else { + // setup pass descriptor for swapchain rendering + // + // NOTE: at least in macOS Sonoma this no longer seems to be the case, the + // current drawable is also valid in a minimized window + // === + // an MTKView current_drawable will not be valid if window is minimized, don't do any rendering in this case + if (0 == swapchain->metal.current_drawable) { + _sg.cur_pass.valid = false; + return; + } + // pin the swapchain resources into memory so that they outlive their command buffer + // (this is necessary because the command buffer doesn't retain references) + int pass_desc_ref = _sg_mtl_add_resource(pass_desc); + _sg_mtl_release_resource(_sg.frame_index, pass_desc_ref); + + _sg.mtl.cur_drawable = (__bridge id) swapchain->metal.current_drawable; + if (swapchain->sample_count > 1) { + // multi-sampling: render into msaa texture, resolve into drawable texture + id msaa_tex = (__bridge id) swapchain->metal.msaa_color_texture; + SOKOL_ASSERT(msaa_tex != nil); + pass_desc.colorAttachments[0].texture = msaa_tex; + pass_desc.colorAttachments[0].resolveTexture = _sg.mtl.cur_drawable.texture; + pass_desc.colorAttachments[0].storeAction = MTLStoreActionMultisampleResolve; + } else { + // non-msaa: render into current_drawable + pass_desc.colorAttachments[0].texture = _sg.mtl.cur_drawable.texture; + pass_desc.colorAttachments[0].storeAction = MTLStoreActionStore; + } + pass_desc.colorAttachments[0].loadAction = _sg_mtl_load_action(action->colors[0].load_action); + const sg_color c = action->colors[0].clear_value; + pass_desc.colorAttachments[0].clearColor = MTLClearColorMake(c.r, c.g, c.b, c.a); + + // optional depth-stencil texture + if (swapchain->metal.depth_stencil_texture) { + id ds_tex = (__bridge id) swapchain->metal.depth_stencil_texture; + SOKOL_ASSERT(ds_tex != nil); + pass_desc.depthAttachment.texture = ds_tex; + pass_desc.depthAttachment.storeAction = MTLStoreActionDontCare; + pass_desc.depthAttachment.loadAction = _sg_mtl_load_action(action->depth.load_action); + pass_desc.depthAttachment.clearDepth = action->depth.clear_value; + if (_sg_is_depth_stencil_format(swapchain->depth_format)) { + pass_desc.stencilAttachment.texture = ds_tex; + pass_desc.stencilAttachment.storeAction = MTLStoreActionDontCare; + pass_desc.stencilAttachment.loadAction = _sg_mtl_load_action(action->stencil.load_action); + pass_desc.stencilAttachment.clearStencil = action->stencil.clear_value; + } + } + } + + // NOTE: at least in macOS Sonoma, the following is no longer the case, a valid + // render command encoder is also returned in a minimized window + // === + // create a render command encoder, this might return nil if window is minimized + _sg.mtl.render_cmd_encoder = [_sg.mtl.cmd_buffer renderCommandEncoderWithDescriptor:pass_desc]; + if (nil == _sg.mtl.render_cmd_encoder) { + _sg.cur_pass.valid = false; + return; + } + + #if defined(SOKOL_DEBUG) + if (pass->label) { + _sg.mtl.render_cmd_encoder.label = [NSString stringWithUTF8String:pass->label]; + } + #endif +} + +_SOKOL_PRIVATE void _sg_mtl_begin_pass(const sg_pass* pass, const _sg_attachments_ptrs_t* atts) { + SOKOL_ASSERT(pass && atts); + SOKOL_ASSERT(_sg.mtl.cmd_queue); + SOKOL_ASSERT(nil == _sg.mtl.compute_cmd_encoder); + SOKOL_ASSERT(nil == _sg.mtl.render_cmd_encoder); + SOKOL_ASSERT(nil == _sg.mtl.cur_drawable); + _sg_mtl_clear_state_cache(); + + // if this is the first pass in the frame, create one command buffer and blit-cmd-encoder for the entire frame + if (nil == _sg.mtl.cmd_buffer) { + // block until the oldest frame in flight has finished + dispatch_semaphore_wait(_sg.mtl.sem, DISPATCH_TIME_FOREVER); + if (_sg.desc.mtl_use_command_buffer_with_retained_references) { + _sg.mtl.cmd_buffer = [_sg.mtl.cmd_queue commandBuffer]; + } else { + _sg.mtl.cmd_buffer = [_sg.mtl.cmd_queue commandBufferWithUnretainedReferences]; + } + [_sg.mtl.cmd_buffer enqueue]; + [_sg.mtl.cmd_buffer addCompletedHandler:^(id cmd_buf) { + // NOTE: this code is called on a different thread! + _SOKOL_UNUSED(cmd_buf); + dispatch_semaphore_signal(_sg.mtl.sem); + }]; + } + + // if this is first pass in frame, get uniform buffer base pointer + if (0 == _sg.mtl.cur_ub_base_ptr) { + _sg.mtl.cur_ub_base_ptr = (uint8_t*)[_sg.mtl.uniform_buffers[_sg.mtl.cur_frame_rotate_index] contents]; + } + + if (pass->compute) { + _sg_mtl_begin_compute_pass(pass); + } else { + _sg_mtl_begin_render_pass(pass, atts); + } + + // bind uniform buffers, those bindings remain valid for the entire pass + if (_sg.cur_pass.valid) { + _sg_mtl_bind_uniform_buffers(); + } +} + +_SOKOL_PRIVATE void _sg_mtl_end_pass(const _sg_attachments_ptrs_t* atts) { + _SOKOL_UNUSED(atts); + if (nil != _sg.mtl.render_cmd_encoder) { + [_sg.mtl.render_cmd_encoder endEncoding]; + // NOTE: MTLRenderCommandEncoder is autoreleased + _sg.mtl.render_cmd_encoder = nil; + } + if (nil != _sg.mtl.compute_cmd_encoder) { + [_sg.mtl.compute_cmd_encoder endEncoding]; + // NOTE: MTLComputeCommandEncoder is autoreleased + _sg.mtl.compute_cmd_encoder = nil; + } + // if this is a swapchain pass, present the drawable + if (nil != _sg.mtl.cur_drawable) { + [_sg.mtl.cmd_buffer presentDrawable:_sg.mtl.cur_drawable]; + _sg.mtl.cur_drawable = nil; + } +} + +_SOKOL_PRIVATE void _sg_mtl_commit(void) { + SOKOL_ASSERT(nil == _sg.mtl.render_cmd_encoder); + SOKOL_ASSERT(nil == _sg.mtl.compute_cmd_encoder); + SOKOL_ASSERT(nil != _sg.mtl.cmd_buffer); + + // commit the frame's command buffer + [_sg.mtl.cmd_buffer commit]; + + // garbage-collect resources pending for release + _sg_mtl_garbage_collect(_sg.frame_index); + + // rotate uniform buffer slot + if (++_sg.mtl.cur_frame_rotate_index >= SG_NUM_INFLIGHT_FRAMES) { + _sg.mtl.cur_frame_rotate_index = 0; + } + _sg.mtl.cur_ub_offset = 0; + _sg.mtl.cur_ub_base_ptr = 0; + // NOTE: MTLCommandBuffer is autoreleased + _sg.mtl.cmd_buffer = nil; +} + +_SOKOL_PRIVATE void _sg_mtl_apply_viewport(int x, int y, int w, int h, bool origin_top_left) { + SOKOL_ASSERT(nil != _sg.mtl.render_cmd_encoder); + SOKOL_ASSERT(_sg.cur_pass.dim.height > 0); + MTLViewport vp; + vp.originX = (double) x; + vp.originY = (double) (origin_top_left ? y : (_sg.cur_pass.dim.height - (y + h))); + vp.width = (double) w; + vp.height = (double) h; + vp.znear = 0.0; + vp.zfar = 1.0; + [_sg.mtl.render_cmd_encoder setViewport:vp]; +} + +_SOKOL_PRIVATE void _sg_mtl_apply_scissor_rect(int x, int y, int w, int h, bool origin_top_left) { + SOKOL_ASSERT(nil != _sg.mtl.render_cmd_encoder); + SOKOL_ASSERT(_sg.cur_pass.dim.width > 0); + SOKOL_ASSERT(_sg.cur_pass.dim.height > 0); + // clip against framebuffer rect + const _sg_recti_t clip = _sg_clipi(x, y, w, h, _sg.cur_pass.dim.width, _sg.cur_pass.dim.height); + MTLScissorRect r; + r.x = (NSUInteger)clip.x; + r.y = (NSUInteger) (origin_top_left ? clip.y : (_sg.cur_pass.dim.height - (clip.y + clip.h))); + r.width = (NSUInteger)clip.w; + r.height = (NSUInteger)clip.h; + [_sg.mtl.render_cmd_encoder setScissorRect:r]; +} + +_SOKOL_PRIVATE void _sg_mtl_apply_pipeline(_sg_pipeline_t* pip) { + SOKOL_ASSERT(pip); + if (!_sg_sref_slot_eql(&_sg.mtl.cache.cur_pip, &pip->slot)) { + _sg.mtl.cache.cur_pip = _sg_sref(&pip->slot); + if (pip->cmn.is_compute) { + SOKOL_ASSERT(_sg.cur_pass.is_compute); + SOKOL_ASSERT(nil != _sg.mtl.compute_cmd_encoder); + SOKOL_ASSERT(pip->mtl.cps != _SG_MTL_INVALID_SLOT_INDEX); + [_sg.mtl.compute_cmd_encoder setComputePipelineState:_sg_mtl_id(pip->mtl.cps)]; + } else { + SOKOL_ASSERT(!_sg.cur_pass.is_compute); + SOKOL_ASSERT(nil != _sg.mtl.render_cmd_encoder); + sg_color c = pip->cmn.blend_color; + [_sg.mtl.render_cmd_encoder setBlendColorRed:c.r green:c.g blue:c.b alpha:c.a]; + _sg_stats_add(metal.pipeline.num_set_blend_color, 1); + [_sg.mtl.render_cmd_encoder setCullMode:pip->mtl.cull_mode]; + _sg_stats_add(metal.pipeline.num_set_cull_mode, 1); + [_sg.mtl.render_cmd_encoder setFrontFacingWinding:pip->mtl.winding]; + _sg_stats_add(metal.pipeline.num_set_front_facing_winding, 1); + [_sg.mtl.render_cmd_encoder setStencilReferenceValue:pip->mtl.stencil_ref]; + _sg_stats_add(metal.pipeline.num_set_stencil_reference_value, 1); + [_sg.mtl.render_cmd_encoder setDepthBias:pip->cmn.depth.bias slopeScale:pip->cmn.depth.bias_slope_scale clamp:pip->cmn.depth.bias_clamp]; + _sg_stats_add(metal.pipeline.num_set_depth_bias, 1); + SOKOL_ASSERT(pip->mtl.rps != _SG_MTL_INVALID_SLOT_INDEX); + [_sg.mtl.render_cmd_encoder setRenderPipelineState:_sg_mtl_id(pip->mtl.rps)]; + _sg_stats_add(metal.pipeline.num_set_render_pipeline_state, 1); + SOKOL_ASSERT(pip->mtl.dss != _SG_MTL_INVALID_SLOT_INDEX); + [_sg.mtl.render_cmd_encoder setDepthStencilState:_sg_mtl_id(pip->mtl.dss)]; + _sg_stats_add(metal.pipeline.num_set_depth_stencil_state, 1); + } + } +} + +_SOKOL_PRIVATE int _sg_mtl_cache_buf_cmp(const _sg_mtl_cache_buf_t* item, const _sg_slot_t* slot, int active_slot, int offset) { + int res = _SG_MTL_CACHE_CMP_EQUAL; + if (!_sg_sref_slot_eql(&item->sref, slot)) { + res |= _SG_MTL_CACHE_CMP_SREF; + } + if (item->active_slot != active_slot) { + res |= _SG_MTL_CACHE_CMP_ACTIVESLOT; + } + if (item->offset != offset) { + res |= _SG_MTL_CACHE_CMP_OFFSET; + } + return res; +} + +_SOKOL_PRIVATE void _sg_mtl_cache_buf_upd(_sg_mtl_cache_buf_t* item, const _sg_slot_t* slot, int active_slot, int offset) { + item->sref = _sg_sref(slot); + item->offset = offset; + item->active_slot = active_slot; +} + +_SOKOL_PRIVATE int _sg_mtl_cache_tex_cmp(const _sg_mtl_cache_tex_t* item, const _sg_slot_t* slot, int active_slot) { + int res = _SG_MTL_CACHE_CMP_EQUAL; + if (!_sg_sref_slot_eql(&item->sref, slot)) { + res |= _SG_MTL_CACHE_CMP_SREF; + } + if (item->active_slot != active_slot) { + res |= _SG_MTL_CACHE_CMP_ACTIVESLOT; + } + return res; +} + +_SOKOL_PRIVATE void _sg_mtl_cache_tex_upd(_sg_mtl_cache_tex_t* item, const _sg_slot_t* slot, int active_slot) { + item->sref = _sg_sref(slot); + item->active_slot = active_slot; +} + + +_SOKOL_PRIVATE bool _sg_mtl_apply_bindings(_sg_bindings_ptrs_t* bnd) { + SOKOL_ASSERT(bnd); + SOKOL_ASSERT(bnd->pip); + const _sg_shader_t* shd = _sg_shader_ref_ptr(&bnd->pip->cmn.shader); + + // don't set vertex- and index-buffers in compute passes + if (!_sg.cur_pass.is_compute) { + SOKOL_ASSERT(nil != _sg.mtl.render_cmd_encoder); + // store index buffer binding, this will be needed later in sg_draw() + _sg.mtl.cache.cur_ibuf = _sg_buffer_ref(bnd->ib); + _sg.mtl.cache.cur_ibuf_offset = bnd->ib_offset; + if (bnd->ib) { + SOKOL_ASSERT(bnd->pip->cmn.index_type != SG_INDEXTYPE_NONE); + } else { + SOKOL_ASSERT(bnd->pip->cmn.index_type == SG_INDEXTYPE_NONE); + } + // apply vertex buffers + for (size_t i = 0; i < SG_MAX_VERTEXBUFFER_BINDSLOTS; i++) { + const _sg_buffer_t* vb = bnd->vbs[i]; + if (vb == 0) { + continue; + } + const NSUInteger mtl_slot = _sg_mtl_vertexbuffer_bindslot(i); + SOKOL_ASSERT(mtl_slot < _SG_MTL_MAX_STAGE_BUFFER_BINDINGS); + const int active_slot = vb->cmn.active_slot; + SOKOL_ASSERT(vb->mtl.buf[active_slot] != _SG_MTL_INVALID_SLOT_INDEX); + const int offset = bnd->vb_offsets[i]; + _sg_mtl_cache_buf_t* cache_item = &_sg.mtl.cache.cur_vsbufs[i]; + const int cmp = _sg_mtl_cache_buf_cmp(cache_item, &vb->slot, active_slot, offset); + if (cmp != _SG_MTL_CACHE_CMP_EQUAL) { + _sg_mtl_cache_buf_upd(cache_item, &vb->slot, active_slot, offset); + if (0 == (cmp & ~_SG_MTL_CACHE_CMP_OFFSET)) { + // only vertex buffer offset has changed + [_sg.mtl.render_cmd_encoder setVertexBufferOffset:(NSUInteger)offset atIndex:mtl_slot]; + _sg_stats_add(metal.bindings.num_set_vertex_buffer_offset, 1); + } else { + [_sg.mtl.render_cmd_encoder setVertexBuffer:_sg_mtl_id(vb->mtl.buf[active_slot]) offset:(NSUInteger)offset atIndex:mtl_slot]; + _sg_stats_add(metal.bindings.num_set_vertex_buffer, 1); + } + } else { + _sg_stats_add(metal.bindings.num_skip_redundant_vertex_buffer, 1); + } + } + } + + // apply view bindings (textures, storage images, storage buffers) + for (size_t i = 0; i < SG_MAX_VIEW_BINDSLOTS; i++) { + const _sg_view_t* view = bnd->views[i]; + if (0 == view) { + continue; + } + const _sg_shader_view_t* shd_view = &shd->cmn.views[i]; + const sg_shader_stage stage = shd_view->stage; + SOKOL_ASSERT((stage == SG_SHADERSTAGE_VERTEX) + || (stage == SG_SHADERSTAGE_FRAGMENT) + || (stage == SG_SHADERSTAGE_COMPUTE)); + SOKOL_ASSERT((shd_view->view_type == SG_VIEWTYPE_TEXTURE) + || (shd_view->view_type == SG_VIEWTYPE_STORAGEBUFFER) + || (shd_view->view_type == SG_VIEWTYPE_STORAGEIMAGE)); + const NSUInteger mtl_slot = shd->mtl.view_buffer_texture_n[i]; + + // same handling for textures and storage images + if ((shd_view->view_type == SG_VIEWTYPE_TEXTURE) || (shd_view->view_type == SG_VIEWTYPE_STORAGEIMAGE)) { + SOKOL_ASSERT(mtl_slot < _SG_MTL_MAX_STAGE_TEXTURE_BINDINGS); + const int active_slot = _sg_image_ref_ptr(&view->cmn.img.ref)->cmn.active_slot; + SOKOL_ASSERT(view->mtl.tex_view[active_slot] != _SG_MTL_INVALID_SLOT_INDEX); + if (stage == SG_SHADERSTAGE_VERTEX) { + SOKOL_ASSERT(nil != _sg.mtl.render_cmd_encoder); + _sg_mtl_cache_tex_t* cache_item = &_sg.mtl.cache.cur_vstexs[mtl_slot]; + const int cmp = _sg_mtl_cache_tex_cmp(cache_item, &view->slot, active_slot); + if (cmp != _SG_MTL_CACHE_CMP_EQUAL) { + _sg_mtl_cache_tex_upd(cache_item, &view->slot, active_slot); + [_sg.mtl.render_cmd_encoder setVertexTexture:_sg_mtl_id(view->mtl.tex_view[active_slot]) atIndex:mtl_slot]; + _sg_stats_add(metal.bindings.num_set_vertex_texture, 1); + } else { + _sg_stats_add(metal.bindings.num_skip_redundant_vertex_texture, 1); + } + } else if (stage == SG_SHADERSTAGE_FRAGMENT) { + SOKOL_ASSERT(nil != _sg.mtl.render_cmd_encoder); + _sg_mtl_cache_tex_t* cache_item = &_sg.mtl.cache.cur_fstexs[mtl_slot]; + const int cmp = _sg_mtl_cache_tex_cmp(cache_item, &view->slot, active_slot); + if (cmp != _SG_MTL_CACHE_CMP_EQUAL) { + _sg_mtl_cache_tex_upd(cache_item, &view->slot, active_slot); + [_sg.mtl.render_cmd_encoder setFragmentTexture:_sg_mtl_id(view->mtl.tex_view[active_slot]) atIndex:mtl_slot]; + _sg_stats_add(metal.bindings.num_set_fragment_texture, 1); + } else { + _sg_stats_add(metal.bindings.num_skip_redundant_fragment_texture, 1); + } + } else if (stage == SG_SHADERSTAGE_COMPUTE) { + SOKOL_ASSERT(nil != _sg.mtl.compute_cmd_encoder); + _sg_mtl_cache_tex_t* cache_item = &_sg.mtl.cache.cur_cstexs[mtl_slot]; + const int cmp = _sg_mtl_cache_tex_cmp(cache_item, &view->slot, active_slot); + if (cmp != _SG_MTL_CACHE_CMP_EQUAL) { + _sg_mtl_cache_tex_upd(cache_item, &view->slot, active_slot); + [_sg.mtl.compute_cmd_encoder setTexture:_sg_mtl_id(view->mtl.tex_view[active_slot]) atIndex:mtl_slot]; + _sg_stats_add(metal.bindings.num_set_compute_texture, 1); + } else { + _sg_stats_add(metal.bindings.num_skip_redundant_compute_texture, 1); + } + } else SOKOL_UNREACHABLE; + } else if (shd_view->view_type == SG_VIEWTYPE_STORAGEBUFFER) { + SOKOL_ASSERT(mtl_slot < _SG_MTL_MAX_STAGE_UB_SBUF_BINDINGS); + const _sg_buffer_t* sbuf = _sg_buffer_ref_ptr(&view->cmn.buf.ref); + const int active_slot = sbuf->cmn.active_slot; + SOKOL_ASSERT(sbuf->mtl.buf[sbuf->cmn.active_slot] != _SG_MTL_INVALID_SLOT_INDEX); + const int offset = view->cmn.buf.offset; + if (stage == SG_SHADERSTAGE_VERTEX) { + SOKOL_ASSERT(nil != _sg.mtl.render_cmd_encoder); + _sg_mtl_cache_buf_t* cache_item = &_sg.mtl.cache.cur_vsbufs[mtl_slot]; + const int cmp = _sg_mtl_cache_buf_cmp(cache_item, &sbuf->slot, active_slot, offset); + if (cmp != _SG_MTL_CACHE_CMP_EQUAL) { + _sg_mtl_cache_buf_upd(cache_item, &sbuf->slot, active_slot, offset); + if (0 == (cmp & ~_SG_MTL_CACHE_CMP_OFFSET)) { + // only offset has changed + [_sg.mtl.render_cmd_encoder setVertexBufferOffset:(NSUInteger)offset atIndex:mtl_slot]; + _sg_stats_add(metal.bindings.num_set_vertex_buffer_offset, 1); + } else { + [_sg.mtl.render_cmd_encoder setVertexBuffer:_sg_mtl_id(sbuf->mtl.buf[sbuf->cmn.active_slot]) offset:(NSUInteger)offset atIndex:mtl_slot]; + _sg_stats_add(metal.bindings.num_set_vertex_buffer, 1); + } + } else { + _sg_stats_add(metal.bindings.num_skip_redundant_vertex_buffer, 1); + } + } else if (stage == SG_SHADERSTAGE_FRAGMENT) { + SOKOL_ASSERT(nil != _sg.mtl.render_cmd_encoder); + _sg_mtl_cache_buf_t* cache_item = &_sg.mtl.cache.cur_fsbufs[mtl_slot]; + const int cmp = _sg_mtl_cache_buf_cmp(cache_item, &sbuf->slot, active_slot, offset); + if (cmp != _SG_MTL_CACHE_CMP_EQUAL) { + _sg_mtl_cache_buf_upd(cache_item, &sbuf->slot, active_slot, offset); + if (0 == (cmp & ~_SG_MTL_CACHE_CMP_OFFSET)) { + // only offset has changed + [_sg.mtl.render_cmd_encoder setFragmentBufferOffset:(NSUInteger)offset atIndex:mtl_slot]; + _sg_stats_add(metal.bindings.num_set_fragment_buffer_offset, 1); + } else { + [_sg.mtl.render_cmd_encoder setFragmentBuffer:_sg_mtl_id(sbuf->mtl.buf[active_slot]) offset:(NSUInteger)offset atIndex:mtl_slot]; + _sg_stats_add(metal.bindings.num_set_fragment_buffer, 1); + } + } else { + _sg_stats_add(metal.bindings.num_skip_redundant_fragment_buffer, 1); + } + } else if (stage == SG_SHADERSTAGE_COMPUTE) { + SOKOL_ASSERT(nil != _sg.mtl.compute_cmd_encoder); + _sg_mtl_cache_buf_t* cache_item = &_sg.mtl.cache.cur_csbufs[mtl_slot]; + const int cmp = _sg_mtl_cache_buf_cmp(cache_item, &sbuf->slot, active_slot, offset); + if (cmp != _SG_MTL_CACHE_CMP_EQUAL) { + _sg_mtl_cache_buf_upd(cache_item, &sbuf->slot, active_slot, offset); + if (0 == (cmp & ~_SG_MTL_CACHE_CMP_OFFSET)) { + // only offset has changed + [_sg.mtl.compute_cmd_encoder setBufferOffset:(NSUInteger)offset atIndex:mtl_slot]; + _sg_stats_add(metal.bindings.num_set_compute_buffer_offset, 1); + } else { + [_sg.mtl.compute_cmd_encoder setBuffer:_sg_mtl_id(sbuf->mtl.buf[active_slot]) offset:(NSUInteger)offset atIndex:mtl_slot]; + _sg_stats_add(metal.bindings.num_set_compute_buffer, 1); + } + } else { + _sg_stats_add(metal.bindings.num_skip_redundant_compute_buffer, 1); + } + } + } else SOKOL_UNREACHABLE; + } + + // apply sampler bindings + for (size_t i = 0; i < SG_MAX_SAMPLER_BINDSLOTS; i++) { + const _sg_sampler_t* smp = bnd->smps[i]; + if (smp == 0) { + continue; + } + SOKOL_ASSERT(smp->mtl.sampler_state != _SG_MTL_INVALID_SLOT_INDEX); + const sg_shader_stage stage = shd->cmn.samplers[i].stage; + SOKOL_ASSERT((stage == SG_SHADERSTAGE_VERTEX) || (stage == SG_SHADERSTAGE_FRAGMENT) || (stage == SG_SHADERSTAGE_COMPUTE)); + const NSUInteger mtl_slot = shd->mtl.smp_sampler_n[i]; + SOKOL_ASSERT(mtl_slot < _SG_MTL_MAX_STAGE_SAMPLER_BINDINGS); + if (stage == SG_SHADERSTAGE_VERTEX) { + SOKOL_ASSERT(nil != _sg.mtl.render_cmd_encoder); + if (!_sg_sref_slot_eql(&_sg.mtl.cache.cur_vssmps[mtl_slot], &smp->slot)) { + _sg.mtl.cache.cur_vssmps[mtl_slot] = _sg_sref(&smp->slot); + [_sg.mtl.render_cmd_encoder setVertexSamplerState:_sg_mtl_id(smp->mtl.sampler_state) atIndex:mtl_slot]; + _sg_stats_add(metal.bindings.num_set_vertex_sampler_state, 1); + } else { + _sg_stats_add(metal.bindings.num_skip_redundant_vertex_sampler_state, 1); + } + } else if (stage == SG_SHADERSTAGE_FRAGMENT) { + SOKOL_ASSERT(nil != _sg.mtl.render_cmd_encoder); + if (!_sg_sref_slot_eql(&_sg.mtl.cache.cur_fssmps[mtl_slot], &smp->slot)) { + _sg.mtl.cache.cur_fssmps[mtl_slot] = _sg_sref(&smp->slot); + [_sg.mtl.render_cmd_encoder setFragmentSamplerState:_sg_mtl_id(smp->mtl.sampler_state) atIndex:mtl_slot]; + _sg_stats_add(metal.bindings.num_set_fragment_sampler_state, 1); + } else { + _sg_stats_add(metal.bindings.num_skip_redundant_fragment_sampler_state, 1); + } + } else if (stage == SG_SHADERSTAGE_COMPUTE) { + SOKOL_ASSERT(nil != _sg.mtl.compute_cmd_encoder); + if (!_sg_sref_slot_eql(&_sg.mtl.cache.cur_cssmps[mtl_slot], &smp->slot)) { + _sg.mtl.cache.cur_cssmps[mtl_slot] = _sg_sref(&smp->slot); + [_sg.mtl.compute_cmd_encoder setSamplerState:_sg_mtl_id(smp->mtl.sampler_state) atIndex:mtl_slot]; + _sg_stats_add(metal.bindings.num_set_compute_sampler_state, 1); + } else { + _sg_stats_add(metal.bindings.num_skip_redundant_compute_sampler_state, 1); + } + } else SOKOL_UNREACHABLE; + } + return true; +} + +_SOKOL_PRIVATE void _sg_mtl_apply_uniforms(int ub_slot, const sg_range* data) { + SOKOL_ASSERT((ub_slot >= 0) && (ub_slot < SG_MAX_UNIFORMBLOCK_BINDSLOTS)); + SOKOL_ASSERT(((size_t)_sg.mtl.cur_ub_offset + data->size) <= (size_t)_sg.mtl.ub_size); + SOKOL_ASSERT((_sg.mtl.cur_ub_offset & (_SG_MTL_UB_ALIGN-1)) == 0); + const _sg_pipeline_t* pip = _sg_pipeline_ref_ptr(&_sg.cur_pip); + SOKOL_ASSERT(pip); + const _sg_shader_t* shd = _sg_shader_ref_ptr(&pip->cmn.shader); + SOKOL_ASSERT(data->size == shd->cmn.uniform_blocks[ub_slot].size); + + const sg_shader_stage stage = shd->cmn.uniform_blocks[ub_slot].stage; + const NSUInteger mtl_slot = shd->mtl.ub_buffer_n[ub_slot]; + + // copy to global uniform buffer, record offset into cmd encoder, and advance offset + uint8_t* dst = &_sg.mtl.cur_ub_base_ptr[_sg.mtl.cur_ub_offset]; + memcpy(dst, data->ptr, data->size); + if (stage == SG_SHADERSTAGE_VERTEX) { + SOKOL_ASSERT(nil != _sg.mtl.render_cmd_encoder); + [_sg.mtl.render_cmd_encoder setVertexBufferOffset:(NSUInteger)_sg.mtl.cur_ub_offset atIndex:mtl_slot]; + _sg_stats_add(metal.uniforms.num_set_vertex_buffer_offset, 1); + } else if (stage == SG_SHADERSTAGE_FRAGMENT) { + SOKOL_ASSERT(nil != _sg.mtl.render_cmd_encoder); + [_sg.mtl.render_cmd_encoder setFragmentBufferOffset:(NSUInteger)_sg.mtl.cur_ub_offset atIndex:mtl_slot]; + _sg_stats_add(metal.uniforms.num_set_fragment_buffer_offset, 1); + } else if (stage == SG_SHADERSTAGE_COMPUTE) { + SOKOL_ASSERT(nil != _sg.mtl.compute_cmd_encoder); + [_sg.mtl.compute_cmd_encoder setBufferOffset:(NSUInteger)_sg.mtl.cur_ub_offset atIndex:mtl_slot]; + _sg_stats_add(metal.uniforms.num_set_compute_buffer_offset, 1); + } else { + SOKOL_UNREACHABLE; + } + _sg.mtl.cur_ub_offset = _sg_roundup(_sg.mtl.cur_ub_offset + (int)data->size, _SG_MTL_UB_ALIGN); +} + +_SOKOL_PRIVATE void _sg_mtl_draw(int base_element, int num_elements, int num_instances, int base_vertex, int base_instance) { + SOKOL_ASSERT(nil != _sg.mtl.render_cmd_encoder); + const _sg_pipeline_t* pip = _sg_pipeline_ref_ptr(&_sg.cur_pip); + SOKOL_ASSERT(pip); + if (_sg.use_indexed_draw) { + // indexed rendering + const _sg_buffer_t* ib = _sg_buffer_ref_ptr(&_sg.mtl.cache.cur_ibuf); + SOKOL_ASSERT(ib && (ib->mtl.buf[ib->cmn.active_slot] != _SG_MTL_INVALID_SLOT_INDEX)); + const NSUInteger index_buffer_offset = (NSUInteger) (_sg.mtl.cache.cur_ibuf_offset + base_element * pip->mtl.index_size); + [_sg.mtl.render_cmd_encoder drawIndexedPrimitives:pip->mtl.prim_type + indexCount:(NSUInteger)num_elements + indexType:pip->mtl.index_type + indexBuffer:_sg_mtl_id(ib->mtl.buf[ib->cmn.active_slot]) + indexBufferOffset:index_buffer_offset + instanceCount:(NSUInteger)num_instances + baseVertex:base_vertex + baseInstance:(NSUInteger)base_instance]; + } else { + // non-indexed rendering + [_sg.mtl.render_cmd_encoder drawPrimitives:pip->mtl.prim_type + vertexStart:(NSUInteger)base_element + vertexCount:(NSUInteger)num_elements + instanceCount:(NSUInteger)num_instances + baseInstance:(NSUInteger)base_instance]; + } +} + +_SOKOL_PRIVATE void _sg_mtl_dispatch(int num_groups_x, int num_groups_y, int num_groups_z) { + SOKOL_ASSERT(nil != _sg.mtl.compute_cmd_encoder); + const _sg_pipeline_t* pip = _sg_pipeline_ref_ptr(&_sg.cur_pip); + SOKOL_ASSERT(pip); + const MTLSize thread_groups = MTLSizeMake( + (NSUInteger)num_groups_x, + (NSUInteger)num_groups_y, + (NSUInteger)num_groups_z); + const MTLSize threads_per_threadgroup = pip->mtl.threads_per_threadgroup; + [_sg.mtl.compute_cmd_encoder dispatchThreadgroups:thread_groups threadsPerThreadgroup:threads_per_threadgroup]; +} + +_SOKOL_PRIVATE void _sg_mtl_update_buffer(_sg_buffer_t* buf, const sg_range* data) { + SOKOL_ASSERT(buf && data && data->ptr && (data->size > 0)); + if (++buf->cmn.active_slot >= buf->cmn.num_slots) { + buf->cmn.active_slot = 0; + } + __unsafe_unretained id mtl_buf = _sg_mtl_id(buf->mtl.buf[buf->cmn.active_slot]); + void* dst_ptr = [mtl_buf contents]; + memcpy(dst_ptr, data->ptr, data->size); + #if defined(_SG_TARGET_MACOS) + if (_sg_mtl_resource_options_storage_mode_managed_or_shared() == MTLResourceStorageModeManaged) { + [mtl_buf didModifyRange:NSMakeRange(0, data->size)]; + } + #endif +} + +_SOKOL_PRIVATE void _sg_mtl_append_buffer(_sg_buffer_t* buf, const sg_range* data, bool new_frame) { + SOKOL_ASSERT(buf && data && data->ptr && (data->size > 0)); + if (new_frame) { + if (++buf->cmn.active_slot >= buf->cmn.num_slots) { + buf->cmn.active_slot = 0; + } + } + __unsafe_unretained id mtl_buf = _sg_mtl_id(buf->mtl.buf[buf->cmn.active_slot]); + uint8_t* dst_ptr = (uint8_t*) [mtl_buf contents]; + dst_ptr += buf->cmn.append_pos; + memcpy(dst_ptr, data->ptr, data->size); + #if defined(_SG_TARGET_MACOS) + if (_sg_mtl_resource_options_storage_mode_managed_or_shared() == MTLResourceStorageModeManaged) { + [mtl_buf didModifyRange:NSMakeRange((NSUInteger)buf->cmn.append_pos, (NSUInteger)data->size)]; + } + #endif +} + +_SOKOL_PRIVATE void _sg_mtl_update_image(_sg_image_t* img, const sg_image_data* data) { + SOKOL_ASSERT(img && data); + if (++img->cmn.active_slot >= img->cmn.num_slots) { + img->cmn.active_slot = 0; + } + __unsafe_unretained id mtl_tex = _sg_mtl_id(img->mtl.tex[img->cmn.active_slot]); + _sg_mtl_copy_image_data(img, mtl_tex, data); +} + +_SOKOL_PRIVATE void _sg_mtl_push_debug_group(const char* name) { + SOKOL_ASSERT(name); + if (_sg.mtl.render_cmd_encoder) { + [_sg.mtl.render_cmd_encoder pushDebugGroup:[NSString stringWithUTF8String:name]]; + } else if (_sg.mtl.compute_cmd_encoder) { + [_sg.mtl.compute_cmd_encoder pushDebugGroup:[NSString stringWithUTF8String:name]]; + } +} + +_SOKOL_PRIVATE void _sg_mtl_pop_debug_group(void) { + if (_sg.mtl.render_cmd_encoder) { + [_sg.mtl.render_cmd_encoder popDebugGroup]; + } else if (_sg.mtl.compute_cmd_encoder) { + [_sg.mtl.compute_cmd_encoder popDebugGroup]; + } +} + +// ██ ██ ███████ ██████ ██████ ██████ ██ ██ ██████ █████ ██████ ██ ██ ███████ ███ ██ ██████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██ ██ ██ +// ██ █ ██ █████ ██████ ██ ███ ██████ ██ ██ ██████ ███████ ██ █████ █████ ██ ██ ██ ██ ██ +// ██ ███ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ███ ███ ███████ ██████ ██████ ██ ██████ ██████ ██ ██ ██████ ██ ██ ███████ ██ ████ ██████ +// +// >>webgpu +// >>wgpu +#elif defined(SOKOL_WGPU) + +_SOKOL_PRIVATE WGPUStringView _sg_wgpu_stringview(const char* str) { + WGPUStringView res; + if (str) { + res.data = str; + res.length = strlen(str); + } else { + res.data = 0; + res.length = 0; + } + return res; +} + +_SOKOL_PRIVATE WGPUOptionalBool _sg_wgpu_optional_bool(bool b) { + return b ? WGPUOptionalBool_True : WGPUOptionalBool_False; +} + +_SOKOL_PRIVATE WGPUBufferUsage _sg_wgpu_buffer_usage(const sg_buffer_usage* usg) { + int res = 0; + if (usg->vertex_buffer) { + res |= (int)WGPUBufferUsage_Vertex; + } + if (usg->index_buffer) { + res |= (int)WGPUBufferUsage_Index; + } + if (usg->storage_buffer) { + res |= (int)WGPUBufferUsage_Storage; + } + if (!usg->immutable) { + res |= (int)WGPUBufferUsage_CopyDst; + } + return (WGPUBufferUsage)res; +} + +_SOKOL_PRIVATE WGPULoadOp _sg_wgpu_load_op(WGPUTextureView view, sg_load_action a) { + if (0 == view) { + return WGPULoadOp_Undefined; + } else switch (a) { + case SG_LOADACTION_CLEAR: + case SG_LOADACTION_DONTCARE: + return WGPULoadOp_Clear; + case SG_LOADACTION_LOAD: + return WGPULoadOp_Load; + default: + SOKOL_UNREACHABLE; + return WGPULoadOp_Force32; + } +} + +_SOKOL_PRIVATE WGPUStoreOp _sg_wgpu_store_op(WGPUTextureView view, sg_store_action a) { + if (0 == view) { + return WGPUStoreOp_Undefined; + } else switch (a) { + case SG_STOREACTION_STORE: + return WGPUStoreOp_Store; + case SG_STOREACTION_DONTCARE: + return WGPUStoreOp_Discard; + default: + SOKOL_UNREACHABLE; + return WGPUStoreOp_Force32; + } +} + +_SOKOL_PRIVATE WGPUTextureViewDimension _sg_wgpu_texture_view_dimension(sg_image_type t) { + switch (t) { + case SG_IMAGETYPE_2D: return WGPUTextureViewDimension_2D; + case SG_IMAGETYPE_CUBE: return WGPUTextureViewDimension_Cube; + case SG_IMAGETYPE_3D: return WGPUTextureViewDimension_3D; + case SG_IMAGETYPE_ARRAY: return WGPUTextureViewDimension_2DArray; + default: SOKOL_UNREACHABLE; return WGPUTextureViewDimension_Force32; + } +} + +_SOKOL_PRIVATE WGPUTextureViewDimension _sg_wgpu_attachment_view_dimension(sg_image_type t) { + switch (t) { + case SG_IMAGETYPE_2D: return WGPUTextureViewDimension_2D; + case SG_IMAGETYPE_CUBE: return WGPUTextureViewDimension_2DArray; // not a bug + case SG_IMAGETYPE_3D: return WGPUTextureViewDimension_2D; + case SG_IMAGETYPE_ARRAY: return WGPUTextureViewDimension_2DArray; + default: SOKOL_UNREACHABLE; return WGPUTextureViewDimension_Force32; + } +} + +_SOKOL_PRIVATE WGPUTextureDimension _sg_wgpu_texture_dimension(sg_image_type t) { + if (SG_IMAGETYPE_3D == t) { + return WGPUTextureDimension_3D; + } else { + return WGPUTextureDimension_2D; + } +} + +_SOKOL_PRIVATE WGPUTextureSampleType _sg_wgpu_texture_sample_type(sg_image_sample_type t, bool msaa) { + switch (t) { + case SG_IMAGESAMPLETYPE_FLOAT: return msaa ? WGPUTextureSampleType_UnfilterableFloat : WGPUTextureSampleType_Float; + case SG_IMAGESAMPLETYPE_DEPTH: return WGPUTextureSampleType_Depth; + case SG_IMAGESAMPLETYPE_SINT: return WGPUTextureSampleType_Sint; + case SG_IMAGESAMPLETYPE_UINT: return WGPUTextureSampleType_Uint; + case SG_IMAGESAMPLETYPE_UNFILTERABLE_FLOAT: return WGPUTextureSampleType_UnfilterableFloat; + default: SOKOL_UNREACHABLE; return WGPUTextureSampleType_Force32; + } +} + +_SOKOL_PRIVATE WGPUSamplerBindingType _sg_wgpu_sampler_binding_type(sg_sampler_type t) { + switch (t) { + case SG_SAMPLERTYPE_FILTERING: return WGPUSamplerBindingType_Filtering; + case SG_SAMPLERTYPE_COMPARISON: return WGPUSamplerBindingType_Comparison; + case SG_SAMPLERTYPE_NONFILTERING: return WGPUSamplerBindingType_NonFiltering; + default: SOKOL_UNREACHABLE; return WGPUSamplerBindingType_Force32; + } +} + +_SOKOL_PRIVATE WGPUAddressMode _sg_wgpu_sampler_address_mode(sg_wrap m) { + switch (m) { + case SG_WRAP_REPEAT: + return WGPUAddressMode_Repeat; + case SG_WRAP_CLAMP_TO_EDGE: + case SG_WRAP_CLAMP_TO_BORDER: + return WGPUAddressMode_ClampToEdge; + case SG_WRAP_MIRRORED_REPEAT: + return WGPUAddressMode_MirrorRepeat; + default: + SOKOL_UNREACHABLE; + return WGPUAddressMode_Force32; + } +} + +_SOKOL_PRIVATE WGPUFilterMode _sg_wgpu_sampler_minmag_filter(sg_filter f) { + switch (f) { + case SG_FILTER_NEAREST: + return WGPUFilterMode_Nearest; + case SG_FILTER_LINEAR: + return WGPUFilterMode_Linear; + default: + SOKOL_UNREACHABLE; + return WGPUFilterMode_Force32; + } +} + +_SOKOL_PRIVATE WGPUMipmapFilterMode _sg_wgpu_sampler_mipmap_filter(sg_filter f) { + switch (f) { + case SG_FILTER_NEAREST: + return WGPUMipmapFilterMode_Nearest; + case SG_FILTER_LINEAR: + return WGPUMipmapFilterMode_Linear; + default: + SOKOL_UNREACHABLE; + return WGPUMipmapFilterMode_Force32; + } +} + +_SOKOL_PRIVATE WGPUIndexFormat _sg_wgpu_indexformat(sg_index_type t) { + // NOTE: there's no WGPUIndexFormat_None + return (t == SG_INDEXTYPE_UINT16) ? WGPUIndexFormat_Uint16 : WGPUIndexFormat_Uint32; +} + +_SOKOL_PRIVATE WGPUIndexFormat _sg_wgpu_stripindexformat(sg_primitive_type prim_type, sg_index_type idx_type) { + if (idx_type == SG_INDEXTYPE_NONE) { + return WGPUIndexFormat_Undefined; + } else if ((prim_type == SG_PRIMITIVETYPE_LINE_STRIP) || (prim_type == SG_PRIMITIVETYPE_TRIANGLE_STRIP)) { + return _sg_wgpu_indexformat(idx_type); + } else { + return WGPUIndexFormat_Undefined; + } +} + +_SOKOL_PRIVATE WGPUVertexStepMode _sg_wgpu_stepmode(sg_vertex_step s) { + return (s == SG_VERTEXSTEP_PER_VERTEX) ? WGPUVertexStepMode_Vertex : WGPUVertexStepMode_Instance; +} + +_SOKOL_PRIVATE WGPUVertexFormat _sg_wgpu_vertexformat(sg_vertex_format f) { + switch (f) { + case SG_VERTEXFORMAT_FLOAT: return WGPUVertexFormat_Float32; + case SG_VERTEXFORMAT_FLOAT2: return WGPUVertexFormat_Float32x2; + case SG_VERTEXFORMAT_FLOAT3: return WGPUVertexFormat_Float32x3; + case SG_VERTEXFORMAT_FLOAT4: return WGPUVertexFormat_Float32x4; + case SG_VERTEXFORMAT_INT: return WGPUVertexFormat_Sint32; + case SG_VERTEXFORMAT_INT2: return WGPUVertexFormat_Sint32x2; + case SG_VERTEXFORMAT_INT3: return WGPUVertexFormat_Sint32x3; + case SG_VERTEXFORMAT_INT4: return WGPUVertexFormat_Sint32x4; + case SG_VERTEXFORMAT_UINT: return WGPUVertexFormat_Uint32; + case SG_VERTEXFORMAT_UINT2: return WGPUVertexFormat_Uint32x2; + case SG_VERTEXFORMAT_UINT3: return WGPUVertexFormat_Uint32x3; + case SG_VERTEXFORMAT_UINT4: return WGPUVertexFormat_Uint32x4; + case SG_VERTEXFORMAT_BYTE4: return WGPUVertexFormat_Sint8x4; + case SG_VERTEXFORMAT_BYTE4N: return WGPUVertexFormat_Snorm8x4; + case SG_VERTEXFORMAT_UBYTE4: return WGPUVertexFormat_Uint8x4; + case SG_VERTEXFORMAT_UBYTE4N: return WGPUVertexFormat_Unorm8x4; + case SG_VERTEXFORMAT_SHORT2: return WGPUVertexFormat_Sint16x2; + case SG_VERTEXFORMAT_SHORT2N: return WGPUVertexFormat_Snorm16x2; + case SG_VERTEXFORMAT_USHORT2: return WGPUVertexFormat_Uint16x2; + case SG_VERTEXFORMAT_USHORT2N: return WGPUVertexFormat_Unorm16x2; + case SG_VERTEXFORMAT_SHORT4: return WGPUVertexFormat_Sint16x4; + case SG_VERTEXFORMAT_SHORT4N: return WGPUVertexFormat_Snorm16x4; + case SG_VERTEXFORMAT_USHORT4: return WGPUVertexFormat_Uint16x4; + case SG_VERTEXFORMAT_USHORT4N: return WGPUVertexFormat_Unorm16x4; + case SG_VERTEXFORMAT_UINT10_N2: return WGPUVertexFormat_Unorm10_10_10_2; + case SG_VERTEXFORMAT_HALF2: return WGPUVertexFormat_Float16x2; + case SG_VERTEXFORMAT_HALF4: return WGPUVertexFormat_Float16x4; + default: + SOKOL_UNREACHABLE; + return WGPUVertexFormat_Force32; + } +} + +_SOKOL_PRIVATE WGPUPrimitiveTopology _sg_wgpu_topology(sg_primitive_type t) { + switch (t) { + case SG_PRIMITIVETYPE_POINTS: return WGPUPrimitiveTopology_PointList; + case SG_PRIMITIVETYPE_LINES: return WGPUPrimitiveTopology_LineList; + case SG_PRIMITIVETYPE_LINE_STRIP: return WGPUPrimitiveTopology_LineStrip; + case SG_PRIMITIVETYPE_TRIANGLES: return WGPUPrimitiveTopology_TriangleList; + case SG_PRIMITIVETYPE_TRIANGLE_STRIP: return WGPUPrimitiveTopology_TriangleStrip; + default: + SOKOL_UNREACHABLE; + return WGPUPrimitiveTopology_Force32; + } +} + +_SOKOL_PRIVATE WGPUFrontFace _sg_wgpu_frontface(sg_face_winding fw) { + return (fw == SG_FACEWINDING_CCW) ? WGPUFrontFace_CCW : WGPUFrontFace_CW; +} + +_SOKOL_PRIVATE WGPUCullMode _sg_wgpu_cullmode(sg_cull_mode cm) { + switch (cm) { + case SG_CULLMODE_NONE: return WGPUCullMode_None; + case SG_CULLMODE_FRONT: return WGPUCullMode_Front; + case SG_CULLMODE_BACK: return WGPUCullMode_Back; + default: + SOKOL_UNREACHABLE; + return WGPUCullMode_Force32; + } +} + +_SOKOL_PRIVATE WGPUTextureFormat _sg_wgpu_textureformat(sg_pixel_format p) { + switch (p) { + case SG_PIXELFORMAT_NONE: return WGPUTextureFormat_Undefined; + case SG_PIXELFORMAT_R8: return WGPUTextureFormat_R8Unorm; + case SG_PIXELFORMAT_R8SN: return WGPUTextureFormat_R8Snorm; + case SG_PIXELFORMAT_R8UI: return WGPUTextureFormat_R8Uint; + case SG_PIXELFORMAT_R8SI: return WGPUTextureFormat_R8Sint; + case SG_PIXELFORMAT_R16UI: return WGPUTextureFormat_R16Uint; + case SG_PIXELFORMAT_R16SI: return WGPUTextureFormat_R16Sint; + case SG_PIXELFORMAT_R16F: return WGPUTextureFormat_R16Float; + case SG_PIXELFORMAT_RG8: return WGPUTextureFormat_RG8Unorm; + case SG_PIXELFORMAT_RG8SN: return WGPUTextureFormat_RG8Snorm; + case SG_PIXELFORMAT_RG8UI: return WGPUTextureFormat_RG8Uint; + case SG_PIXELFORMAT_RG8SI: return WGPUTextureFormat_RG8Sint; + case SG_PIXELFORMAT_R32UI: return WGPUTextureFormat_R32Uint; + case SG_PIXELFORMAT_R32SI: return WGPUTextureFormat_R32Sint; + case SG_PIXELFORMAT_R32F: return WGPUTextureFormat_R32Float; + case SG_PIXELFORMAT_RG16UI: return WGPUTextureFormat_RG16Uint; + case SG_PIXELFORMAT_RG16SI: return WGPUTextureFormat_RG16Sint; + case SG_PIXELFORMAT_RG16F: return WGPUTextureFormat_RG16Float; + case SG_PIXELFORMAT_RGBA8: return WGPUTextureFormat_RGBA8Unorm; + case SG_PIXELFORMAT_SRGB8A8: return WGPUTextureFormat_RGBA8UnormSrgb; + case SG_PIXELFORMAT_RGBA8SN: return WGPUTextureFormat_RGBA8Snorm; + case SG_PIXELFORMAT_RGBA8UI: return WGPUTextureFormat_RGBA8Uint; + case SG_PIXELFORMAT_RGBA8SI: return WGPUTextureFormat_RGBA8Sint; + case SG_PIXELFORMAT_BGRA8: return WGPUTextureFormat_BGRA8Unorm; + case SG_PIXELFORMAT_RGB10A2: return WGPUTextureFormat_RGB10A2Unorm; + case SG_PIXELFORMAT_RG11B10F: return WGPUTextureFormat_RG11B10Ufloat; + case SG_PIXELFORMAT_RG32UI: return WGPUTextureFormat_RG32Uint; + case SG_PIXELFORMAT_RG32SI: return WGPUTextureFormat_RG32Sint; + case SG_PIXELFORMAT_RG32F: return WGPUTextureFormat_RG32Float; + case SG_PIXELFORMAT_RGBA16UI: return WGPUTextureFormat_RGBA16Uint; + case SG_PIXELFORMAT_RGBA16SI: return WGPUTextureFormat_RGBA16Sint; + case SG_PIXELFORMAT_RGBA16F: return WGPUTextureFormat_RGBA16Float; + case SG_PIXELFORMAT_RGBA32UI: return WGPUTextureFormat_RGBA32Uint; + case SG_PIXELFORMAT_RGBA32SI: return WGPUTextureFormat_RGBA32Sint; + case SG_PIXELFORMAT_RGBA32F: return WGPUTextureFormat_RGBA32Float; + case SG_PIXELFORMAT_DEPTH: return WGPUTextureFormat_Depth32Float; + case SG_PIXELFORMAT_DEPTH_STENCIL: return WGPUTextureFormat_Depth32FloatStencil8; + case SG_PIXELFORMAT_BC1_RGBA: return WGPUTextureFormat_BC1RGBAUnorm; + case SG_PIXELFORMAT_BC2_RGBA: return WGPUTextureFormat_BC2RGBAUnorm; + case SG_PIXELFORMAT_BC3_RGBA: return WGPUTextureFormat_BC3RGBAUnorm; + case SG_PIXELFORMAT_BC3_SRGBA: return WGPUTextureFormat_BC3RGBAUnormSrgb; + case SG_PIXELFORMAT_BC4_R: return WGPUTextureFormat_BC4RUnorm; + case SG_PIXELFORMAT_BC4_RSN: return WGPUTextureFormat_BC4RSnorm; + case SG_PIXELFORMAT_BC5_RG: return WGPUTextureFormat_BC5RGUnorm; + case SG_PIXELFORMAT_BC5_RGSN: return WGPUTextureFormat_BC5RGSnorm; + case SG_PIXELFORMAT_BC6H_RGBF: return WGPUTextureFormat_BC6HRGBFloat; + case SG_PIXELFORMAT_BC6H_RGBUF: return WGPUTextureFormat_BC6HRGBUfloat; + case SG_PIXELFORMAT_BC7_RGBA: return WGPUTextureFormat_BC7RGBAUnorm; + case SG_PIXELFORMAT_BC7_SRGBA: return WGPUTextureFormat_BC7RGBAUnormSrgb; + case SG_PIXELFORMAT_ETC2_RGB8: return WGPUTextureFormat_ETC2RGB8Unorm; + case SG_PIXELFORMAT_ETC2_RGB8A1: return WGPUTextureFormat_ETC2RGB8A1Unorm; + case SG_PIXELFORMAT_ETC2_RGBA8: return WGPUTextureFormat_ETC2RGBA8Unorm; + case SG_PIXELFORMAT_ETC2_SRGB8: return WGPUTextureFormat_ETC2RGB8UnormSrgb; + case SG_PIXELFORMAT_ETC2_SRGB8A8: return WGPUTextureFormat_ETC2RGBA8UnormSrgb; + case SG_PIXELFORMAT_EAC_R11: return WGPUTextureFormat_EACR11Unorm; + case SG_PIXELFORMAT_EAC_R11SN: return WGPUTextureFormat_EACR11Snorm; + case SG_PIXELFORMAT_EAC_RG11: return WGPUTextureFormat_EACRG11Unorm; + case SG_PIXELFORMAT_EAC_RG11SN: return WGPUTextureFormat_EACRG11Snorm; + case SG_PIXELFORMAT_RGB9E5: return WGPUTextureFormat_RGB9E5Ufloat; + case SG_PIXELFORMAT_ASTC_4x4_RGBA: return WGPUTextureFormat_ASTC4x4Unorm; + case SG_PIXELFORMAT_ASTC_4x4_SRGBA: return WGPUTextureFormat_ASTC4x4UnormSrgb; + // NOT SUPPORTED + case SG_PIXELFORMAT_R16: + case SG_PIXELFORMAT_R16SN: + case SG_PIXELFORMAT_RG16: + case SG_PIXELFORMAT_RG16SN: + case SG_PIXELFORMAT_RGBA16: + case SG_PIXELFORMAT_RGBA16SN: + return WGPUTextureFormat_Undefined; + + default: + SOKOL_UNREACHABLE; + return WGPUTextureFormat_Force32; + } +} + +_SOKOL_PRIVATE WGPUCompareFunction _sg_wgpu_comparefunc(sg_compare_func f) { + switch (f) { + case SG_COMPAREFUNC_NEVER: return WGPUCompareFunction_Never; + case SG_COMPAREFUNC_LESS: return WGPUCompareFunction_Less; + case SG_COMPAREFUNC_EQUAL: return WGPUCompareFunction_Equal; + case SG_COMPAREFUNC_LESS_EQUAL: return WGPUCompareFunction_LessEqual; + case SG_COMPAREFUNC_GREATER: return WGPUCompareFunction_Greater; + case SG_COMPAREFUNC_NOT_EQUAL: return WGPUCompareFunction_NotEqual; + case SG_COMPAREFUNC_GREATER_EQUAL: return WGPUCompareFunction_GreaterEqual; + case SG_COMPAREFUNC_ALWAYS: return WGPUCompareFunction_Always; + default: + SOKOL_UNREACHABLE; + return WGPUCompareFunction_Force32; + } +} + +_SOKOL_PRIVATE WGPUStencilOperation _sg_wgpu_stencilop(sg_stencil_op op) { + switch (op) { + case SG_STENCILOP_KEEP: return WGPUStencilOperation_Keep; + case SG_STENCILOP_ZERO: return WGPUStencilOperation_Zero; + case SG_STENCILOP_REPLACE: return WGPUStencilOperation_Replace; + case SG_STENCILOP_INCR_CLAMP: return WGPUStencilOperation_IncrementClamp; + case SG_STENCILOP_DECR_CLAMP: return WGPUStencilOperation_DecrementClamp; + case SG_STENCILOP_INVERT: return WGPUStencilOperation_Invert; + case SG_STENCILOP_INCR_WRAP: return WGPUStencilOperation_IncrementWrap; + case SG_STENCILOP_DECR_WRAP: return WGPUStencilOperation_DecrementWrap; + default: + SOKOL_UNREACHABLE; + return WGPUStencilOperation_Force32; + } +} + +_SOKOL_PRIVATE WGPUBlendOperation _sg_wgpu_blendop(sg_blend_op op) { + switch (op) { + case SG_BLENDOP_ADD: return WGPUBlendOperation_Add; + case SG_BLENDOP_SUBTRACT: return WGPUBlendOperation_Subtract; + case SG_BLENDOP_REVERSE_SUBTRACT: return WGPUBlendOperation_ReverseSubtract; + case SG_BLENDOP_MIN: return WGPUBlendOperation_Min; + case SG_BLENDOP_MAX: return WGPUBlendOperation_Max; + default: + SOKOL_UNREACHABLE; + return WGPUBlendOperation_Force32; + } +} + +_SOKOL_PRIVATE WGPUBlendFactor _sg_wgpu_blendfactor(sg_blend_factor f) { + switch (f) { + case SG_BLENDFACTOR_ZERO: return WGPUBlendFactor_Zero; + case SG_BLENDFACTOR_ONE: return WGPUBlendFactor_One; + case SG_BLENDFACTOR_SRC_COLOR: return WGPUBlendFactor_Src; + case SG_BLENDFACTOR_ONE_MINUS_SRC_COLOR: return WGPUBlendFactor_OneMinusSrc; + case SG_BLENDFACTOR_SRC_ALPHA: return WGPUBlendFactor_SrcAlpha; + case SG_BLENDFACTOR_ONE_MINUS_SRC_ALPHA: return WGPUBlendFactor_OneMinusSrcAlpha; + case SG_BLENDFACTOR_DST_COLOR: return WGPUBlendFactor_Dst; + case SG_BLENDFACTOR_ONE_MINUS_DST_COLOR: return WGPUBlendFactor_OneMinusDst; + case SG_BLENDFACTOR_DST_ALPHA: return WGPUBlendFactor_DstAlpha; + case SG_BLENDFACTOR_ONE_MINUS_DST_ALPHA: return WGPUBlendFactor_OneMinusDstAlpha; + case SG_BLENDFACTOR_SRC_ALPHA_SATURATED: return WGPUBlendFactor_SrcAlphaSaturated; + case SG_BLENDFACTOR_BLEND_COLOR: return WGPUBlendFactor_Constant; + case SG_BLENDFACTOR_ONE_MINUS_BLEND_COLOR: return WGPUBlendFactor_OneMinusConstant; + // FIXME: separate blend alpha value not supported? + case SG_BLENDFACTOR_BLEND_ALPHA: return WGPUBlendFactor_Constant; + case SG_BLENDFACTOR_ONE_MINUS_BLEND_ALPHA: return WGPUBlendFactor_OneMinusConstant; + default: + SOKOL_UNREACHABLE; + return WGPUBlendFactor_Force32; + } +} + +_SOKOL_PRIVATE WGPUColorWriteMask _sg_wgpu_colorwritemask(sg_color_mask m) { + int res = 0; + if (0 != (m & SG_COLORMASK_R)) { + res |= (int)WGPUColorWriteMask_Red; + } + if (0 != (m & SG_COLORMASK_G)) { + res |= (int)WGPUColorWriteMask_Green; + } + if (0 != (m & SG_COLORMASK_B)) { + res |= (int)WGPUColorWriteMask_Blue; + } + if (0 != (m & SG_COLORMASK_A)) { + res |= (int)WGPUColorWriteMask_Alpha; + } + return (WGPUColorWriteMask)res; +} + +_SOKOL_PRIVATE WGPUShaderStage _sg_wgpu_shader_stage(sg_shader_stage stage) { + switch (stage) { + case SG_SHADERSTAGE_VERTEX: return WGPUShaderStage_Vertex; + case SG_SHADERSTAGE_FRAGMENT: return WGPUShaderStage_Fragment; + case SG_SHADERSTAGE_COMPUTE: return WGPUShaderStage_Compute; + default: SOKOL_UNREACHABLE; return WGPUShaderStage_None; + } +} + +_SOKOL_PRIVATE void _sg_wgpu_init_caps(void) { + _sg.backend = SG_BACKEND_WGPU; + _sg.features.origin_top_left = true; + _sg.features.image_clamp_to_border = false; + _sg.features.mrt_independent_blend_state = true; + _sg.features.mrt_independent_write_mask = true; + _sg.features.compute = true; + _sg.features.msaa_texture_bindings = true; + _sg.features.draw_base_vertex = true; + _sg.features.draw_base_instance = true; + + wgpuDeviceGetLimits(_sg.wgpu.dev, &_sg.wgpu.limits); + + const WGPULimits* l = &_sg.wgpu.limits; + _sg.limits.max_image_size_2d = (int) l->maxTextureDimension2D; + _sg.limits.max_image_size_cube = (int) l->maxTextureDimension2D; // not a bug, see: https://github.com/gpuweb/gpuweb/issues/1327 + _sg.limits.max_image_size_3d = (int) l->maxTextureDimension3D; + _sg.limits.max_image_size_array = (int) l->maxTextureDimension2D; + _sg.limits.max_image_array_layers = (int) l->maxTextureArrayLayers; + _sg.limits.max_vertex_attrs = SG_MAX_VERTEX_ATTRIBUTES; + _sg.limits.max_color_attachments = _sg_min((int)l->maxColorAttachments, SG_MAX_COLOR_ATTACHMENTS); + _sg.limits.max_texture_bindings_per_stage = _sg_min((int)l->maxSampledTexturesPerShaderStage, SG_MAX_VIEW_BINDSLOTS); + _sg.limits.max_storage_buffer_bindings_per_stage = _sg_min((int)l->maxStorageBuffersPerShaderStage, SG_MAX_VIEW_BINDSLOTS); + _sg.limits.max_storage_image_bindings_per_stage = _sg_min((int)l->maxStorageTexturesPerShaderStage, SG_MAX_VIEW_BINDSLOTS); + + // NOTE: no WGPUTextureFormat_R16Unorm + _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_R8]); + _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RG8]); + _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RGBA8]); + _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_SRGB8A8]); + _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_BGRA8]); + _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_R16F]); + _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RG16F]); + _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RGBA16F]); + _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RGB10A2]); + + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_R8SN]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_RG8SN]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_RGBA8SN]); + + // FIXME: can be made renderable via extension + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_RG11B10F]); + + // NOTE: msaa rendering is possible in WebGPU, but no resolve + // which is a combination that's not currently supported in sokol-gfx + _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_R8UI]); + _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_R8SI]); + _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_RG8UI]); + _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_RG8SI]); + _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_RGBA8UI]); + _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_RGBA8SI]); + _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_R16UI]); + _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_R16SI]); + _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_RG16UI]); + _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_RG16SI]); + _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_RGBA16UI]); + _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_RGBA16SI]); + _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_R32UI]); + _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_R32SI]); + _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_RG32UI]); + _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_RG32SI]); + _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_RGBA32UI]); + _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_RGBA32SI]); + + if (wgpuDeviceHasFeature(_sg.wgpu.dev, WGPUFeatureName_Float32Filterable)) { + _sg_pixelformat_sfr(&_sg.formats[SG_PIXELFORMAT_R32F]); + _sg_pixelformat_sfr(&_sg.formats[SG_PIXELFORMAT_RG32F]); + _sg_pixelformat_sfr(&_sg.formats[SG_PIXELFORMAT_RGBA32F]); + } else { + _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_R32F]); + _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_RG32F]); + _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_RGBA32F]); + } + + _sg_pixelformat_srmd(&_sg.formats[SG_PIXELFORMAT_DEPTH]); + _sg_pixelformat_srmd(&_sg.formats[SG_PIXELFORMAT_DEPTH_STENCIL]); + + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_RGB9E5]); + + if (wgpuDeviceHasFeature(_sg.wgpu.dev, WGPUFeatureName_TextureCompressionBC)) { + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC1_RGBA]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC2_RGBA]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC3_RGBA]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC3_SRGBA]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC4_R]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC4_RSN]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC5_RG]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC5_RGSN]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC6H_RGBF]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC6H_RGBUF]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC7_RGBA]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC7_SRGBA]); + } + if (wgpuDeviceHasFeature(_sg.wgpu.dev, WGPUFeatureName_TextureCompressionETC2)) { + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_ETC2_RGB8]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_ETC2_SRGB8]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_ETC2_RGB8A1]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_ETC2_RGBA8]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_ETC2_SRGB8A8]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_EAC_R11]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_EAC_R11SN]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_EAC_RG11]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_EAC_RG11SN]); + } + + if (wgpuDeviceHasFeature(_sg.wgpu.dev, WGPUFeatureName_TextureCompressionASTC)) { + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_ASTC_4x4_RGBA]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_ASTC_4x4_SRGBA]); + } + + // see: https://github.com/gpuweb/gpuweb/issues/513 + _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_RGBA8]); + _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_RGBA8SN]); + _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_RGBA8UI]); + _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_RGBA8SI]); + _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_RGBA16UI]); + _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_RGBA16SI]); + _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_RGBA16F]); + _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_R32UI]); + _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_R32SI]); + _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_R32F]); + _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_RG32UI]); + _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_RG32SI]); + _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_RG32F]); + _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_RGBA32UI]); + _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_RGBA32SI]); + _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_RGBA32F]); +} + +_SOKOL_PRIVATE void _sg_wgpu_uniform_buffer_init(const sg_desc* desc) { + SOKOL_ASSERT(0 == _sg.wgpu.uniform.staging); + SOKOL_ASSERT(0 == _sg.wgpu.uniform.buf); + + // Add the max-uniform-update size (64 KB) to the requested buffer size, + // this is to prevent validation errors in the WebGPU implementation + // if the entire buffer size is used per frame. 64 KB is the allowed + // max uniform update size on NVIDIA + // + // FIXME: is this still needed? + _sg.wgpu.uniform.num_bytes = (uint32_t)(desc->uniform_buffer_size + _SG_WGPU_MAX_UNIFORM_UPDATE_SIZE); + _sg.wgpu.uniform.staging = (uint8_t*)_sg_malloc(_sg.wgpu.uniform.num_bytes); + + WGPUBufferDescriptor ub_desc; + _sg_clear(&ub_desc, sizeof(ub_desc)); + ub_desc.size = _sg.wgpu.uniform.num_bytes; + ub_desc.usage = WGPUBufferUsage_Uniform|WGPUBufferUsage_CopyDst; + _sg.wgpu.uniform.buf = wgpuDeviceCreateBuffer(_sg.wgpu.dev, &ub_desc); + SOKOL_ASSERT(_sg.wgpu.uniform.buf); +} + +_SOKOL_PRIVATE void _sg_wgpu_uniform_buffer_discard(void) { + if (_sg.wgpu.uniform.buf) { + wgpuBufferRelease(_sg.wgpu.uniform.buf); + _sg.wgpu.uniform.buf = 0; + } + if (_sg.wgpu.uniform.staging) { + _sg_free(_sg.wgpu.uniform.staging); + _sg.wgpu.uniform.staging = 0; + } +} + +_SOKOL_PRIVATE void _sg_wgpu_uniform_buffer_on_commit(void) { + wgpuQueueWriteBuffer(_sg.wgpu.queue, _sg.wgpu.uniform.buf, 0, _sg.wgpu.uniform.staging, _sg.wgpu.uniform.offset); + _sg_stats_add(wgpu.uniforms.size_write_buffer, _sg.wgpu.uniform.offset); + _sg.wgpu.uniform.offset = 0; + _sg_clear(_sg.wgpu.uniform.bind_offsets, sizeof(_sg.wgpu.uniform.bind_offsets)); +} + +_SOKOL_PRIVATE void _sg_wgpu_bindgroups_pool_init(const sg_desc* desc) { + SOKOL_ASSERT((desc->wgpu_bindgroups_cache_size > 0) && (desc->wgpu_bindgroups_cache_size < _SG_MAX_POOL_SIZE)); + _sg_wgpu_bindgroups_pool_t* p = &_sg.wgpu.bindgroups_pool; + SOKOL_ASSERT(0 == p->bindgroups); + const int pool_size = desc->wgpu_bindgroups_cache_size; + _sg_pool_init(&p->pool, pool_size); + size_t pool_byte_size = sizeof(_sg_wgpu_bindgroup_t) * (size_t)p->pool.size; + p->bindgroups = (_sg_wgpu_bindgroup_t*) _sg_malloc_clear(pool_byte_size); +} + +_SOKOL_PRIVATE void _sg_wgpu_bindgroups_pool_discard(void) { + _sg_wgpu_bindgroups_pool_t* p = &_sg.wgpu.bindgroups_pool; + SOKOL_ASSERT(p->bindgroups); + _sg_free(p->bindgroups); p->bindgroups = 0; + _sg_pool_discard(&p->pool); +} + +_SOKOL_PRIVATE _sg_wgpu_bindgroup_t* _sg_wgpu_bindgroup_at(uint32_t bg_id) { + SOKOL_ASSERT(SG_INVALID_ID != bg_id); + _sg_wgpu_bindgroups_pool_t* p = &_sg.wgpu.bindgroups_pool; + int slot_index = _sg_slot_index(bg_id); + SOKOL_ASSERT((slot_index > _SG_INVALID_SLOT_INDEX) && (slot_index < p->pool.size)); + return &p->bindgroups[slot_index]; +} + +_SOKOL_PRIVATE _sg_wgpu_bindgroup_t* _sg_wgpu_lookup_bindgroup(uint32_t bg_id) { + if (SG_INVALID_ID != bg_id) { + _sg_wgpu_bindgroup_t* bg = _sg_wgpu_bindgroup_at(bg_id); + if (bg->slot.id == bg_id) { + return bg; + } + } + return 0; +} + +_SOKOL_PRIVATE _sg_wgpu_bindgroup_handle_t _sg_wgpu_alloc_bindgroup(void) { + _sg_wgpu_bindgroups_pool_t* p = &_sg.wgpu.bindgroups_pool; + _sg_wgpu_bindgroup_handle_t res; + int slot_index = _sg_pool_alloc_index(&p->pool); + if (_SG_INVALID_SLOT_INDEX != slot_index) { + res.id = _sg_slot_alloc(&p->pool, &p->bindgroups[slot_index].slot, slot_index); + } else { + res.id = SG_INVALID_ID; + _SG_ERROR(WGPU_BINDGROUPS_POOL_EXHAUSTED); + } + return res; +} + +_SOKOL_PRIVATE void _sg_wgpu_dealloc_bindgroup(_sg_wgpu_bindgroup_t* bg) { + SOKOL_ASSERT(bg && (bg->slot.state == SG_RESOURCESTATE_ALLOC) && (bg->slot.id != SG_INVALID_ID)); + _sg_wgpu_bindgroups_pool_t* p = &_sg.wgpu.bindgroups_pool; + _sg_pool_free_index(&p->pool, _sg_slot_index(bg->slot.id)); + _sg_slot_reset(&bg->slot); +} + +_SOKOL_PRIVATE void _sg_wgpu_reset_bindgroup_to_alloc_state(_sg_wgpu_bindgroup_t* bg) { + SOKOL_ASSERT(bg); + _sg_slot_t slot = bg->slot; + _sg_clear(bg, sizeof(_sg_wgpu_bindgroup_t)); + bg->slot = slot; + bg->slot.state = SG_RESOURCESTATE_ALLOC; +} + +// MurmurHash64B (see: https://github.com/aappleby/smhasher/blob/61a0530f28277f2e850bfc39600ce61d02b518de/src/MurmurHash2.cpp#L142) +_SOKOL_PRIVATE uint64_t _sg_wgpu_hash(const void* key, int len, uint64_t seed) { + const uint32_t m = 0x5bd1e995; + const int r = 24; + uint32_t h1 = (uint32_t)seed ^ (uint32_t)len; + uint32_t h2 = (uint32_t)(seed >> 32); + const uint32_t * data = (const uint32_t *)key; + while (len >= 8) { + uint32_t k1 = *data++; + k1 *= m; k1 ^= k1 >> r; k1 *= m; + h1 *= m; h1 ^= k1; + len -= 4; + uint32_t k2 = *data++; + k2 *= m; k2 ^= k2 >> r; k2 *= m; + h2 *= m; h2 ^= k2; + len -= 4; + } + if (len >= 4) { + uint32_t k1 = *data++; + k1 *= m; k1 ^= k1 >> r; k1 *= m; + h1 *= m; h1 ^= k1; + len -= 4; + } + switch(len) { + case 3: h2 ^= (uint32_t)(((unsigned char*)data)[2] << 16); + // fall through + case 2: h2 ^= (uint32_t)(((unsigned char*)data)[1] << 8); + // fall through + case 1: h2 ^= ((unsigned char*)data)[0]; + // fall through + h2 *= m; + }; + h1 ^= h2 >> 18; h1 *= m; + h2 ^= h1 >> 22; h2 *= m; + h1 ^= h2 >> 17; h1 *= m; + h2 ^= h1 >> 19; h2 *= m; + uint64_t h = h1; + h = (h << 32) | h2; + return h; +} + +_SOKOL_PRIVATE uint64_t _sg_wgpu_bindgroups_cache_item(_sg_wgpu_bindgroups_cache_item_type_t type, uint8_t wgpu_binding, uint32_t id, uint32_t uninit_count) { + const uint64_t bb = wgpu_binding; + const uint64_t t = type & 3; + const uint64_t ccccc = uninit_count & ((1 << 22) - 1); + const uint64_t iiiiiiii = id; + return (bb << 56) | (t << 54) | (ccccc << 32) | iiiiiiii; +} + +_SOKOL_PRIVATE uint64_t _sg_wgpu_bindgroups_cache_pip_item(const _sg_slot_t* slot) { + return _sg_wgpu_bindgroups_cache_item(_SG_WGPU_BINDGROUPSCACHEITEMTYPE_PIPELINE, 0xFF, slot->id, slot->uninit_count); +} + +_SOKOL_PRIVATE uint64_t _sg_wgpu_bindgroups_cache_view_item(uint8_t wgpu_binding, const _sg_slot_t* slot) { + return _sg_wgpu_bindgroups_cache_item(_SG_WGPU_BINDGROUPSCACHEITEMTYPE_VIEW, wgpu_binding, slot->id, slot->uninit_count); +} + +_SOKOL_PRIVATE uint64_t _sg_wgpu_bindgroups_cache_sampler_item(uint8_t wgpu_binding, const _sg_slot_t* slot) { + return _sg_wgpu_bindgroups_cache_item(_SG_WGPU_BINDGROUPSCACHEITEMTYPE_SAMPLER, wgpu_binding, slot->id, slot->uninit_count); +} + +_SOKOL_PRIVATE void _sg_wgpu_init_bindgroups_cache_key(_sg_wgpu_bindgroups_cache_key_t* key, const _sg_bindings_ptrs_t* bnd) { + SOKOL_ASSERT(bnd); + SOKOL_ASSERT(bnd->pip); + const _sg_shader_t* shd = _sg_shader_ref_ptr(&bnd->pip->cmn.shader); + + _sg_clear(key->items, sizeof(key->items)); + key->items[0] = _sg_wgpu_bindgroups_cache_pip_item(&bnd->pip->slot); + for (size_t i = 0; i < SG_MAX_VIEW_BINDSLOTS; i++) { + if (shd->cmn.views[i].stage == SG_SHADERSTAGE_NONE) { + continue; + } + SOKOL_ASSERT(bnd->views[i]); + const size_t item_idx = i + 1; + SOKOL_ASSERT(item_idx < _SG_WGPU_BINDGROUPSCACHEKEY_NUM_ITEMS); + SOKOL_ASSERT(0 == key->items[item_idx]); + const uint8_t wgpu_binding = shd->wgpu.view_grp1_bnd_n[i]; + key->items[item_idx] = _sg_wgpu_bindgroups_cache_view_item(wgpu_binding, &bnd->views[i]->slot); + } + for (size_t i = 0; i < SG_MAX_SAMPLER_BINDSLOTS; i++) { + if (shd->cmn.samplers[i].stage == SG_SHADERSTAGE_NONE) { + continue; + } + SOKOL_ASSERT(bnd->smps[i]); + const size_t item_idx = i + 1 + SG_MAX_VIEW_BINDSLOTS; + SOKOL_ASSERT(item_idx < _SG_WGPU_BINDGROUPSCACHEKEY_NUM_ITEMS); + SOKOL_ASSERT(0 == key->items[item_idx]); + const uint8_t wgpu_binding = shd->wgpu.smp_grp1_bnd_n[i]; + key->items[item_idx] = _sg_wgpu_bindgroups_cache_sampler_item(wgpu_binding, &bnd->smps[i]->slot); + } + key->hash = _sg_wgpu_hash(&key->items, (int)sizeof(key->items), 0x1234567887654321); +} + +_SOKOL_PRIVATE bool _sg_wgpu_compare_bindgroups_cache_key(_sg_wgpu_bindgroups_cache_key_t* k0, _sg_wgpu_bindgroups_cache_key_t* k1) { + SOKOL_ASSERT(k0 && k1); + if (k0->hash != k1->hash) { + return false; + } + if (memcmp(&k0->items, &k1->items, sizeof(k0->items)) != 0) { + _sg_stats_add(wgpu.bindings.num_bindgroup_cache_hash_vs_key_mismatch, 1); + return false; + } + return true; +} + +_SOKOL_PRIVATE _sg_wgpu_bindgroup_t* _sg_wgpu_create_bindgroup(_sg_bindings_ptrs_t* bnd) { + SOKOL_ASSERT(_sg.wgpu.dev); + SOKOL_ASSERT(bnd->pip); + const _sg_shader_t* shd = _sg_shader_ref_ptr(&bnd->pip->cmn.shader); + _sg_stats_add(wgpu.bindings.num_create_bindgroup, 1); + _sg_wgpu_bindgroup_handle_t bg_id = _sg_wgpu_alloc_bindgroup(); + if (bg_id.id == SG_INVALID_ID) { + return 0; + } + _sg_wgpu_bindgroup_t* bg = _sg_wgpu_bindgroup_at(bg_id.id); + SOKOL_ASSERT(bg && (bg->slot.state == SG_RESOURCESTATE_ALLOC)); + + // create wgpu bindgroup object (also see _sg_wgpu_create_shader()) + WGPUBindGroupLayout bgl = shd->wgpu.bgl_view_smp; + SOKOL_ASSERT(bgl); + WGPUBindGroupEntry bg_entries[_SG_WGPU_MAX_VIEW_SMP_BINDGROUP_ENTRIES]; + _sg_clear(&bg_entries, sizeof(bg_entries)); + size_t bgl_index = 0; + for (size_t i = 0; i < SG_MAX_VIEW_BINDSLOTS; i++) { + if (shd->cmn.views[i].stage == SG_SHADERSTAGE_NONE) { + continue; + } + const _sg_view_t* view = bnd->views[i]; + SOKOL_ASSERT(view); + SOKOL_ASSERT(bgl_index < _SG_WGPU_MAX_VIEW_SMP_BINDGROUP_ENTRIES); + WGPUBindGroupEntry* bg_entry = &bg_entries[bgl_index]; + bg_entry->binding = shd->wgpu.view_grp1_bnd_n[i]; + if (view->cmn.type == SG_VIEWTYPE_STORAGEBUFFER) { + const _sg_buffer_t* buf = _sg_buffer_ref_ptr(&view->cmn.buf.ref); + SOKOL_ASSERT(buf->wgpu.buf); + SOKOL_ASSERT(view->cmn.buf.offset < buf->cmn.size); + bg_entry->buffer = buf->wgpu.buf; + bg_entry->offset = (uint64_t)view->cmn.buf.offset; + bg_entry->size = (uint64_t)(buf->cmn.size - view->cmn.buf.offset); + } else { + SOKOL_ASSERT(view->wgpu.view); + bg_entry->textureView = view->wgpu.view; + } + bgl_index += 1; + } + for (size_t i = 0; i < SG_MAX_SAMPLER_BINDSLOTS; i++) { + if (shd->cmn.samplers[i].stage == SG_SHADERSTAGE_NONE) { + continue; + } + SOKOL_ASSERT(bnd->smps[i]); + SOKOL_ASSERT(bgl_index < _SG_WGPU_MAX_VIEW_SMP_BINDGROUP_ENTRIES); + WGPUBindGroupEntry* bg_entry = &bg_entries[bgl_index]; + bg_entry->binding = shd->wgpu.smp_grp1_bnd_n[i]; + bg_entry->sampler = bnd->smps[i]->wgpu.smp; + bgl_index += 1; + } + WGPUBindGroupDescriptor bg_desc; + _sg_clear(&bg_desc, sizeof(bg_desc)); + bg_desc.layout = bgl; + bg_desc.entryCount = bgl_index; + bg_desc.entries = bg_entries; + bg->bindgroup = wgpuDeviceCreateBindGroup(_sg.wgpu.dev, &bg_desc); + if (bg->bindgroup == 0) { + _SG_ERROR(WGPU_CREATEBINDGROUP_FAILED); + bg->slot.state = SG_RESOURCESTATE_FAILED; + return bg; + } + _sg_wgpu_init_bindgroups_cache_key(&bg->key, bnd); + bg->slot.state = SG_RESOURCESTATE_VALID; + return bg; +} + +_SOKOL_PRIVATE void _sg_wgpu_discard_bindgroup(_sg_wgpu_bindgroup_t* bg) { + SOKOL_ASSERT(bg); + _sg_stats_add(wgpu.bindings.num_discard_bindgroup, 1); + if (bg->slot.state == SG_RESOURCESTATE_VALID) { + if (bg->bindgroup) { + wgpuBindGroupRelease(bg->bindgroup); + bg->bindgroup = 0; + } + _sg_wgpu_reset_bindgroup_to_alloc_state(bg); + SOKOL_ASSERT(bg->slot.state == SG_RESOURCESTATE_ALLOC); + } + if (bg->slot.state == SG_RESOURCESTATE_ALLOC) { + _sg_wgpu_dealloc_bindgroup(bg); + SOKOL_ASSERT(bg->slot.state == SG_RESOURCESTATE_INITIAL); + } +} + +_SOKOL_PRIVATE void _sg_wgpu_discard_all_bindgroups(void) { + _sg_wgpu_bindgroups_pool_t* p = &_sg.wgpu.bindgroups_pool; + for (int i = 0; i < p->pool.size; i++) { + sg_resource_state state = p->bindgroups[i].slot.state; + if ((state == SG_RESOURCESTATE_VALID) || (state == SG_RESOURCESTATE_FAILED)) { + _sg_wgpu_discard_bindgroup(&p->bindgroups[i]); + } + } +} + +_SOKOL_PRIVATE void _sg_wgpu_bindgroups_cache_init(const sg_desc* desc) { + SOKOL_ASSERT(desc); + SOKOL_ASSERT(_sg.wgpu.bindgroups_cache.num == 0); + SOKOL_ASSERT(_sg.wgpu.bindgroups_cache.index_mask == 0); + SOKOL_ASSERT(_sg.wgpu.bindgroups_cache.items == 0); + const int num = desc->wgpu_bindgroups_cache_size; + if (num <= 1) { + _SG_PANIC(WGPU_BINDGROUPSCACHE_SIZE_GREATER_ONE); + } + if (!_sg_ispow2(num)) { + _SG_PANIC(WGPU_BINDGROUPSCACHE_SIZE_POW2); + } + _sg.wgpu.bindgroups_cache.num = (uint32_t)desc->wgpu_bindgroups_cache_size; + _sg.wgpu.bindgroups_cache.index_mask = _sg.wgpu.bindgroups_cache.num - 1; + size_t size_in_bytes = sizeof(_sg_wgpu_bindgroup_handle_t) * (size_t)num; + _sg.wgpu.bindgroups_cache.items = (_sg_wgpu_bindgroup_handle_t*)_sg_malloc_clear(size_in_bytes); +} + +_SOKOL_PRIVATE void _sg_wgpu_bindgroups_cache_discard(void) { + if (_sg.wgpu.bindgroups_cache.items) { + _sg_free(_sg.wgpu.bindgroups_cache.items); + _sg.wgpu.bindgroups_cache.items = 0; + } + _sg.wgpu.bindgroups_cache.num = 0; + _sg.wgpu.bindgroups_cache.index_mask = 0; +} + +_SOKOL_PRIVATE void _sg_wgpu_bindgroups_cache_set(uint64_t hash, uint32_t bg_id) { + uint32_t index = hash & _sg.wgpu.bindgroups_cache.index_mask; + SOKOL_ASSERT(index < _sg.wgpu.bindgroups_cache.num); + SOKOL_ASSERT(_sg.wgpu.bindgroups_cache.items); + _sg.wgpu.bindgroups_cache.items[index].id = bg_id; +} + +_SOKOL_PRIVATE uint32_t _sg_wgpu_bindgroups_cache_get(uint64_t hash) { + uint32_t index = hash & _sg.wgpu.bindgroups_cache.index_mask; + SOKOL_ASSERT(index < _sg.wgpu.bindgroups_cache.num); + SOKOL_ASSERT(_sg.wgpu.bindgroups_cache.items); + return _sg.wgpu.bindgroups_cache.items[index].id; +} + +// called from wgpu resource destroy functions to also invalidate any +// bindgroups cache slot and bindgroup referencing that resource +_SOKOL_PRIVATE void _sg_wgpu_bindgroups_cache_invalidate(_sg_wgpu_bindgroups_cache_item_type_t type, const _sg_slot_t* slot) { + const uint64_t key_mask = _sg_wgpu_bindgroups_cache_item(type, 0xFF, 0xFFFFFFFF, 0xFFFFFFFF); + const uint64_t key_item = _sg_wgpu_bindgroups_cache_item(type, 0, slot->id, slot->uninit_count) & key_mask; + SOKOL_ASSERT(_sg.wgpu.bindgroups_cache.items); + for (uint32_t cache_item_idx = 0; cache_item_idx < _sg.wgpu.bindgroups_cache.num; cache_item_idx++) { + const uint32_t bg_id = _sg.wgpu.bindgroups_cache.items[cache_item_idx].id; + if (bg_id != SG_INVALID_ID) { + _sg_wgpu_bindgroup_t* bg = _sg_wgpu_lookup_bindgroup(bg_id); + SOKOL_ASSERT(bg && (bg->slot.state == SG_RESOURCESTATE_VALID)); + // check if resource is in bindgroup, if yes discard bindgroup and invalidate cache slot + bool invalidate_cache_item = false; + for (int key_item_idx = 0; key_item_idx < _SG_WGPU_BINDGROUPSCACHEKEY_NUM_ITEMS; key_item_idx++) { + if ((bg->key.items[key_item_idx] & key_mask) == key_item) { + invalidate_cache_item = true; + break; + } + } + if (invalidate_cache_item) { + _sg_wgpu_discard_bindgroup(bg); bg = 0; + _sg_wgpu_bindgroups_cache_set(cache_item_idx, SG_INVALID_ID); + _sg_stats_add(wgpu.bindings.num_bindgroup_cache_invalidates, 1); + } + } + } +} + +_SOKOL_PRIVATE void _sg_wgpu_bindings_cache_clear(void) { + memset(&_sg.wgpu.bindings_cache, 0, sizeof(_sg.wgpu.bindings_cache)); +} + +_SOKOL_PRIVATE bool _sg_wgpu_bindings_cache_vb_dirty(size_t index, const _sg_buffer_t* vb, uint64_t offset) { + SOKOL_ASSERT(index < SG_MAX_VERTEXBUFFER_BINDSLOTS); + if (vb) { + return (_sg.wgpu.bindings_cache.vbs[index].buffer.id != vb->slot.id) + || (_sg.wgpu.bindings_cache.vbs[index].offset != offset); + } else { + return _sg.wgpu.bindings_cache.vbs[index].buffer.id != SG_INVALID_ID; + } +} + +_SOKOL_PRIVATE void _sg_wgpu_bindings_cache_vb_update(size_t index, const _sg_buffer_t* vb, uint64_t offset) { + SOKOL_ASSERT(index < SG_MAX_VERTEXBUFFER_BINDSLOTS); + if (vb) { + _sg.wgpu.bindings_cache.vbs[index].buffer.id = vb->slot.id; + _sg.wgpu.bindings_cache.vbs[index].offset = offset; + } else { + _sg.wgpu.bindings_cache.vbs[index].buffer.id = SG_INVALID_ID; + _sg.wgpu.bindings_cache.vbs[index].offset = 0; + } +} + +_SOKOL_PRIVATE bool _sg_wgpu_bindings_cache_ib_dirty(const _sg_buffer_t* ib, uint64_t offset) { + if (ib) { + return (_sg.wgpu.bindings_cache.ib.buffer.id != ib->slot.id) + || (_sg.wgpu.bindings_cache.ib.offset != offset); + } else { + return _sg.wgpu.bindings_cache.ib.buffer.id != SG_INVALID_ID; + } +} + +_SOKOL_PRIVATE void _sg_wgpu_bindings_cache_ib_update(const _sg_buffer_t* ib, uint64_t offset) { + if (ib) { + _sg.wgpu.bindings_cache.ib.buffer.id = ib->slot.id; + _sg.wgpu.bindings_cache.ib.offset = offset; + } else { + _sg.wgpu.bindings_cache.ib.buffer.id = SG_INVALID_ID; + _sg.wgpu.bindings_cache.ib.offset = 0; + } +} + +_SOKOL_PRIVATE bool _sg_wgpu_bindings_cache_bg_dirty(const _sg_wgpu_bindgroup_t* bg) { + if (bg) { + return _sg.wgpu.bindings_cache.bg.id != bg->slot.id; + } else { + return _sg.wgpu.bindings_cache.bg.id != SG_INVALID_ID; + } +} + +_SOKOL_PRIVATE void _sg_wgpu_bindings_cache_bg_update(const _sg_wgpu_bindgroup_t* bg) { + if (bg) { + _sg.wgpu.bindings_cache.bg.id = bg->slot.id; + } else { + _sg.wgpu.bindings_cache.bg.id = SG_INVALID_ID; + } +} + +_SOKOL_PRIVATE void _sg_wgpu_set_bindgroup(uint32_t bg_idx, _sg_wgpu_bindgroup_t* bg) { + if (_sg_wgpu_bindings_cache_bg_dirty(bg)) { + _sg_wgpu_bindings_cache_bg_update(bg); + _sg_stats_add(wgpu.bindings.num_set_bindgroup, 1); + if (_sg.cur_pass.is_compute) { + SOKOL_ASSERT(_sg.wgpu.cpass_enc); + if (bg) { + SOKOL_ASSERT(bg->slot.state == SG_RESOURCESTATE_VALID); + SOKOL_ASSERT(bg->bindgroup); + wgpuComputePassEncoderSetBindGroup(_sg.wgpu.cpass_enc, bg_idx, bg->bindgroup, 0, 0); + } else { + wgpuComputePassEncoderSetBindGroup(_sg.wgpu.cpass_enc, bg_idx, _sg.wgpu.empty_bind_group, 0, 0); + } + } else { + SOKOL_ASSERT(_sg.wgpu.rpass_enc); + if (bg) { + SOKOL_ASSERT(bg->slot.state == SG_RESOURCESTATE_VALID); + SOKOL_ASSERT(bg->bindgroup); + wgpuRenderPassEncoderSetBindGroup(_sg.wgpu.rpass_enc, bg_idx, bg->bindgroup, 0, 0); + } else { + wgpuRenderPassEncoderSetBindGroup(_sg.wgpu.rpass_enc, bg_idx, _sg.wgpu.empty_bind_group, 0, 0); + } + } + } else { + _sg_stats_add(wgpu.bindings.num_skip_redundant_bindgroup, 1); + } +} + +_SOKOL_PRIVATE bool _sg_wgpu_apply_bindings_bindgroup(_sg_bindings_ptrs_t* bnd) { + if (!_sg.desc.wgpu_disable_bindgroups_cache) { + _sg_wgpu_bindgroup_t* bg = 0; + _sg_wgpu_bindgroups_cache_key_t key; + _sg_wgpu_init_bindgroups_cache_key(&key, bnd); + uint32_t bg_id = _sg_wgpu_bindgroups_cache_get(key.hash); + if (bg_id != SG_INVALID_ID) { + // potential cache hit + bg = _sg_wgpu_lookup_bindgroup(bg_id); + SOKOL_ASSERT(bg && (bg->slot.state == SG_RESOURCESTATE_VALID)); + if (!_sg_wgpu_compare_bindgroups_cache_key(&key, &bg->key)) { + // cache collision, need to delete cached bindgroup + _sg_stats_add(wgpu.bindings.num_bindgroup_cache_collisions, 1); + _sg_wgpu_discard_bindgroup(bg); + _sg_wgpu_bindgroups_cache_set(key.hash, SG_INVALID_ID); + bg = 0; + } else { + _sg_stats_add(wgpu.bindings.num_bindgroup_cache_hits, 1); + } + } else { + _sg_stats_add(wgpu.bindings.num_bindgroup_cache_misses, 1); + } + if (bg == 0) { + // either no cache entry yet, or cache collision, create new bindgroup and store in cache + bg = _sg_wgpu_create_bindgroup(bnd); + _sg_wgpu_bindgroups_cache_set(key.hash, bg->slot.id); + } + if (bg && bg->slot.state == SG_RESOURCESTATE_VALID) { + _sg_wgpu_set_bindgroup(_SG_WGPU_VIEW_SMP_BINDGROUP_INDEX, bg); + } else { + return false; + } + } else { + // bindgroups cache disabled, create and destroy bindgroup on the fly (expensive!) + _sg_wgpu_bindgroup_t* bg = _sg_wgpu_create_bindgroup(bnd); + if (bg) { + if (bg->slot.state == SG_RESOURCESTATE_VALID) { + _sg_wgpu_set_bindgroup(_SG_WGPU_VIEW_SMP_BINDGROUP_INDEX, bg); + } + _sg_wgpu_discard_bindgroup(bg); + } else { + return false; + } + } + return true; +} + +_SOKOL_PRIVATE bool _sg_wgpu_apply_index_buffer(_sg_bindings_ptrs_t* bnd) { + SOKOL_ASSERT(_sg.wgpu.rpass_enc); + const _sg_buffer_t* ib = bnd->ib; + uint64_t offset = (uint64_t)bnd->ib_offset; + if (_sg_wgpu_bindings_cache_ib_dirty(ib, offset)) { + _sg_wgpu_bindings_cache_ib_update(ib, offset); + if (ib) { + const WGPUIndexFormat format = _sg_wgpu_indexformat(bnd->pip->cmn.index_type); + const uint64_t buf_size = (uint64_t)ib->cmn.size; + SOKOL_ASSERT(buf_size > offset); + const uint64_t max_bytes = buf_size - offset; + wgpuRenderPassEncoderSetIndexBuffer(_sg.wgpu.rpass_enc, ib->wgpu.buf, format, offset, max_bytes); + /* FIXME: the else-pass should actually set a null index buffer, but that doesn't seem to work yet + } else { + wgpuRenderPassEncoderSetIndexBuffer(_sg.wgpu.rpass_enc, 0, WGPUIndexFormat_Undefined, 0, 0); + */ + } + _sg_stats_add(wgpu.bindings.num_set_index_buffer, 1); + } else { + _sg_stats_add(wgpu.bindings.num_skip_redundant_index_buffer, 1); + } + return true; +} + +_SOKOL_PRIVATE bool _sg_wgpu_apply_vertex_buffers(_sg_bindings_ptrs_t* bnd) { + SOKOL_ASSERT(_sg.wgpu.rpass_enc); + for (uint32_t slot = 0; slot < SG_MAX_VERTEXBUFFER_BINDSLOTS; slot++) { + const _sg_buffer_t* vb = bnd->vbs[slot]; + const uint64_t offset = (uint64_t)bnd->vb_offsets[slot]; + if (_sg_wgpu_bindings_cache_vb_dirty(slot, vb, offset)) { + _sg_wgpu_bindings_cache_vb_update(slot, vb, offset); + if (vb) { + const uint64_t buf_size = (uint64_t)vb->cmn.size; + SOKOL_ASSERT(buf_size > offset); + const uint64_t max_bytes = buf_size - offset; + wgpuRenderPassEncoderSetVertexBuffer(_sg.wgpu.rpass_enc, slot, vb->wgpu.buf, offset, max_bytes); + /* FIXME: the else-pass should actually set a null vertex buffer, but that doesn't seem to work yet + } else { + wgpuRenderPassEncoderSetVertexBuffer(_sg.wgpu.rpass_enc, slot, 0, 0, 0); + */ + } + _sg_stats_add(wgpu.bindings.num_set_vertex_buffer, 1); + } else { + _sg_stats_add(wgpu.bindings.num_skip_redundant_vertex_buffer, 1); + } + } + return true; +} + +_SOKOL_PRIVATE void _sg_wgpu_setup_backend(const sg_desc* desc) { + SOKOL_ASSERT(desc); + SOKOL_ASSERT(desc->environment.wgpu.device); + SOKOL_ASSERT(desc->uniform_buffer_size > 0); + _sg.backend = SG_BACKEND_WGPU; + _sg.wgpu.valid = true; + _sg.wgpu.dev = (WGPUDevice) desc->environment.wgpu.device; + _sg.wgpu.queue = wgpuDeviceGetQueue(_sg.wgpu.dev); + SOKOL_ASSERT(_sg.wgpu.queue); + + _sg_wgpu_init_caps(); + _sg_wgpu_uniform_buffer_init(desc); + _sg_wgpu_bindgroups_pool_init(desc); + _sg_wgpu_bindgroups_cache_init(desc); + _sg_wgpu_bindings_cache_clear(); + + // create an empty bind group + WGPUBindGroupLayoutDescriptor bgl_desc; + _sg_clear(&bgl_desc, sizeof(bgl_desc)); + WGPUBindGroupLayout empty_bgl = wgpuDeviceCreateBindGroupLayout(_sg.wgpu.dev, &bgl_desc); + SOKOL_ASSERT(empty_bgl); + WGPUBindGroupDescriptor bg_desc; + _sg_clear(&bg_desc, sizeof(bg_desc)); + bg_desc.layout = empty_bgl; + _sg.wgpu.empty_bind_group = wgpuDeviceCreateBindGroup(_sg.wgpu.dev, &bg_desc); + SOKOL_ASSERT(_sg.wgpu.empty_bind_group); + wgpuBindGroupLayoutRelease(empty_bgl); +} + +_SOKOL_PRIVATE void _sg_wgpu_discard_backend(void) { + SOKOL_ASSERT(_sg.wgpu.valid); + _sg.wgpu.valid = false; + _sg_wgpu_discard_all_bindgroups(); + _sg_wgpu_bindgroups_cache_discard(); + _sg_wgpu_bindgroups_pool_discard(); + _sg_wgpu_uniform_buffer_discard(); + wgpuBindGroupRelease(_sg.wgpu.empty_bind_group); _sg.wgpu.empty_bind_group = 0; + // the command encoder is usually released in sg_commit() + if (_sg.wgpu.cmd_enc) { + wgpuCommandEncoderRelease(_sg.wgpu.cmd_enc); _sg.wgpu.cmd_enc = 0; + } + wgpuQueueRelease(_sg.wgpu.queue); _sg.wgpu.queue = 0; +} + +_SOKOL_PRIVATE void _sg_wgpu_reset_state_cache(void) { + _sg_wgpu_bindings_cache_clear(); +} + +_SOKOL_PRIVATE sg_resource_state _sg_wgpu_create_buffer(_sg_buffer_t* buf, const sg_buffer_desc* desc) { + SOKOL_ASSERT(buf && desc); + SOKOL_ASSERT(buf->cmn.size > 0); + const bool injected = (0 != desc->wgpu_buffer); + if (injected) { + buf->wgpu.buf = (WGPUBuffer) desc->wgpu_buffer; + wgpuBufferAddRef(buf->wgpu.buf); + } else { + // buffer mapping size must be multiple of 4, so round up buffer size (only a problem + // with index buffers containing odd number of indices) + const uint64_t wgpu_buf_size = _sg_roundup_u64((uint64_t)buf->cmn.size, 4); + const bool map_at_creation = buf->cmn.usage.immutable && (desc->data.ptr); + + WGPUBufferDescriptor wgpu_buf_desc; + _sg_clear(&wgpu_buf_desc, sizeof(wgpu_buf_desc)); + wgpu_buf_desc.usage = _sg_wgpu_buffer_usage(&buf->cmn.usage); + wgpu_buf_desc.size = wgpu_buf_size; + wgpu_buf_desc.mappedAtCreation = map_at_creation; + wgpu_buf_desc.label = _sg_wgpu_stringview(desc->label); + buf->wgpu.buf = wgpuDeviceCreateBuffer(_sg.wgpu.dev, &wgpu_buf_desc); + if (0 == buf->wgpu.buf) { + _SG_ERROR(WGPU_CREATE_BUFFER_FAILED); + return SG_RESOURCESTATE_FAILED; + } + if (map_at_creation) { + SOKOL_ASSERT(desc->data.ptr && (desc->data.size > 0)); + SOKOL_ASSERT(desc->data.size <= (size_t)buf->cmn.size); + // FIXME: inefficient on WASM + void* ptr = wgpuBufferGetMappedRange(buf->wgpu.buf, 0, wgpu_buf_size); + SOKOL_ASSERT(ptr); + memcpy(ptr, desc->data.ptr, desc->data.size); + wgpuBufferUnmap(buf->wgpu.buf); + } + } + return SG_RESOURCESTATE_VALID; +} + +_SOKOL_PRIVATE void _sg_wgpu_discard_buffer(_sg_buffer_t* buf) { + SOKOL_ASSERT(buf); + if (buf->wgpu.buf) { + wgpuBufferRelease(buf->wgpu.buf); + } +} + +_SOKOL_PRIVATE void _sg_wgpu_copy_buffer_data(const _sg_buffer_t* buf, uint64_t offset, const sg_range* data) { + SOKOL_ASSERT((offset + data->size) <= (size_t)buf->cmn.size); + // WebGPU's write-buffer requires the size to be a multiple of four, so we may need to split the copy + // operation into two writeBuffer calls + uint64_t clamped_size = data->size & ~3UL; + uint64_t extra_size = data->size & 3UL; + SOKOL_ASSERT(extra_size < 4); + wgpuQueueWriteBuffer(_sg.wgpu.queue, buf->wgpu.buf, offset, data->ptr, clamped_size); + if (extra_size > 0) { + const uint64_t extra_src_offset = clamped_size; + const uint64_t extra_dst_offset = offset + clamped_size; + uint8_t extra_data[4] = { 0 }; + const uint8_t* extra_src_ptr = ((uint8_t*)data->ptr) + extra_src_offset; + for (size_t i = 0; i < extra_size; i++) { + extra_data[i] = extra_src_ptr[i]; + } + wgpuQueueWriteBuffer(_sg.wgpu.queue, buf->wgpu.buf, extra_dst_offset, extra_data, 4); + } +} + +_SOKOL_PRIVATE void _sg_wgpu_copy_image_data(const _sg_image_t* img, WGPUTexture wgpu_tex, const sg_image_data* data) { + WGPUTexelCopyBufferLayout wgpu_layout; + _sg_clear(&wgpu_layout, sizeof(wgpu_layout)); + WGPUTexelCopyTextureInfo wgpu_copy_tex; + _sg_clear(&wgpu_copy_tex, sizeof(wgpu_copy_tex)); + wgpu_copy_tex.texture = wgpu_tex; + wgpu_copy_tex.aspect = WGPUTextureAspect_All; + WGPUExtent3D wgpu_extent; + _sg_clear(&wgpu_extent, sizeof(wgpu_extent)); + for (int mip_index = 0; mip_index < img->cmn.num_mipmaps; mip_index++) { + wgpu_copy_tex.mipLevel = (uint32_t)mip_index; + int mip_width = _sg_miplevel_dim(img->cmn.width, mip_index); + int mip_height = _sg_miplevel_dim(img->cmn.height, mip_index); + int mip_slices = (img->cmn.type == SG_IMAGETYPE_3D) ? _sg_miplevel_dim(img->cmn.num_slices, mip_index) : img->cmn.num_slices; + const int row_pitch = _sg_row_pitch(img->cmn.pixel_format, mip_width, 1); + const int num_rows = _sg_num_rows(img->cmn.pixel_format, mip_height); + if (_sg_is_compressed_pixel_format(img->cmn.pixel_format)) { + mip_width = _sg_roundup(mip_width, 4); + mip_height = _sg_roundup(mip_height, 4); + } + wgpu_layout.bytesPerRow = (uint32_t)row_pitch; + wgpu_layout.rowsPerImage = (uint32_t)num_rows; + wgpu_extent.width = (uint32_t)mip_width; + wgpu_extent.height = (uint32_t)mip_height; + wgpu_extent.depthOrArrayLayers = (uint32_t)mip_slices; + const sg_range* mip_data = &data->mip_levels[mip_index]; + wgpuQueueWriteTexture(_sg.wgpu.queue, &wgpu_copy_tex, mip_data->ptr, mip_data->size, &wgpu_layout, &wgpu_extent); + } +} + +_SOKOL_PRIVATE sg_resource_state _sg_wgpu_create_image(_sg_image_t* img, const sg_image_desc* desc) { + SOKOL_ASSERT(img && desc); + const bool injected = (0 != desc->wgpu_texture); + if (injected) { + img->wgpu.tex = (WGPUTexture)desc->wgpu_texture; + wgpuTextureAddRef(img->wgpu.tex); + } else { + WGPUTextureDescriptor wgpu_tex_desc; + _sg_clear(&wgpu_tex_desc, sizeof(wgpu_tex_desc)); + wgpu_tex_desc.label = _sg_wgpu_stringview(desc->label); + wgpu_tex_desc.usage = WGPUTextureUsage_TextureBinding|WGPUTextureUsage_CopyDst; + if (desc->usage.color_attachment || desc->usage.resolve_attachment || desc->usage.depth_stencil_attachment) { + wgpu_tex_desc.usage |= WGPUTextureUsage_RenderAttachment; + } + if (desc->usage.storage_image) { + wgpu_tex_desc.usage |= WGPUTextureUsage_StorageBinding; + } + wgpu_tex_desc.dimension = _sg_wgpu_texture_dimension(img->cmn.type); + wgpu_tex_desc.size.width = (uint32_t) img->cmn.width; + wgpu_tex_desc.size.height = (uint32_t) img->cmn.height; + if (desc->type == SG_IMAGETYPE_CUBE) { + wgpu_tex_desc.size.depthOrArrayLayers = 6; + } else { + wgpu_tex_desc.size.depthOrArrayLayers = (uint32_t) img->cmn.num_slices; + } + wgpu_tex_desc.format = _sg_wgpu_textureformat(img->cmn.pixel_format); + wgpu_tex_desc.mipLevelCount = (uint32_t) img->cmn.num_mipmaps; + wgpu_tex_desc.sampleCount = (uint32_t) img->cmn.sample_count; + img->wgpu.tex = wgpuDeviceCreateTexture(_sg.wgpu.dev, &wgpu_tex_desc); + if (0 == img->wgpu.tex) { + _SG_ERROR(WGPU_CREATE_TEXTURE_FAILED); + return SG_RESOURCESTATE_FAILED; + } + if (desc->data.mip_levels[0].ptr) { + _sg_wgpu_copy_image_data(img, img->wgpu.tex, &desc->data); + } + } + return SG_RESOURCESTATE_VALID; +} + +_SOKOL_PRIVATE void _sg_wgpu_discard_image(_sg_image_t* img) { + SOKOL_ASSERT(img); + if (img->wgpu.tex) { + wgpuTextureRelease(img->wgpu.tex); + img->wgpu.tex = 0; + } +} + +_SOKOL_PRIVATE sg_resource_state _sg_wgpu_create_sampler(_sg_sampler_t* smp, const sg_sampler_desc* desc) { + SOKOL_ASSERT(smp && desc); + SOKOL_ASSERT(_sg.wgpu.dev); + const bool injected = (0 != desc->wgpu_sampler); + if (injected) { + smp->wgpu.smp = (WGPUSampler) desc->wgpu_sampler; + wgpuSamplerAddRef(smp->wgpu.smp); + } else { + WGPUSamplerDescriptor wgpu_desc; + _sg_clear(&wgpu_desc, sizeof(wgpu_desc)); + wgpu_desc.label = _sg_wgpu_stringview(desc->label); + wgpu_desc.addressModeU = _sg_wgpu_sampler_address_mode(desc->wrap_u); + wgpu_desc.addressModeV = _sg_wgpu_sampler_address_mode(desc->wrap_v); + wgpu_desc.addressModeW = _sg_wgpu_sampler_address_mode(desc->wrap_w); + wgpu_desc.magFilter = _sg_wgpu_sampler_minmag_filter(desc->mag_filter); + wgpu_desc.minFilter = _sg_wgpu_sampler_minmag_filter(desc->min_filter); + wgpu_desc.mipmapFilter = _sg_wgpu_sampler_mipmap_filter(desc->mipmap_filter); + wgpu_desc.lodMinClamp = desc->min_lod; + wgpu_desc.lodMaxClamp = desc->max_lod; + wgpu_desc.compare = _sg_wgpu_comparefunc(desc->compare); + if (wgpu_desc.compare == WGPUCompareFunction_Never) { + wgpu_desc.compare = WGPUCompareFunction_Undefined; + } + wgpu_desc.maxAnisotropy = (uint16_t)desc->max_anisotropy; + smp->wgpu.smp = wgpuDeviceCreateSampler(_sg.wgpu.dev, &wgpu_desc); + if (0 == smp->wgpu.smp) { + _SG_ERROR(WGPU_CREATE_SAMPLER_FAILED); + return SG_RESOURCESTATE_FAILED; + } + } + return SG_RESOURCESTATE_VALID; +} + +_SOKOL_PRIVATE void _sg_wgpu_discard_sampler(_sg_sampler_t* smp) { + SOKOL_ASSERT(smp); + _sg_wgpu_bindgroups_cache_invalidate(_SG_WGPU_BINDGROUPSCACHEITEMTYPE_SAMPLER, &smp->slot); + if (smp->wgpu.smp) { + wgpuSamplerRelease(smp->wgpu.smp); + smp->wgpu.smp = 0; + } +} + +_SOKOL_PRIVATE _sg_wgpu_shader_func_t _sg_wgpu_create_shader_func(const sg_shader_function* func, const char* label) { + SOKOL_ASSERT(func); + SOKOL_ASSERT(func->source); + SOKOL_ASSERT(func->entry); + + _sg_wgpu_shader_func_t res; + _sg_clear(&res, sizeof(res)); + _sg_strcpy(&res.entry, func->entry); + + WGPUShaderSourceWGSL wgpu_shdsrc_wgsl; + _sg_clear(&wgpu_shdsrc_wgsl, sizeof(wgpu_shdsrc_wgsl)); + wgpu_shdsrc_wgsl.chain.sType = WGPUSType_ShaderSourceWGSL; + wgpu_shdsrc_wgsl.code = _sg_wgpu_stringview(func->source); + + WGPUShaderModuleDescriptor wgpu_shdmod_desc; + _sg_clear(&wgpu_shdmod_desc, sizeof(wgpu_shdmod_desc)); + wgpu_shdmod_desc.nextInChain = &wgpu_shdsrc_wgsl.chain; + wgpu_shdmod_desc.label = _sg_wgpu_stringview(label); + + // NOTE: if compilation fails we won't actually find out in this call since + // it always returns a valid module handle, and the GetCompilationInfo() call + // is asynchronous + res.module = wgpuDeviceCreateShaderModule(_sg.wgpu.dev, &wgpu_shdmod_desc); + if (0 == res.module) { + _SG_ERROR(WGPU_CREATE_SHADER_MODULE_FAILED); + } + return res; +} + +_SOKOL_PRIVATE void _sg_wgpu_discard_shader_func(_sg_wgpu_shader_func_t* func) { + if (func->module) { + wgpuShaderModuleRelease(func->module); + func->module = 0; + } +} + +typedef struct { uint8_t sokol_slot, wgpu_slot; } _sg_wgpu_dynoffset_mapping_t; + +_SOKOL_PRIVATE int _sg_wgpu_dynoffset_cmp(const void* a, const void* b) { + const _sg_wgpu_dynoffset_mapping_t* aa = (const _sg_wgpu_dynoffset_mapping_t*)a; + const _sg_wgpu_dynoffset_mapping_t* bb = (const _sg_wgpu_dynoffset_mapping_t*)b; + if (aa->wgpu_slot < bb->wgpu_slot) return -1; + else if (aa->wgpu_slot > bb->wgpu_slot) return 1; + return 0; +} + +// NOTE: this is an out-of-range check for WGSL bindslots that's also active in release mode +_SOKOL_PRIVATE bool _sg_wgpu_ensure_wgsl_bindslot_ranges(const sg_shader_desc* desc) { + SOKOL_ASSERT(desc); + for (size_t i = 0; i < SG_MAX_UNIFORMBLOCK_BINDSLOTS; i++) { + const sg_shader_uniform_block* ub = &desc->uniform_blocks[i]; + if (ub->stage != SG_SHADERSTAGE_NONE) { + if (ub->wgsl_group0_binding_n >= _SG_WGPU_MAX_UB_BINDGROUP_WGSL_SLOTS) { + _SG_ERROR(WGPU_UNIFORMBLOCK_WGSL_GROUP0_BINDING_OUT_OF_RANGE); + return false; + } + } + } + for (size_t i = 0; i < SG_MAX_VIEW_BINDSLOTS; i++) { + const sg_shader_view* view = &desc->views[i]; + if (view->texture.stage != SG_SHADERSTAGE_NONE) { + if (view->texture.wgsl_group1_binding_n >= _SG_WGPU_MAX_VIEW_SMP_BINDGROUP_WGSL_SLOTS) { + _SG_ERROR(WGPU_TEXTURE_WGSL_GROUP1_BINDING_OUT_OF_RANGE); + return false; + } + } + if (view->storage_buffer.stage != SG_SHADERSTAGE_NONE) { + if (view->storage_buffer.wgsl_group1_binding_n >= _SG_WGPU_MAX_VIEW_SMP_BINDGROUP_WGSL_SLOTS) { + _SG_ERROR(WGPU_STORAGEBUFFER_WGSL_GROUP1_BINDING_OUT_OF_RANGE); + return false; + } + } + if (view->storage_image.stage != SG_SHADERSTAGE_NONE) { + if (view->storage_image.wgsl_group1_binding_n >= _SG_WGPU_MAX_VIEW_SMP_BINDGROUP_WGSL_SLOTS) { + _SG_ERROR(WGPU_STORAGEIMAGE_WGSL_GROUP1_BINDING_OUT_OF_RANGE); + return false; + } + } + } + for (size_t i = 0; i < SG_MAX_SAMPLER_BINDSLOTS; i++) { + const sg_shader_sampler* smp = &desc->samplers[i]; + if (smp->stage != SG_SHADERSTAGE_NONE) { + if (smp->wgsl_group1_binding_n >= _SG_WGPU_MAX_VIEW_SMP_BINDGROUP_WGSL_SLOTS) { + _SG_ERROR(WGPU_SAMPLER_WGSL_GROUP1_BINDING_OUT_OF_RANGE); + return false; + } + } + } + return true; +} + +_SOKOL_PRIVATE sg_resource_state _sg_wgpu_create_shader(_sg_shader_t* shd, const sg_shader_desc* desc) { + SOKOL_ASSERT(shd && desc); + SOKOL_ASSERT(shd->wgpu.vertex_func.module == 0); + SOKOL_ASSERT(shd->wgpu.fragment_func.module == 0); + SOKOL_ASSERT(shd->wgpu.compute_func.module == 0); + SOKOL_ASSERT(shd->wgpu.bgl_ub == 0); + SOKOL_ASSERT(shd->wgpu.bg_ub == 0); + SOKOL_ASSERT(shd->wgpu.bgl_view_smp == 0); + + // do a release-mode bounds-check on wgsl bindslots, even though out-of-range + // bindslots can't cause out-of-bounds accesses in the wgpu backend, this + // is done to be consistent with the other backends + if (!_sg_wgpu_ensure_wgsl_bindslot_ranges(desc)) { + return SG_RESOURCESTATE_FAILED; + } + + // build shader modules + bool shd_valid = true; + if (desc->vertex_func.source) { + shd->wgpu.vertex_func = _sg_wgpu_create_shader_func(&desc->vertex_func, desc->label); + shd_valid &= shd->wgpu.vertex_func.module != 0; + } + if (desc->fragment_func.source) { + shd->wgpu.fragment_func = _sg_wgpu_create_shader_func(&desc->fragment_func, desc->label); + shd_valid &= shd->wgpu.fragment_func.module != 0; + } + if (desc->compute_func.source) { + shd->wgpu.compute_func = _sg_wgpu_create_shader_func(&desc->compute_func, desc->label); + shd_valid &= shd->wgpu.compute_func.module != 0; + } + if (!shd_valid) { + _sg_wgpu_discard_shader_func(&shd->wgpu.vertex_func); + _sg_wgpu_discard_shader_func(&shd->wgpu.fragment_func); + _sg_wgpu_discard_shader_func(&shd->wgpu.compute_func); + return SG_RESOURCESTATE_FAILED; + } + + // create bind group layout and bind group for uniform blocks + // NOTE also need to create a mapping of sokol ub bind slots to array indices + // for the dynamic offsets array in the setBindGroup call + SOKOL_ASSERT(_SG_WGPU_MAX_UB_BINDGROUP_ENTRIES <= _SG_WGPU_MAX_VIEW_SMP_BINDGROUP_ENTRIES); + WGPUBindGroupLayoutEntry bgl_entries[_SG_WGPU_MAX_VIEW_SMP_BINDGROUP_ENTRIES]; + _sg_clear(bgl_entries, sizeof(bgl_entries)); + WGPUBindGroupLayoutDescriptor bgl_desc; + _sg_clear(&bgl_desc, sizeof(bgl_desc)); + WGPUBindGroupEntry bg_entries[_SG_WGPU_MAX_VIEW_SMP_BINDGROUP_ENTRIES]; + _sg_clear(&bg_entries, sizeof(bg_entries)); + WGPUBindGroupDescriptor bg_desc; + _sg_clear(&bg_desc, sizeof(bg_desc)); + _sg_wgpu_dynoffset_mapping_t dynoffset_map[SG_MAX_UNIFORMBLOCK_BINDSLOTS]; + _sg_clear(dynoffset_map, sizeof(dynoffset_map)); + size_t bgl_index = 0; + for (size_t i = 0; i < SG_MAX_UNIFORMBLOCK_BINDSLOTS; i++) { + if (shd->cmn.uniform_blocks[i].stage == SG_SHADERSTAGE_NONE) { + continue; + } + shd->wgpu.ub_grp0_bnd_n[i] = desc->uniform_blocks[i].wgsl_group0_binding_n; + WGPUBindGroupEntry* bg_entry = &bg_entries[bgl_index]; + WGPUBindGroupLayoutEntry* bgl_entry = &bgl_entries[bgl_index]; + bgl_entry->binding = shd->wgpu.ub_grp0_bnd_n[i]; + bgl_entry->visibility = _sg_wgpu_shader_stage(shd->cmn.uniform_blocks[i].stage); + bgl_entry->buffer.type = WGPUBufferBindingType_Uniform; + bgl_entry->buffer.hasDynamicOffset = true; + bg_entry->binding = bgl_entry->binding; + bg_entry->buffer = _sg.wgpu.uniform.buf; + bg_entry->size = _SG_WGPU_MAX_UNIFORM_UPDATE_SIZE; + dynoffset_map[i].sokol_slot = (uint8_t)i; + dynoffset_map[i].wgpu_slot = (uint8_t)bgl_entry->binding; + bgl_index += 1; + } + bgl_desc.entryCount = bgl_index; + bgl_desc.entries = bgl_entries; + shd->wgpu.bgl_ub = wgpuDeviceCreateBindGroupLayout(_sg.wgpu.dev, &bgl_desc); + SOKOL_ASSERT(shd->wgpu.bgl_ub); + bg_desc.layout = shd->wgpu.bgl_ub; + bg_desc.entryCount = bgl_index; + bg_desc.entries = bg_entries; + shd->wgpu.bg_ub = wgpuDeviceCreateBindGroup(_sg.wgpu.dev, &bg_desc); + SOKOL_ASSERT(shd->wgpu.bg_ub); + + // sort the dynoffset_map by wgpu bindings, this is because the + // dynamic offsets of the WebGPU setBindGroup call must be in + // 'binding order', not 'bindgroup entry order' + qsort(dynoffset_map, bgl_index, sizeof(_sg_wgpu_dynoffset_mapping_t), _sg_wgpu_dynoffset_cmp); + shd->wgpu.ub_num_dynoffsets = (uint8_t)bgl_index; + for (uint8_t i = 0; i < bgl_index; i++) { + const uint8_t sokol_slot = dynoffset_map[i].sokol_slot; + shd->wgpu.ub_dynoffsets[sokol_slot] = i; + } + + // create bind group layout for textures, storage buffers/images and samplers + _sg_clear(bgl_entries, sizeof(bgl_entries)); + _sg_clear(&bgl_desc, sizeof(bgl_desc)); + bgl_index = 0; + for (size_t i = 0; i < SG_MAX_VIEW_BINDSLOTS; i++) { + if (shd->cmn.views[i].stage == SG_SHADERSTAGE_NONE) { + continue; + } + WGPUBindGroupLayoutEntry* bgl_entry = &bgl_entries[bgl_index]; + bgl_entry->visibility = _sg_wgpu_shader_stage(shd->cmn.views[i].stage); + if (shd->cmn.views[i].view_type == SG_VIEWTYPE_TEXTURE) { + shd->wgpu.view_grp1_bnd_n[i] = desc->views[i].texture.wgsl_group1_binding_n; + const bool msaa = shd->cmn.views[i].multisampled; + bgl_entry->texture.viewDimension = _sg_wgpu_texture_view_dimension(shd->cmn.views[i].image_type); + bgl_entry->texture.sampleType = _sg_wgpu_texture_sample_type(shd->cmn.views[i].sample_type, msaa); + bgl_entry->texture.multisampled = msaa; + } else if (shd->cmn.views[i].view_type == SG_VIEWTYPE_STORAGEBUFFER) { + shd->wgpu.view_grp1_bnd_n[i] = desc->views[i].storage_buffer.wgsl_group1_binding_n; + if (shd->cmn.views[i].sbuf_readonly) { + bgl_entry->buffer.type = WGPUBufferBindingType_ReadOnlyStorage; + } else { + bgl_entry->buffer.type = WGPUBufferBindingType_Storage; + } + } else if (shd->cmn.views[i].view_type == SG_VIEWTYPE_STORAGEIMAGE) { + shd->wgpu.view_grp1_bnd_n[i] = desc->views[i].storage_image.wgsl_group1_binding_n; + if (shd->cmn.views[i].simg_writeonly) { + bgl_entry->storageTexture.access = WGPUStorageTextureAccess_WriteOnly; + } else { + bgl_entry->storageTexture.access = WGPUStorageTextureAccess_ReadWrite; + } + bgl_entry->storageTexture.format = _sg_wgpu_textureformat(shd->cmn.views[i].access_format); + bgl_entry->texture.viewDimension = _sg_wgpu_texture_view_dimension(shd->cmn.views[i].image_type); + } else { + SOKOL_UNREACHABLE; + } + bgl_entry->binding = shd->wgpu.view_grp1_bnd_n[i]; + bgl_index += 1; + } + for (size_t i = 0; i < SG_MAX_SAMPLER_BINDSLOTS; i++) { + if (shd->cmn.samplers[i].stage == SG_SHADERSTAGE_NONE) { + continue; + } + shd->wgpu.smp_grp1_bnd_n[i] = desc->samplers[i].wgsl_group1_binding_n; + WGPUBindGroupLayoutEntry* bgl_entry = &bgl_entries[bgl_index]; + bgl_entry->binding = shd->wgpu.smp_grp1_bnd_n[i]; + bgl_entry->visibility = _sg_wgpu_shader_stage(shd->cmn.samplers[i].stage); + bgl_entry->sampler.type = _sg_wgpu_sampler_binding_type(shd->cmn.samplers[i].sampler_type); + bgl_index += 1; + } + bgl_desc.entryCount = bgl_index; + bgl_desc.entries = bgl_entries; + shd->wgpu.bgl_view_smp = wgpuDeviceCreateBindGroupLayout(_sg.wgpu.dev, &bgl_desc); + if (shd->wgpu.bgl_view_smp == 0) { + _SG_ERROR(WGPU_SHADER_CREATE_BINDGROUP_LAYOUT_FAILED); + return SG_RESOURCESTATE_FAILED; + } + return SG_RESOURCESTATE_VALID; +} + +_SOKOL_PRIVATE void _sg_wgpu_discard_shader(_sg_shader_t* shd) { + SOKOL_ASSERT(shd); + _sg_wgpu_discard_shader_func(&shd->wgpu.vertex_func); + _sg_wgpu_discard_shader_func(&shd->wgpu.fragment_func); + _sg_wgpu_discard_shader_func(&shd->wgpu.compute_func); + if (shd->wgpu.bgl_ub) { + wgpuBindGroupLayoutRelease(shd->wgpu.bgl_ub); + shd->wgpu.bgl_ub = 0; + } + if (shd->wgpu.bg_ub) { + wgpuBindGroupRelease(shd->wgpu.bg_ub); + shd->wgpu.bg_ub = 0; + } + if (shd->wgpu.bgl_view_smp) { + wgpuBindGroupLayoutRelease(shd->wgpu.bgl_view_smp); + shd->wgpu.bgl_view_smp = 0; + } +} + +_SOKOL_PRIVATE sg_resource_state _sg_wgpu_create_pipeline(_sg_pipeline_t* pip, const sg_pipeline_desc* desc) { + SOKOL_ASSERT(pip && desc); + + const _sg_shader_t* shd = _sg_shader_ref_ptr(&pip->cmn.shader); + SOKOL_ASSERT(shd->wgpu.bgl_ub); + SOKOL_ASSERT(shd->wgpu.bgl_view_smp); + + pip->wgpu.blend_color.r = (double) desc->blend_color.r; + pip->wgpu.blend_color.g = (double) desc->blend_color.g; + pip->wgpu.blend_color.b = (double) desc->blend_color.b; + pip->wgpu.blend_color.a = (double) desc->blend_color.a; + + // - @group(0) for uniform blocks + // - @group(1) for all image, sampler and storagebuffer resources + // - @group(2) optional: storage image attachments in compute passes + size_t num_bgls = 2; + WGPUBindGroupLayout wgpu_bgl[_SG_WGPU_MAX_BINDGROUPS]; + _sg_clear(&wgpu_bgl, sizeof(wgpu_bgl)); + wgpu_bgl[_SG_WGPU_UB_BINDGROUP_INDEX ] = shd->wgpu.bgl_ub; + wgpu_bgl[_SG_WGPU_VIEW_SMP_BINDGROUP_INDEX] = shd->wgpu.bgl_view_smp; + WGPUPipelineLayoutDescriptor wgpu_pl_desc; + _sg_clear(&wgpu_pl_desc, sizeof(wgpu_pl_desc)); + wgpu_pl_desc.bindGroupLayoutCount = num_bgls; + wgpu_pl_desc.bindGroupLayouts = &wgpu_bgl[0]; + const WGPUPipelineLayout wgpu_pip_layout = wgpuDeviceCreatePipelineLayout(_sg.wgpu.dev, &wgpu_pl_desc); + if (0 == wgpu_pip_layout) { + _SG_ERROR(WGPU_CREATE_PIPELINE_LAYOUT_FAILED); + return SG_RESOURCESTATE_FAILED; + } + SOKOL_ASSERT(wgpu_pip_layout); + + if (pip->cmn.is_compute) { + WGPUComputePipelineDescriptor wgpu_pip_desc; + _sg_clear(&wgpu_pip_desc, sizeof(wgpu_pip_desc)); + wgpu_pip_desc.label = _sg_wgpu_stringview(desc->label); + wgpu_pip_desc.layout = wgpu_pip_layout; + wgpu_pip_desc.compute.module = shd->wgpu.compute_func.module; + wgpu_pip_desc.compute.entryPoint = _sg_wgpu_stringview(shd->wgpu.compute_func.entry.buf); + pip->wgpu.cpip = wgpuDeviceCreateComputePipeline(_sg.wgpu.dev, &wgpu_pip_desc); + wgpuPipelineLayoutRelease(wgpu_pip_layout); + if (0 == pip->wgpu.cpip) { + _SG_ERROR(WGPU_CREATE_COMPUTE_PIPELINE_FAILED); + return SG_RESOURCESTATE_FAILED; + } + } else { + WGPUVertexBufferLayout wgpu_vb_layouts[SG_MAX_VERTEXBUFFER_BINDSLOTS]; + _sg_clear(wgpu_vb_layouts, sizeof(wgpu_vb_layouts)); + WGPUVertexAttribute wgpu_vtx_attrs[SG_MAX_VERTEXBUFFER_BINDSLOTS][SG_MAX_VERTEX_ATTRIBUTES]; + _sg_clear(wgpu_vtx_attrs, sizeof(wgpu_vtx_attrs)); + int wgpu_vb_num = 0; + for (int vb_idx = 0; vb_idx < SG_MAX_VERTEXBUFFER_BINDSLOTS; vb_idx++, wgpu_vb_num++) { + const sg_vertex_buffer_layout_state* vbl_state = &desc->layout.buffers[vb_idx]; + if (0 == vbl_state->stride) { + break; + } + wgpu_vb_layouts[vb_idx].arrayStride = (uint64_t)vbl_state->stride; + wgpu_vb_layouts[vb_idx].stepMode = _sg_wgpu_stepmode(vbl_state->step_func); + wgpu_vb_layouts[vb_idx].attributes = &wgpu_vtx_attrs[vb_idx][0]; + } + for (int va_idx = 0; va_idx < SG_MAX_VERTEX_ATTRIBUTES; va_idx++) { + const sg_vertex_attr_state* va_state = &desc->layout.attrs[va_idx]; + if (SG_VERTEXFORMAT_INVALID == va_state->format) { + break; + } + const int vb_idx = va_state->buffer_index; + SOKOL_ASSERT(vb_idx < SG_MAX_VERTEXBUFFER_BINDSLOTS); + SOKOL_ASSERT(pip->cmn.vertex_buffer_layout_active[vb_idx]); + const size_t wgpu_attr_idx = wgpu_vb_layouts[vb_idx].attributeCount; + wgpu_vb_layouts[vb_idx].attributeCount += 1; + wgpu_vtx_attrs[vb_idx][wgpu_attr_idx].format = _sg_wgpu_vertexformat(va_state->format); + wgpu_vtx_attrs[vb_idx][wgpu_attr_idx].offset = (uint64_t)va_state->offset; + wgpu_vtx_attrs[vb_idx][wgpu_attr_idx].shaderLocation = (uint32_t)va_idx; + } + + WGPURenderPipelineDescriptor wgpu_pip_desc; + _sg_clear(&wgpu_pip_desc, sizeof(wgpu_pip_desc)); + WGPUDepthStencilState wgpu_ds_state; + _sg_clear(&wgpu_ds_state, sizeof(wgpu_ds_state)); + WGPUFragmentState wgpu_frag_state; + _sg_clear(&wgpu_frag_state, sizeof(wgpu_frag_state)); + WGPUColorTargetState wgpu_ctgt_state[SG_MAX_COLOR_ATTACHMENTS]; + _sg_clear(&wgpu_ctgt_state, sizeof(wgpu_ctgt_state)); + WGPUBlendState wgpu_blend_state[SG_MAX_COLOR_ATTACHMENTS]; + _sg_clear(&wgpu_blend_state, sizeof(wgpu_blend_state)); + wgpu_pip_desc.label = _sg_wgpu_stringview(desc->label); + wgpu_pip_desc.layout = wgpu_pip_layout; + wgpu_pip_desc.vertex.module = shd->wgpu.vertex_func.module; + wgpu_pip_desc.vertex.entryPoint = _sg_wgpu_stringview(shd->wgpu.vertex_func.entry.buf); + wgpu_pip_desc.vertex.bufferCount = (size_t)wgpu_vb_num; + wgpu_pip_desc.vertex.buffers = &wgpu_vb_layouts[0]; + wgpu_pip_desc.primitive.topology = _sg_wgpu_topology(desc->primitive_type); + wgpu_pip_desc.primitive.stripIndexFormat = _sg_wgpu_stripindexformat(desc->primitive_type, desc->index_type); + wgpu_pip_desc.primitive.frontFace = _sg_wgpu_frontface(desc->face_winding); + wgpu_pip_desc.primitive.cullMode = _sg_wgpu_cullmode(desc->cull_mode); + if (SG_PIXELFORMAT_NONE != desc->depth.pixel_format) { + wgpu_ds_state.format = _sg_wgpu_textureformat(desc->depth.pixel_format); + wgpu_ds_state.depthWriteEnabled = _sg_wgpu_optional_bool(desc->depth.write_enabled); + wgpu_ds_state.depthCompare = _sg_wgpu_comparefunc(desc->depth.compare); + wgpu_ds_state.stencilFront.compare = _sg_wgpu_comparefunc(desc->stencil.front.compare); + wgpu_ds_state.stencilFront.failOp = _sg_wgpu_stencilop(desc->stencil.front.fail_op); + wgpu_ds_state.stencilFront.depthFailOp = _sg_wgpu_stencilop(desc->stencil.front.depth_fail_op); + wgpu_ds_state.stencilFront.passOp = _sg_wgpu_stencilop(desc->stencil.front.pass_op); + wgpu_ds_state.stencilBack.compare = _sg_wgpu_comparefunc(desc->stencil.back.compare); + wgpu_ds_state.stencilBack.failOp = _sg_wgpu_stencilop(desc->stencil.back.fail_op); + wgpu_ds_state.stencilBack.depthFailOp = _sg_wgpu_stencilop(desc->stencil.back.depth_fail_op); + wgpu_ds_state.stencilBack.passOp = _sg_wgpu_stencilop(desc->stencil.back.pass_op); + wgpu_ds_state.stencilReadMask = desc->stencil.read_mask; + wgpu_ds_state.stencilWriteMask = desc->stencil.write_mask; + wgpu_ds_state.depthBias = (int32_t)desc->depth.bias; + wgpu_ds_state.depthBiasSlopeScale = desc->depth.bias_slope_scale; + wgpu_ds_state.depthBiasClamp = desc->depth.bias_clamp; + wgpu_pip_desc.depthStencil = &wgpu_ds_state; + } + wgpu_pip_desc.multisample.count = (uint32_t)desc->sample_count; + wgpu_pip_desc.multisample.mask = 0xFFFFFFFF; + wgpu_pip_desc.multisample.alphaToCoverageEnabled = desc->alpha_to_coverage_enabled; + if (desc->color_count > 0) { + wgpu_frag_state.module = shd->wgpu.fragment_func.module; + wgpu_frag_state.entryPoint = _sg_wgpu_stringview(shd->wgpu.fragment_func.entry.buf); + wgpu_frag_state.targetCount = (size_t)desc->color_count; + wgpu_frag_state.targets = &wgpu_ctgt_state[0]; + for (int i = 0; i < desc->color_count; i++) { + SOKOL_ASSERT(i < SG_MAX_COLOR_ATTACHMENTS); + wgpu_ctgt_state[i].format = _sg_wgpu_textureformat(desc->colors[i].pixel_format); + wgpu_ctgt_state[i].writeMask = _sg_wgpu_colorwritemask(desc->colors[i].write_mask); + if (desc->colors[i].blend.enabled) { + wgpu_ctgt_state[i].blend = &wgpu_blend_state[i]; + wgpu_blend_state[i].color.operation = _sg_wgpu_blendop(desc->colors[i].blend.op_rgb); + wgpu_blend_state[i].color.srcFactor = _sg_wgpu_blendfactor(desc->colors[i].blend.src_factor_rgb); + wgpu_blend_state[i].color.dstFactor = _sg_wgpu_blendfactor(desc->colors[i].blend.dst_factor_rgb); + wgpu_blend_state[i].alpha.operation = _sg_wgpu_blendop(desc->colors[i].blend.op_alpha); + wgpu_blend_state[i].alpha.srcFactor = _sg_wgpu_blendfactor(desc->colors[i].blend.src_factor_alpha); + wgpu_blend_state[i].alpha.dstFactor = _sg_wgpu_blendfactor(desc->colors[i].blend.dst_factor_alpha); + } + } + wgpu_pip_desc.fragment = &wgpu_frag_state; + } + pip->wgpu.rpip = wgpuDeviceCreateRenderPipeline(_sg.wgpu.dev, &wgpu_pip_desc); + wgpuPipelineLayoutRelease(wgpu_pip_layout); + if (0 == pip->wgpu.rpip) { + _SG_ERROR(WGPU_CREATE_RENDER_PIPELINE_FAILED); + return SG_RESOURCESTATE_FAILED; + } + } + return SG_RESOURCESTATE_VALID; +} + +_SOKOL_PRIVATE void _sg_wgpu_discard_pipeline(_sg_pipeline_t* pip) { + SOKOL_ASSERT(pip); + _sg_wgpu_bindgroups_cache_invalidate(_SG_WGPU_BINDGROUPSCACHEITEMTYPE_PIPELINE, &pip->slot); + if (pip->wgpu.rpip) { + wgpuRenderPipelineRelease(pip->wgpu.rpip); + pip->wgpu.rpip = 0; + } + if (pip->wgpu.cpip) { + wgpuComputePipelineRelease(pip->wgpu.cpip); + pip->wgpu.cpip = 0; + } +} + +_SOKOL_PRIVATE sg_resource_state _sg_wgpu_create_view(_sg_view_t* view, const sg_view_desc* desc) { + SOKOL_ASSERT(view && desc); + if (view->cmn.type != SG_VIEWTYPE_STORAGEBUFFER) { + const _sg_image_t* img = _sg_image_ref_ptr(&view->cmn.img.ref); + SOKOL_ASSERT(img->wgpu.tex); + SOKOL_ASSERT(view->cmn.img.mip_level_count >= 1); + SOKOL_ASSERT(view->cmn.img.slice_count >= 1); + WGPUTextureViewDescriptor wgpu_texview_desc; + _sg_clear(&wgpu_texview_desc, sizeof(wgpu_texview_desc)); + wgpu_texview_desc.label = _sg_wgpu_stringview(desc->label); + wgpu_texview_desc.baseMipLevel = (uint32_t)view->cmn.img.mip_level; + wgpu_texview_desc.mipLevelCount = (uint32_t)view->cmn.img.mip_level_count; + wgpu_texview_desc.baseArrayLayer = (uint32_t)view->cmn.img.slice; + wgpu_texview_desc.arrayLayerCount = (uint32_t)view->cmn.img.slice_count; + if (view->cmn.type == SG_VIEWTYPE_TEXTURE) { + wgpu_texview_desc.dimension = _sg_wgpu_texture_view_dimension(img->cmn.type); + } else { + wgpu_texview_desc.dimension = _sg_wgpu_attachment_view_dimension(img->cmn.type); + } + if (view->cmn.type == SG_VIEWTYPE_DEPTHSTENCILATTACHMENT) { + wgpu_texview_desc.aspect = WGPUTextureAspect_All; + } else if (_sg_is_depth_or_depth_stencil_format(img->cmn.pixel_format)) { + wgpu_texview_desc.aspect = WGPUTextureAspect_DepthOnly; + } else { + wgpu_texview_desc.aspect = WGPUTextureAspect_All; + } + view->wgpu.view = wgpuTextureCreateView(img->wgpu.tex, &wgpu_texview_desc); + if (0 == view->wgpu.view) { + _SG_ERROR(WGPU_CREATE_TEXTURE_VIEW_FAILED); + return SG_RESOURCESTATE_FAILED; + } + } + return SG_RESOURCESTATE_VALID; +} + +_SOKOL_PRIVATE void _sg_wgpu_discard_view(_sg_view_t* view) { + SOKOL_ASSERT(view); + _sg_wgpu_bindgroups_cache_invalidate(_SG_WGPU_BINDGROUPSCACHEITEMTYPE_VIEW, &view->slot); + if (view->wgpu.view) { + wgpuTextureViewRelease(view->wgpu.view); + view->wgpu.view = 0; + } +} + +_SOKOL_PRIVATE void _sg_wgpu_init_color_att(WGPURenderPassColorAttachment* wgpu_att, const sg_color_attachment_action* action, WGPUTextureView color_view, WGPUTextureView resolve_view) { + wgpu_att->depthSlice = WGPU_DEPTH_SLICE_UNDEFINED; + wgpu_att->view = color_view; + wgpu_att->resolveTarget = resolve_view; + wgpu_att->loadOp = _sg_wgpu_load_op(color_view, action->load_action); + wgpu_att->storeOp = _sg_wgpu_store_op(color_view, action->store_action); + wgpu_att->clearValue.r = action->clear_value.r; + wgpu_att->clearValue.g = action->clear_value.g; + wgpu_att->clearValue.b = action->clear_value.b; + wgpu_att->clearValue.a = action->clear_value.a; +} + +_SOKOL_PRIVATE void _sg_wgpu_init_ds_att(WGPURenderPassDepthStencilAttachment* wgpu_att, const sg_pass_action* action, sg_pixel_format fmt, WGPUTextureView view) { + wgpu_att->view = view; + wgpu_att->depthLoadOp = _sg_wgpu_load_op(view, action->depth.load_action); + wgpu_att->depthStoreOp = _sg_wgpu_store_op(view, action->depth.store_action); + wgpu_att->depthClearValue = action->depth.clear_value; + wgpu_att->depthReadOnly = false; + if (_sg_is_depth_stencil_format(fmt)) { + wgpu_att->stencilLoadOp = _sg_wgpu_load_op(view, action->stencil.load_action); + wgpu_att->stencilStoreOp = _sg_wgpu_store_op(view, action->stencil.store_action); + } else { + wgpu_att->stencilLoadOp = WGPULoadOp_Undefined; + wgpu_att->stencilStoreOp = WGPUStoreOp_Undefined; + } + wgpu_att->stencilClearValue = action->stencil.clear_value; + wgpu_att->stencilReadOnly = false; +} + +_SOKOL_PRIVATE void _sg_wgpu_begin_compute_pass(const sg_pass* pass) { + WGPUComputePassDescriptor wgpu_pass_desc; + _sg_clear(&wgpu_pass_desc, sizeof(wgpu_pass_desc)); + wgpu_pass_desc.label = _sg_wgpu_stringview(pass->label); + _sg.wgpu.cpass_enc = wgpuCommandEncoderBeginComputePass(_sg.wgpu.cmd_enc, &wgpu_pass_desc); + SOKOL_ASSERT(_sg.wgpu.cpass_enc); + // clear initial bindings + wgpuComputePassEncoderSetBindGroup(_sg.wgpu.cpass_enc, _SG_WGPU_UB_BINDGROUP_INDEX, _sg.wgpu.empty_bind_group, 0, 0); + wgpuComputePassEncoderSetBindGroup(_sg.wgpu.cpass_enc, _SG_WGPU_VIEW_SMP_BINDGROUP_INDEX, _sg.wgpu.empty_bind_group, 0, 0); + _sg_stats_add(wgpu.bindings.num_set_bindgroup, 1); +} + +_SOKOL_PRIVATE void _sg_wgpu_begin_render_pass(const sg_pass* pass, const _sg_attachments_ptrs_t* atts) { + const sg_swapchain* swapchain = &pass->swapchain; + const sg_pass_action* action = &pass->action; + + WGPURenderPassDescriptor wgpu_pass_desc; + WGPURenderPassColorAttachment wgpu_color_att[SG_MAX_COLOR_ATTACHMENTS]; + WGPURenderPassDepthStencilAttachment wgpu_ds_att; + _sg_clear(&wgpu_pass_desc, sizeof(wgpu_pass_desc)); + _sg_clear(&wgpu_color_att, sizeof(wgpu_color_att)); + _sg_clear(&wgpu_ds_att, sizeof(wgpu_ds_att)); + wgpu_pass_desc.label = _sg_wgpu_stringview(pass->label); + if (!atts->empty) { + SOKOL_ASSERT(atts->num_color_views <= SG_MAX_COLOR_ATTACHMENTS); + for (int i = 0; i < atts->num_color_views; i++) { + SOKOL_ASSERT(atts->color_views[i]); + WGPUTextureView wgpu_color_view = atts->color_views[i]->wgpu.view; + WGPUTextureView wgpu_resolve_view = 0; + if (atts->resolve_views[i]) { + wgpu_resolve_view = atts->resolve_views[i]->wgpu.view; + } + _sg_wgpu_init_color_att(&wgpu_color_att[i], &action->colors[i], wgpu_color_view, wgpu_resolve_view); + } + wgpu_pass_desc.colorAttachmentCount = (size_t)atts->num_color_views; + wgpu_pass_desc.colorAttachments = &wgpu_color_att[0]; + if (atts->ds_view) { + const _sg_image_t* img = _sg_image_ref_ptr(&atts->ds_view->cmn.img.ref); + WGPUTextureView wgpu_ds_view = atts->ds_view->wgpu.view; + SOKOL_ASSERT(wgpu_ds_view); + _sg_wgpu_init_ds_att(&wgpu_ds_att, action, img->cmn.pixel_format, wgpu_ds_view); + wgpu_pass_desc.depthStencilAttachment = &wgpu_ds_att; + } + } else { + WGPUTextureView wgpu_color_view = (WGPUTextureView) swapchain->wgpu.render_view; + WGPUTextureView wgpu_resolve_view = (WGPUTextureView) swapchain->wgpu.resolve_view; + WGPUTextureView wgpu_depth_stencil_view = (WGPUTextureView) swapchain->wgpu.depth_stencil_view; + _sg_wgpu_init_color_att(&wgpu_color_att[0], &action->colors[0], wgpu_color_view, wgpu_resolve_view); + wgpu_pass_desc.colorAttachmentCount = 1; + wgpu_pass_desc.colorAttachments = &wgpu_color_att[0]; + if (wgpu_depth_stencil_view) { + SOKOL_ASSERT(swapchain->depth_format > SG_PIXELFORMAT_NONE); + _sg_wgpu_init_ds_att(&wgpu_ds_att, action, swapchain->depth_format, wgpu_depth_stencil_view); + wgpu_pass_desc.depthStencilAttachment = &wgpu_ds_att; + } + } + _sg.wgpu.rpass_enc = wgpuCommandEncoderBeginRenderPass(_sg.wgpu.cmd_enc, &wgpu_pass_desc); + SOKOL_ASSERT(_sg.wgpu.rpass_enc); + + wgpuRenderPassEncoderSetBindGroup(_sg.wgpu.rpass_enc, _SG_WGPU_UB_BINDGROUP_INDEX, _sg.wgpu.empty_bind_group, 0, 0); + wgpuRenderPassEncoderSetBindGroup(_sg.wgpu.rpass_enc, _SG_WGPU_VIEW_SMP_BINDGROUP_INDEX, _sg.wgpu.empty_bind_group, 0, 0); + _sg_stats_add(wgpu.bindings.num_set_bindgroup, 1); +} + +_SOKOL_PRIVATE void _sg_wgpu_begin_pass(const sg_pass* pass, const _sg_attachments_ptrs_t* atts) { + SOKOL_ASSERT(pass && atts); + SOKOL_ASSERT(_sg.wgpu.dev); + SOKOL_ASSERT(0 == _sg.wgpu.rpass_enc); + SOKOL_ASSERT(0 == _sg.wgpu.cpass_enc); + + // first pass in the frame? create command encoder + if (0 == _sg.wgpu.cmd_enc) { + WGPUCommandEncoderDescriptor cmd_enc_desc; + _sg_clear(&cmd_enc_desc, sizeof(cmd_enc_desc)); + _sg.wgpu.cmd_enc = wgpuDeviceCreateCommandEncoder(_sg.wgpu.dev, &cmd_enc_desc); + SOKOL_ASSERT(_sg.wgpu.cmd_enc); + } + + _sg_wgpu_bindings_cache_clear(); + if (pass->compute) { + _sg_wgpu_begin_compute_pass(pass); + } else { + _sg_wgpu_begin_render_pass(pass, atts); + } +} + +_SOKOL_PRIVATE void _sg_wgpu_end_pass(const _sg_attachments_ptrs_t* atts) { + _SOKOL_UNUSED(atts); + if (_sg.wgpu.rpass_enc) { + wgpuRenderPassEncoderEnd(_sg.wgpu.rpass_enc); + wgpuRenderPassEncoderRelease(_sg.wgpu.rpass_enc); + _sg.wgpu.rpass_enc = 0; + } + if (_sg.wgpu.cpass_enc) { + wgpuComputePassEncoderEnd(_sg.wgpu.cpass_enc); + wgpuComputePassEncoderRelease(_sg.wgpu.cpass_enc); + _sg.wgpu.cpass_enc = 0; + } +} + +_SOKOL_PRIVATE void _sg_wgpu_commit(void) { + SOKOL_ASSERT(_sg.wgpu.cmd_enc); + + _sg_wgpu_uniform_buffer_on_commit(); + + WGPUCommandBufferDescriptor cmd_buf_desc; + _sg_clear(&cmd_buf_desc, sizeof(cmd_buf_desc)); + WGPUCommandBuffer wgpu_cmd_buf = wgpuCommandEncoderFinish(_sg.wgpu.cmd_enc, &cmd_buf_desc); + SOKOL_ASSERT(wgpu_cmd_buf); + wgpuCommandEncoderRelease(_sg.wgpu.cmd_enc); + _sg.wgpu.cmd_enc = 0; + + wgpuQueueSubmit(_sg.wgpu.queue, 1, &wgpu_cmd_buf); + wgpuCommandBufferRelease(wgpu_cmd_buf); +} + +_SOKOL_PRIVATE void _sg_wgpu_apply_viewport(int x, int y, int w, int h, bool origin_top_left) { + SOKOL_ASSERT(_sg.wgpu.rpass_enc); + float xf = (float) x; + float yf = (float) (origin_top_left ? y : (_sg.cur_pass.dim.height - (y + h))); + float wf = (float) w; + float hf = (float) h; + wgpuRenderPassEncoderSetViewport(_sg.wgpu.rpass_enc, xf, yf, wf, hf, 0.0f, 1.0f); +} + +_SOKOL_PRIVATE void _sg_wgpu_apply_scissor_rect(int x, int y, int w, int h, bool origin_top_left) { + SOKOL_ASSERT(_sg.wgpu.rpass_enc); + const _sg_recti_t clip = _sg_clipi(x, y, w, h, _sg.cur_pass.dim.width, _sg.cur_pass.dim.height); + uint32_t sx = (uint32_t) clip.x; + uint32_t sy = (uint32_t) (origin_top_left ? clip.y : (_sg.cur_pass.dim.height - (clip.y + clip.h))); + uint32_t sw = (uint32_t) clip.w; + uint32_t sh = (uint32_t) clip.h; + wgpuRenderPassEncoderSetScissorRect(_sg.wgpu.rpass_enc, sx, sy, sw, sh); +} + +_SOKOL_PRIVATE void _sg_wgpu_set_ub_bindgroup(const _sg_shader_t* shd) { + // NOTE: dynamic offsets must be in binding order, not in BindGroupEntry order + SOKOL_ASSERT(shd->wgpu.ub_num_dynoffsets < SG_MAX_UNIFORMBLOCK_BINDSLOTS); + uint32_t dyn_offsets[SG_MAX_UNIFORMBLOCK_BINDSLOTS]; + _sg_clear(dyn_offsets, sizeof(dyn_offsets)); + for (size_t i = 0; i < SG_MAX_UNIFORMBLOCK_BINDSLOTS; i++) { + if (shd->cmn.uniform_blocks[i].stage == SG_SHADERSTAGE_NONE) { + continue; + } + uint8_t dynoffset_index = shd->wgpu.ub_dynoffsets[i]; + SOKOL_ASSERT(dynoffset_index < shd->wgpu.ub_num_dynoffsets); + dyn_offsets[dynoffset_index] = _sg.wgpu.uniform.bind_offsets[i]; + } + if (_sg.cur_pass.is_compute) { + SOKOL_ASSERT(_sg.wgpu.cpass_enc); + wgpuComputePassEncoderSetBindGroup(_sg.wgpu.cpass_enc, + _SG_WGPU_UB_BINDGROUP_INDEX, + shd->wgpu.bg_ub, + shd->wgpu.ub_num_dynoffsets, + dyn_offsets); + } else { + SOKOL_ASSERT(_sg.wgpu.rpass_enc); + wgpuRenderPassEncoderSetBindGroup(_sg.wgpu.rpass_enc, + _SG_WGPU_UB_BINDGROUP_INDEX, + shd->wgpu.bg_ub, + shd->wgpu.ub_num_dynoffsets, + dyn_offsets); + } +} + +_SOKOL_PRIVATE void _sg_wgpu_apply_pipeline(_sg_pipeline_t* pip) { + SOKOL_ASSERT(pip); + const _sg_shader_t* shd = _sg_shader_ref_ptr(&pip->cmn.shader); + if (pip->cmn.is_compute) { + SOKOL_ASSERT(_sg.cur_pass.is_compute); + SOKOL_ASSERT(pip->wgpu.cpip); + SOKOL_ASSERT(_sg.wgpu.cpass_enc); + wgpuComputePassEncoderSetPipeline(_sg.wgpu.cpass_enc, pip->wgpu.cpip); + } else { + SOKOL_ASSERT(!_sg.cur_pass.is_compute); + SOKOL_ASSERT(pip->wgpu.rpip); + SOKOL_ASSERT(_sg.wgpu.rpass_enc); + wgpuRenderPassEncoderSetPipeline(_sg.wgpu.rpass_enc, pip->wgpu.rpip); + wgpuRenderPassEncoderSetBlendConstant(_sg.wgpu.rpass_enc, &pip->wgpu.blend_color); + wgpuRenderPassEncoderSetStencilReference(_sg.wgpu.rpass_enc, pip->cmn.stencil.ref); + } + // bind groups must be set because pipelines without uniform blocks or resource bindings + // will still create 'empty' BindGroupLayouts + _sg_wgpu_set_ub_bindgroup(shd); + _sg_wgpu_set_bindgroup(_SG_WGPU_VIEW_SMP_BINDGROUP_INDEX, 0); // this will set the 'empty bind group' +} + +_SOKOL_PRIVATE bool _sg_wgpu_apply_bindings(_sg_bindings_ptrs_t* bnd) { + SOKOL_ASSERT(bnd); + bool retval = true; + if (!_sg.cur_pass.is_compute) { + retval &= _sg_wgpu_apply_index_buffer(bnd); + retval &= _sg_wgpu_apply_vertex_buffers(bnd); + } + retval &= _sg_wgpu_apply_bindings_bindgroup(bnd); + return retval; +} + +_SOKOL_PRIVATE void _sg_wgpu_apply_uniforms(int ub_slot, const sg_range* data) { + const uint32_t alignment = _sg.wgpu.limits.minUniformBufferOffsetAlignment; + SOKOL_ASSERT(_sg.wgpu.uniform.staging); + SOKOL_ASSERT((ub_slot >= 0) && (ub_slot < SG_MAX_UNIFORMBLOCK_BINDSLOTS)); + SOKOL_ASSERT((_sg.wgpu.uniform.offset + data->size) <= _sg.wgpu.uniform.num_bytes); + SOKOL_ASSERT((_sg.wgpu.uniform.offset & (alignment - 1)) == 0); + const _sg_pipeline_t* pip = _sg_pipeline_ref_ptr(&_sg.cur_pip); + const _sg_shader_t* shd = _sg_shader_ref_ptr(&pip->cmn.shader); + SOKOL_ASSERT(data->size == shd->cmn.uniform_blocks[ub_slot].size); + SOKOL_ASSERT(data->size <= _SG_WGPU_MAX_UNIFORM_UPDATE_SIZE); + + _sg_stats_add(wgpu.uniforms.num_set_bindgroup, 1); + memcpy(_sg.wgpu.uniform.staging + _sg.wgpu.uniform.offset, data->ptr, data->size); + _sg.wgpu.uniform.bind_offsets[ub_slot] = _sg.wgpu.uniform.offset; + _sg.wgpu.uniform.offset = _sg_roundup_u32(_sg.wgpu.uniform.offset + (uint32_t)data->size, alignment); + + _sg_wgpu_set_ub_bindgroup(shd); +} + +_SOKOL_PRIVATE void _sg_wgpu_draw(int base_element, int num_elements, int num_instances, int base_vertex, int base_instance) { + SOKOL_ASSERT(_sg.wgpu.rpass_enc); + if (_sg.use_indexed_draw) { + wgpuRenderPassEncoderDrawIndexed(_sg.wgpu.rpass_enc, + (uint32_t)num_elements, + (uint32_t)num_instances, + (uint32_t)base_element, + base_vertex, + (uint32_t)base_instance); + } else { + wgpuRenderPassEncoderDraw(_sg.wgpu.rpass_enc, + (uint32_t)num_elements, + (uint32_t)num_instances, + (uint32_t)base_element, + (uint32_t)base_instance); + } +} + +_SOKOL_PRIVATE void _sg_wgpu_dispatch(int num_groups_x, int num_groups_y, int num_groups_z) { + SOKOL_ASSERT(_sg.wgpu.cpass_enc); + wgpuComputePassEncoderDispatchWorkgroups(_sg.wgpu.cpass_enc, + (uint32_t)num_groups_x, + (uint32_t)num_groups_y, + (uint32_t)num_groups_z); +} + +_SOKOL_PRIVATE void _sg_wgpu_update_buffer(_sg_buffer_t* buf, const sg_range* data) { + SOKOL_ASSERT(data && data->ptr && (data->size > 0)); + SOKOL_ASSERT(buf); + _sg_wgpu_copy_buffer_data(buf, 0, data); +} + +_SOKOL_PRIVATE void _sg_wgpu_append_buffer(_sg_buffer_t* buf, const sg_range* data, bool new_frame) { + SOKOL_ASSERT(data && data->ptr && (data->size > 0)); + _SOKOL_UNUSED(new_frame); + _sg_wgpu_copy_buffer_data(buf, (uint64_t)buf->cmn.append_pos, data); +} + +_SOKOL_PRIVATE void _sg_wgpu_update_image(_sg_image_t* img, const sg_image_data* data) { + SOKOL_ASSERT(img && data); + _sg_wgpu_copy_image_data(img, img->wgpu.tex, data); +} +#endif + +// ██████ ███████ ███ ██ ███████ ██████ ██ ██████ ██████ █████ ██████ ██ ██ ███████ ███ ██ ██████ +// ██ ██ ████ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██ ██ ██ +// ██ ███ █████ ██ ██ ██ █████ ██████ ██ ██ ██████ ███████ ██ █████ █████ ██ ██ ██ ██ ██ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██████ ███████ ██ ████ ███████ ██ ██ ██ ██████ ██████ ██ ██ ██████ ██ ██ ███████ ██ ████ ██████ +// +// >>generic backend +static inline void _sg_setup_backend(const sg_desc* desc) { + #if defined(_SOKOL_ANY_GL) + _sg_gl_setup_backend(desc); + #elif defined(SOKOL_METAL) + _sg_mtl_setup_backend(desc); + #elif defined(SOKOL_D3D11) + _sg_d3d11_setup_backend(desc); + #elif defined(SOKOL_WGPU) + _sg_wgpu_setup_backend(desc); + #elif defined(SOKOL_DUMMY_BACKEND) + _sg_dummy_setup_backend(desc); + #else + #error("INVALID BACKEND"); + #endif +} + +static inline void _sg_discard_backend(void) { + #if defined(_SOKOL_ANY_GL) + _sg_gl_discard_backend(); + #elif defined(SOKOL_METAL) + _sg_mtl_discard_backend(); + #elif defined(SOKOL_D3D11) + _sg_d3d11_discard_backend(); + #elif defined(SOKOL_WGPU) + _sg_wgpu_discard_backend(); + #elif defined(SOKOL_DUMMY_BACKEND) + _sg_dummy_discard_backend(); + #else + #error("INVALID BACKEND"); + #endif +} + +static inline void _sg_reset_state_cache(void) { + #if defined(_SOKOL_ANY_GL) + _sg_gl_reset_state_cache(); + #elif defined(SOKOL_METAL) + _sg_mtl_reset_state_cache(); + #elif defined(SOKOL_D3D11) + _sg_d3d11_reset_state_cache(); + #elif defined(SOKOL_WGPU) + _sg_wgpu_reset_state_cache(); + #elif defined(SOKOL_DUMMY_BACKEND) + _sg_dummy_reset_state_cache(); + #else + #error("INVALID BACKEND"); + #endif +} + +static inline sg_resource_state _sg_create_buffer(_sg_buffer_t* buf, const sg_buffer_desc* desc) { + #if defined(_SOKOL_ANY_GL) + return _sg_gl_create_buffer(buf, desc); + #elif defined(SOKOL_METAL) + return _sg_mtl_create_buffer(buf, desc); + #elif defined(SOKOL_D3D11) + return _sg_d3d11_create_buffer(buf, desc); + #elif defined(SOKOL_WGPU) + return _sg_wgpu_create_buffer(buf, desc); + #elif defined(SOKOL_DUMMY_BACKEND) + return _sg_dummy_create_buffer(buf, desc); + #else + #error("INVALID BACKEND"); + #endif +} + +static inline void _sg_discard_buffer(_sg_buffer_t* buf) { + #if defined(_SOKOL_ANY_GL) + _sg_gl_discard_buffer(buf); + #elif defined(SOKOL_METAL) + _sg_mtl_discard_buffer(buf); + #elif defined(SOKOL_D3D11) + _sg_d3d11_discard_buffer(buf); + #elif defined(SOKOL_WGPU) + _sg_wgpu_discard_buffer(buf); + #elif defined(SOKOL_DUMMY_BACKEND) + _sg_dummy_discard_buffer(buf); + #else + #error("INVALID BACKEND"); + #endif +} + +static inline sg_resource_state _sg_create_image(_sg_image_t* img, const sg_image_desc* desc) { + #if defined(_SOKOL_ANY_GL) + return _sg_gl_create_image(img, desc); + #elif defined(SOKOL_METAL) + return _sg_mtl_create_image(img, desc); + #elif defined(SOKOL_D3D11) + return _sg_d3d11_create_image(img, desc); + #elif defined(SOKOL_WGPU) + return _sg_wgpu_create_image(img, desc); + #elif defined(SOKOL_DUMMY_BACKEND) + return _sg_dummy_create_image(img, desc); + #else + #error("INVALID BACKEND"); + #endif +} + +static inline void _sg_discard_image(_sg_image_t* img) { + #if defined(_SOKOL_ANY_GL) + _sg_gl_discard_image(img); + #elif defined(SOKOL_METAL) + _sg_mtl_discard_image(img); + #elif defined(SOKOL_D3D11) + _sg_d3d11_discard_image(img); + #elif defined(SOKOL_WGPU) + _sg_wgpu_discard_image(img); + #elif defined(SOKOL_DUMMY_BACKEND) + _sg_dummy_discard_image(img); + #else + #error("INVALID BACKEND"); + #endif +} + +static inline sg_resource_state _sg_create_sampler(_sg_sampler_t* smp, const sg_sampler_desc* desc) { + #if defined(_SOKOL_ANY_GL) + return _sg_gl_create_sampler(smp, desc); + #elif defined(SOKOL_METAL) + return _sg_mtl_create_sampler(smp, desc); + #elif defined(SOKOL_D3D11) + return _sg_d3d11_create_sampler(smp, desc); + #elif defined(SOKOL_WGPU) + return _sg_wgpu_create_sampler(smp, desc); + #elif defined(SOKOL_DUMMY_BACKEND) + return _sg_dummy_create_sampler(smp, desc); + #else + #error("INVALID BACKEND"); + #endif +} + +static inline void _sg_discard_sampler(_sg_sampler_t* smp) { + #if defined(_SOKOL_ANY_GL) + _sg_gl_discard_sampler(smp); + #elif defined(SOKOL_METAL) + _sg_mtl_discard_sampler(smp); + #elif defined(SOKOL_D3D11) + _sg_d3d11_discard_sampler(smp); + #elif defined(SOKOL_WGPU) + _sg_wgpu_discard_sampler(smp); + #elif defined(SOKOL_DUMMY_BACKEND) + _sg_dummy_discard_sampler(smp); + #else + #error("INVALID BACKEND"); + #endif +} + +static inline sg_resource_state _sg_create_shader(_sg_shader_t* shd, const sg_shader_desc* desc) { + #if defined(_SOKOL_ANY_GL) + return _sg_gl_create_shader(shd, desc); + #elif defined(SOKOL_METAL) + return _sg_mtl_create_shader(shd, desc); + #elif defined(SOKOL_D3D11) + return _sg_d3d11_create_shader(shd, desc); + #elif defined(SOKOL_WGPU) + return _sg_wgpu_create_shader(shd, desc); + #elif defined(SOKOL_DUMMY_BACKEND) + return _sg_dummy_create_shader(shd, desc); + #else + #error("INVALID BACKEND"); + #endif +} + +static inline void _sg_discard_shader(_sg_shader_t* shd) { + #if defined(_SOKOL_ANY_GL) + _sg_gl_discard_shader(shd); + #elif defined(SOKOL_METAL) + _sg_mtl_discard_shader(shd); + #elif defined(SOKOL_D3D11) + _sg_d3d11_discard_shader(shd); + #elif defined(SOKOL_WGPU) + _sg_wgpu_discard_shader(shd); + #elif defined(SOKOL_DUMMY_BACKEND) + _sg_dummy_discard_shader(shd); + #else + #error("INVALID BACKEND"); + #endif +} + +static inline sg_resource_state _sg_create_pipeline(_sg_pipeline_t* pip, const sg_pipeline_desc* desc) { + #if defined(_SOKOL_ANY_GL) + return _sg_gl_create_pipeline(pip, desc); + #elif defined(SOKOL_METAL) + return _sg_mtl_create_pipeline(pip, desc); + #elif defined(SOKOL_D3D11) + return _sg_d3d11_create_pipeline(pip, desc); + #elif defined(SOKOL_WGPU) + return _sg_wgpu_create_pipeline(pip, desc); + #elif defined(SOKOL_DUMMY_BACKEND) + return _sg_dummy_create_pipeline(pip, desc); + #else + #error("INVALID BACKEND"); + #endif +} + +static inline void _sg_discard_pipeline(_sg_pipeline_t* pip) { + #if defined(_SOKOL_ANY_GL) + _sg_gl_discard_pipeline(pip); + #elif defined(SOKOL_METAL) + _sg_mtl_discard_pipeline(pip); + #elif defined(SOKOL_D3D11) + _sg_d3d11_discard_pipeline(pip); + #elif defined(SOKOL_WGPU) + _sg_wgpu_discard_pipeline(pip); + #elif defined(SOKOL_DUMMY_BACKEND) + _sg_dummy_discard_pipeline(pip); + #else + #error("INVALID BACKEND"); + #endif +} + +static inline sg_resource_state _sg_create_view(_sg_view_t* view, const sg_view_desc* desc) { + #if defined(_SOKOL_ANY_GL) + return _sg_gl_create_view(view, desc); + #elif defined(SOKOL_METAL) + return _sg_mtl_create_view(view, desc); + #elif defined(SOKOL_D3D11) + return _sg_d3d11_create_view(view, desc); + #elif defined(SOKOL_WGPU) + return _sg_wgpu_create_view(view, desc); + #elif defined(SOKOL_DUMMY_BACKEND) + return _sg_dummy_create_view(view, desc); + #else + #error("INVALID BACKEND"); + #endif +} + +static inline void _sg_discard_view(_sg_view_t* view) { + #if defined(_SOKOL_ANY_GL) + _sg_gl_discard_view(view); + #elif defined(SOKOL_METAL) + _sg_mtl_discard_view(view); + #elif defined(SOKOL_D3D11) + _sg_d3d11_discard_view(view); + #elif defined(SOKOL_WGPU) + _sg_wgpu_discard_view(view); + #elif defined(SOKOL_DUMMY_BACKEND) + _sg_dummy_discard_view(view); + #else + #error("INVALID BACKEND"); + #endif +} + +static inline void _sg_begin_pass(const sg_pass* pass, const _sg_attachments_ptrs_t* atts) { + #if defined(_SOKOL_ANY_GL) + _sg_gl_begin_pass(pass, atts); + #elif defined(SOKOL_METAL) + _sg_mtl_begin_pass(pass, atts); + #elif defined(SOKOL_D3D11) + _sg_d3d11_begin_pass(pass, atts); + #elif defined(SOKOL_WGPU) + _sg_wgpu_begin_pass(pass, atts); + #elif defined(SOKOL_DUMMY_BACKEND) + _sg_dummy_begin_pass(pass, atts); + #else + #error("INVALID BACKEND"); + #endif +} + +static inline void _sg_end_pass(const _sg_attachments_ptrs_t* atts) { + #if defined(_SOKOL_ANY_GL) + _sg_gl_end_pass(atts); + #elif defined(SOKOL_METAL) + _sg_mtl_end_pass(atts); + #elif defined(SOKOL_D3D11) + _sg_d3d11_end_pass(atts); + #elif defined(SOKOL_WGPU) + _sg_wgpu_end_pass(atts); + #elif defined(SOKOL_DUMMY_BACKEND) + _sg_dummy_end_pass(atts); + #else + #error("INVALID BACKEND"); + #endif +} + +static inline void _sg_apply_viewport(int x, int y, int w, int h, bool origin_top_left) { + #if defined(_SOKOL_ANY_GL) + _sg_gl_apply_viewport(x, y, w, h, origin_top_left); + #elif defined(SOKOL_METAL) + _sg_mtl_apply_viewport(x, y, w, h, origin_top_left); + #elif defined(SOKOL_D3D11) + _sg_d3d11_apply_viewport(x, y, w, h, origin_top_left); + #elif defined(SOKOL_WGPU) + _sg_wgpu_apply_viewport(x, y, w, h, origin_top_left); + #elif defined(SOKOL_DUMMY_BACKEND) + _sg_dummy_apply_viewport(x, y, w, h, origin_top_left); + #else + #error("INVALID BACKEND"); + #endif +} + +static inline void _sg_apply_scissor_rect(int x, int y, int w, int h, bool origin_top_left) { + #if defined(_SOKOL_ANY_GL) + _sg_gl_apply_scissor_rect(x, y, w, h, origin_top_left); + #elif defined(SOKOL_METAL) + _sg_mtl_apply_scissor_rect(x, y, w, h, origin_top_left); + #elif defined(SOKOL_D3D11) + _sg_d3d11_apply_scissor_rect(x, y, w, h, origin_top_left); + #elif defined(SOKOL_WGPU) + _sg_wgpu_apply_scissor_rect(x, y, w, h, origin_top_left); + #elif defined(SOKOL_DUMMY_BACKEND) + _sg_dummy_apply_scissor_rect(x, y, w, h, origin_top_left); + #else + #error("INVALID BACKEND"); + #endif +} + +static inline void _sg_apply_pipeline(_sg_pipeline_t* pip) { + #if defined(_SOKOL_ANY_GL) + _sg_gl_apply_pipeline(pip); + #elif defined(SOKOL_METAL) + _sg_mtl_apply_pipeline(pip); + #elif defined(SOKOL_D3D11) + _sg_d3d11_apply_pipeline(pip); + #elif defined(SOKOL_WGPU) + _sg_wgpu_apply_pipeline(pip); + #elif defined(SOKOL_DUMMY_BACKEND) + _sg_dummy_apply_pipeline(pip); + #else + #error("INVALID BACKEND"); + #endif +} + +static inline bool _sg_apply_bindings(_sg_bindings_ptrs_t* bnd) { + #if defined(_SOKOL_ANY_GL) + return _sg_gl_apply_bindings(bnd); + #elif defined(SOKOL_METAL) + return _sg_mtl_apply_bindings(bnd); + #elif defined(SOKOL_D3D11) + return _sg_d3d11_apply_bindings(bnd); + #elif defined(SOKOL_WGPU) + return _sg_wgpu_apply_bindings(bnd); + #elif defined(SOKOL_DUMMY_BACKEND) + return _sg_dummy_apply_bindings(bnd); + #else + #error("INVALID BACKEND"); + #endif +} + +static inline void _sg_apply_uniforms(int ub_slot, const sg_range* data) { + #if defined(_SOKOL_ANY_GL) + _sg_gl_apply_uniforms(ub_slot, data); + #elif defined(SOKOL_METAL) + _sg_mtl_apply_uniforms(ub_slot, data); + #elif defined(SOKOL_D3D11) + _sg_d3d11_apply_uniforms(ub_slot, data); + #elif defined(SOKOL_WGPU) + _sg_wgpu_apply_uniforms(ub_slot, data); + #elif defined(SOKOL_DUMMY_BACKEND) + _sg_dummy_apply_uniforms(ub_slot, data); + #else + #error("INVALID BACKEND"); + #endif +} + +static inline void _sg_draw(int base_element, int num_elements, int num_instances, int base_vertex, int base_index) { + #if defined(_SOKOL_ANY_GL) + _sg_gl_draw(base_element, num_elements, num_instances, base_vertex, base_index); + #elif defined(SOKOL_METAL) + _sg_mtl_draw(base_element, num_elements, num_instances, base_vertex, base_index); + #elif defined(SOKOL_D3D11) + _sg_d3d11_draw(base_element, num_elements, num_instances, base_vertex, base_index); + #elif defined(SOKOL_WGPU) + _sg_wgpu_draw(base_element, num_elements, num_instances, base_vertex, base_index); + #elif defined(SOKOL_DUMMY_BACKEND) + _sg_dummy_draw(base_element, num_elements, num_instances, base_vertex, base_index); + #else + #error("INVALID BACKEND"); + #endif +} + +static inline void _sg_dispatch(int num_groups_x, int num_groups_y, int num_groups_z) { + #if defined(_SOKOL_ANY_GL) + _sg_gl_dispatch(num_groups_x, num_groups_y, num_groups_z); + #elif defined(SOKOL_METAL) + _sg_mtl_dispatch(num_groups_x, num_groups_y, num_groups_z); + #elif defined(SOKOL_D3D11) + _sg_d3d11_dispatch(num_groups_x, num_groups_y, num_groups_z); + #elif defined(SOKOL_WGPU) + _sg_wgpu_dispatch(num_groups_x, num_groups_y, num_groups_z); + #elif defined(SOKOL_DUMMY_BACKEND) + _sg_dummy_dispatch(num_groups_x, num_groups_y, num_groups_z); + #else + #error("INVALID BACKEND"); + #endif +} + +static inline void _sg_commit(void) { + #if defined(_SOKOL_ANY_GL) + _sg_gl_commit(); + #elif defined(SOKOL_METAL) + _sg_mtl_commit(); + #elif defined(SOKOL_D3D11) + _sg_d3d11_commit(); + #elif defined(SOKOL_WGPU) + _sg_wgpu_commit(); + #elif defined(SOKOL_DUMMY_BACKEND) + _sg_dummy_commit(); + #else + #error("INVALID BACKEND"); + #endif +} + +static inline void _sg_update_buffer(_sg_buffer_t* buf, const sg_range* data) { + #if defined(_SOKOL_ANY_GL) + _sg_gl_update_buffer(buf, data); + #elif defined(SOKOL_METAL) + _sg_mtl_update_buffer(buf, data); + #elif defined(SOKOL_D3D11) + _sg_d3d11_update_buffer(buf, data); + #elif defined(SOKOL_WGPU) + _sg_wgpu_update_buffer(buf, data); + #elif defined(SOKOL_DUMMY_BACKEND) + _sg_dummy_update_buffer(buf, data); + #else + #error("INVALID BACKEND"); + #endif +} + +static inline void _sg_append_buffer(_sg_buffer_t* buf, const sg_range* data, bool new_frame) { + #if defined(_SOKOL_ANY_GL) + _sg_gl_append_buffer(buf, data, new_frame); + #elif defined(SOKOL_METAL) + _sg_mtl_append_buffer(buf, data, new_frame); + #elif defined(SOKOL_D3D11) + _sg_d3d11_append_buffer(buf, data, new_frame); + #elif defined(SOKOL_WGPU) + _sg_wgpu_append_buffer(buf, data, new_frame); + #elif defined(SOKOL_DUMMY_BACKEND) + _sg_dummy_append_buffer(buf, data, new_frame); + #else + #error("INVALID BACKEND"); + #endif +} + +static inline void _sg_update_image(_sg_image_t* img, const sg_image_data* data) { + #if defined(_SOKOL_ANY_GL) + _sg_gl_update_image(img, data); + #elif defined(SOKOL_METAL) + _sg_mtl_update_image(img, data); + #elif defined(SOKOL_D3D11) + _sg_d3d11_update_image(img, data); + #elif defined(SOKOL_WGPU) + _sg_wgpu_update_image(img, data); + #elif defined(SOKOL_DUMMY_BACKEND) + _sg_dummy_update_image(img, data); + #else + #error("INVALID BACKEND"); + #endif +} + +static inline void _sg_push_debug_group(const char* name) { + #if defined(SOKOL_METAL) + _sg_mtl_push_debug_group(name); + #else + _SOKOL_UNUSED(name); + #endif +} + +static inline void _sg_pop_debug_group(void) { + #if defined(SOKOL_METAL) + _sg_mtl_pop_debug_group(); + #endif +} + +// ██ ██ █████ ██ ██ ██████ █████ ████████ ██ ██████ ███ ██ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██ +// ██ ██ ███████ ██ ██ ██ ██ ███████ ██ ██ ██ ██ ██ ██ ██ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ████ ██ ██ ███████ ██ ██████ ██ ██ ██ ██ ██████ ██ ████ +// +// >>validation +#if defined(SOKOL_DEBUG) +_SOKOL_PRIVATE void _sg_validate_begin(void) { + _sg.validate_error = SG_LOGITEM_OK; +} + +_SOKOL_PRIVATE bool _sg_validate_end(void) { + if (_sg.validate_error != SG_LOGITEM_OK) { + #if !defined(SOKOL_VALIDATE_NON_FATAL) + _SG_PANIC(VALIDATION_FAILED); + return false; + #else + return false; + #endif + } else { + return true; + } +} +#endif + +_SOKOL_PRIVATE bool _sg_one(bool b0, bool b1, bool b2) { + return (b0 && !b1 && !b2) || (!b0 && b1 && !b2) || (!b0 && !b1 && b2); +} + +_SOKOL_PRIVATE bool _sg_validate_buffer_desc(const sg_buffer_desc* desc) { + #if !defined(SOKOL_DEBUG) + _SOKOL_UNUSED(desc); + return true; + #else + if (_sg.desc.disable_validation) { + return true; + } + SOKOL_ASSERT(desc); + _sg_validate_begin(); + _SG_VALIDATE(desc->_start_canary == 0, VALIDATE_BUFFERDESC_CANARY); + _SG_VALIDATE(desc->_end_canary == 0, VALIDATE_BUFFERDESC_CANARY); + _SG_VALIDATE(desc->size > 0, VALIDATE_BUFFERDESC_EXPECT_NONZERO_SIZE); + _SG_VALIDATE(_sg_one(desc->usage.immutable, desc->usage.dynamic_update, desc->usage.stream_update), VALIDATE_BUFFERDESC_IMMUTABLE_DYNAMIC_STREAM); + if (_sg.features.separate_buffer_types) { + _SG_VALIDATE(_sg_one(desc->usage.vertex_buffer, desc->usage.index_buffer, desc->usage.storage_buffer), VALIDATE_BUFFERDESC_SEPARATE_BUFFER_TYPES); + } + bool injected = (0 != desc->gl_buffers[0]) || + (0 != desc->mtl_buffers[0]) || + (0 != desc->d3d11_buffer) || + (0 != desc->wgpu_buffer); + if (!injected && desc->usage.immutable) { + if (desc->data.ptr) { + _SG_VALIDATE(desc->size == desc->data.size, VALIDATE_BUFFERDESC_EXPECT_MATCHING_DATA_SIZE); + } else { + _SG_VALIDATE(desc->usage.storage_buffer, VALIDATE_BUFFERDESC_EXPECT_DATA); + _SG_VALIDATE(desc->data.size == 0, VALIDATE_BUFFERDESC_EXPECT_ZERO_DATA_SIZE); + } + } else { + _SG_VALIDATE(0 == desc->data.ptr, VALIDATE_BUFFERDESC_EXPECT_NO_DATA); + _SG_VALIDATE(desc->data.size == 0, VALIDATE_BUFFERDESC_EXPECT_ZERO_DATA_SIZE); + } + if (desc->usage.storage_buffer) { + _SG_VALIDATE(_sg.features.compute, VALIDATE_BUFFERDESC_STORAGEBUFFER_SUPPORTED); + _SG_VALIDATE(_sg_multiple_u64(desc->size, 4), VALIDATE_BUFFERDESC_STORAGEBUFFER_SIZE_MULTIPLE_4); + } + return _sg_validate_end(); + #endif +} + +_SOKOL_PRIVATE void _sg_validate_image_data(const sg_image_data* data, sg_pixel_format fmt, int width, int height, int num_mips, int num_slices) { + #if !defined(SOKOL_DEBUG) + _SOKOL_UNUSED(data); + _SOKOL_UNUSED(fmt); + _SOKOL_UNUSED(width); + _SOKOL_UNUSED(height); + _SOKOL_UNUSED(num_mips); + _SOKOL_UNUSED(num_slices); + #else + for (int mip_index = 0; mip_index < num_mips; mip_index++) { + const bool has_data = data->mip_levels[mip_index].ptr != 0; + const bool has_size = data->mip_levels[mip_index].size > 0; + _SG_VALIDATE(has_data && has_size, VALIDATE_IMAGEDATA_NODATA); + const int mip_width = _sg_miplevel_dim(width, mip_index); + const int mip_height = _sg_miplevel_dim(height, mip_index); + const int bytes_per_slice = _sg_surface_pitch(fmt, mip_width, mip_height, 1); + const int expected_size = bytes_per_slice * num_slices; + _SG_VALIDATE(expected_size == (int)data->mip_levels[mip_index].size, VALIDATE_IMAGEDATA_DATA_SIZE); + } + #endif +} + +_SOKOL_PRIVATE bool _sg_validate_image_desc(const sg_image_desc* desc) { + #if !defined(SOKOL_DEBUG) + _SOKOL_UNUSED(desc); + return true; + #else + if (_sg.desc.disable_validation) { + return true; + } + const sg_image_usage* usg = &desc->usage; + const bool any_attachment = usg->color_attachment || usg->resolve_attachment || usg->depth_stencil_attachment; + SOKOL_ASSERT(desc); + _sg_validate_begin(); + _SG_VALIDATE(desc->_start_canary == 0, VALIDATE_IMAGEDESC_CANARY); + _SG_VALIDATE(desc->_end_canary == 0, VALIDATE_IMAGEDESC_CANARY); + _SG_VALIDATE(_sg_one(usg->immutable, usg->dynamic_update, usg->stream_update), VALIDATE_IMAGEDESC_IMMUTABLE_DYNAMIC_STREAM); + switch (desc->type) { + case SG_IMAGETYPE_2D: + _SG_VALIDATE(desc->num_slices == 1, VALIDATE_IMAGEDESC_IMAGETYPE_2D_NUMSLICES); + break; + case SG_IMAGETYPE_CUBE: + _SG_VALIDATE(desc->num_slices == 6, VALIDATE_IMAGEDESC_IMAGETYPE_CUBE_NUMSLICES); + break; + case SG_IMAGETYPE_ARRAY: + _SG_VALIDATE((desc->num_slices >= 1) && (desc->num_slices <= _sg.limits.max_image_array_layers), VALIDATE_IMAGEDESC_IMAGETYPE_ARRAY_NUMSLICES); + break; + case SG_IMAGETYPE_3D: + _SG_VALIDATE((desc->num_slices >= 1) && (desc->num_slices <= _sg.limits.max_image_size_3d), VALIDATE_IMAGEDESC_IMAGETYPE_3D_NUMSLICES); + break; + default: + SOKOL_UNREACHABLE; + break; + } + _SG_VALIDATE(desc->width > 0, VALIDATE_IMAGEDESC_WIDTH); + _SG_VALIDATE(desc->height > 0, VALIDATE_IMAGEDESC_HEIGHT); + const sg_pixel_format fmt = desc->pixel_format; + const bool injected = (0 != desc->gl_textures[0]) || + (0 != desc->mtl_textures[0]) || + (0 != desc->d3d11_texture) || + (0 != desc->wgpu_texture); + if (_sg_is_depth_or_depth_stencil_format(fmt)) { + _SG_VALIDATE(desc->type != SG_IMAGETYPE_3D, VALIDATE_IMAGEDESC_DEPTH_3D_IMAGE); + } + if (any_attachment || usg->storage_image) { + SOKOL_ASSERT(((int)fmt >= 0) && ((int)fmt < _SG_PIXELFORMAT_NUM)); + _SG_VALIDATE(usg->immutable, VALIDATE_IMAGEDESC_ATTACHMENT_EXPECT_IMMUTABLE); + _SG_VALIDATE(desc->data.mip_levels[0].ptr==0, VALIDATE_IMAGEDESC_ATTACHMENT_EXPECT_NO_DATA); + if (any_attachment) { + _SG_VALIDATE(_sg.formats[fmt].render, VALIDATE_IMAGEDESC_ATTACHMENT_PIXELFORMAT); + if (usg->resolve_attachment) { + _SG_VALIDATE(desc->sample_count == 1, VALIDATE_IMAGEDESC_ATTACHMENT_RESOLVE_EXPECT_NO_MSAA); + } + if (desc->sample_count > 1) { + _SG_VALIDATE(_sg.formats[fmt].msaa, VALIDATE_IMAGEDESC_ATTACHMENT_NO_MSAA_SUPPORT); + _SG_VALIDATE(desc->num_mipmaps == 1, VALIDATE_IMAGEDESC_ATTACHMENT_MSAA_NUM_MIPMAPS); + _SG_VALIDATE(desc->type != SG_IMAGETYPE_ARRAY, VALIDATE_IMAGEDESC_ATTACHMENT_MSAA_ARRAY_IMAGE); + _SG_VALIDATE(desc->type != SG_IMAGETYPE_3D, VALIDATE_IMAGEDESC_ATTACHMENT_MSAA_3D_IMAGE); + _SG_VALIDATE(desc->type != SG_IMAGETYPE_CUBE, VALIDATE_IMAGEDESC_ATTACHMENT_MSAA_CUBE_IMAGE); + } + } else if (usg->storage_image) { + _SG_VALIDATE(_sg_is_valid_storage_image_format(fmt), VALIDATE_IMAGEDESC_STORAGEIMAGE_PIXELFORMAT); + // D3D11 doesn't allow multisampled UAVs (see: https://github.com/gpuweb/gpuweb/issues/513) + _SG_VALIDATE(desc->sample_count == 1, VALIDATE_IMAGEDESC_STORAGEIMAGE_EXPECT_NO_MSAA); + } + } else { + _SG_VALIDATE(desc->sample_count == 1, VALIDATE_IMAGEDESC_MSAA_BUT_NO_ATTACHMENT); + const bool valid_nonrt_fmt = !_sg_is_valid_attachment_depth_format(fmt); + _SG_VALIDATE(valid_nonrt_fmt, VALIDATE_IMAGEDESC_NONRT_PIXELFORMAT); + const bool is_compressed = _sg_is_compressed_pixel_format(desc->pixel_format); + if (is_compressed) { + _SG_VALIDATE(usg->immutable, VALIDATE_IMAGEDESC_COMPRESSED_IMMUTABLE); + } + if (!injected && usg->immutable) { + // image desc must have valid data + _sg_validate_image_data(&desc->data, + desc->pixel_format, + desc->width, + desc->height, + desc->num_mipmaps, + desc->num_slices); + } else { + // image desc must not have data + for (int mip_index = 0; mip_index < SG_MAX_MIPMAPS; mip_index++) { + const bool no_data = 0 == desc->data.mip_levels[mip_index].ptr; + const bool no_size = 0 == desc->data.mip_levels[mip_index].size; + if (injected) { + _SG_VALIDATE(no_data && no_size, VALIDATE_IMAGEDESC_INJECTED_NO_DATA); + } + if (!usg->immutable) { + _SG_VALIDATE(no_data && no_size, VALIDATE_IMAGEDESC_DYNAMIC_NO_DATA); + } + } + } + } + return _sg_validate_end(); + #endif +} + +_SOKOL_PRIVATE bool _sg_validate_sampler_desc(const sg_sampler_desc* desc) { + #if !defined(SOKOL_DEBUG) + _SOKOL_UNUSED(desc); + return true; + #else + if (_sg.desc.disable_validation) { + return true; + } + SOKOL_ASSERT(desc); + _sg_validate_begin(); + _SG_VALIDATE(desc->_start_canary == 0, VALIDATE_SAMPLERDESC_CANARY); + _SG_VALIDATE(desc->_end_canary == 0, VALIDATE_SAMPLERDESC_CANARY); + // restriction from WebGPU: when anisotropy > 1, all filters must be linear + if (desc->max_anisotropy > 1) { + _SG_VALIDATE((desc->min_filter == SG_FILTER_LINEAR) + && (desc->mag_filter == SG_FILTER_LINEAR) + && (desc->mipmap_filter == SG_FILTER_LINEAR), + VALIDATE_SAMPLERDESC_ANISTROPIC_REQUIRES_LINEAR_FILTERING); + } + return _sg_validate_end(); + #endif +} + +typedef struct { + uint64_t lo, hi; +} _sg_u128_t; + +_SOKOL_PRIVATE _sg_u128_t _sg_u128(void) { + _sg_u128_t res; + _sg_clear(&res, sizeof(res)); + return res; +} + +_SOKOL_PRIVATE _sg_u128_t _sg_validate_set_slot_bit(_sg_u128_t bits, sg_shader_stage stage, uint8_t slot) { + switch (stage) { + case SG_SHADERSTAGE_NONE: + SOKOL_ASSERT(slot < 128); + if (slot < 64) { + bits.lo |= 1ULL << slot; + } else { + bits.hi |= 1ULL << (slot - 64); + } + break; + case SG_SHADERSTAGE_VERTEX: + SOKOL_ASSERT(slot < 64); + bits.lo |= 1ULL << slot; + break; + case SG_SHADERSTAGE_FRAGMENT: + SOKOL_ASSERT(slot < 64); + bits.hi |= 1ULL << slot; + break; + case SG_SHADERSTAGE_COMPUTE: + SOKOL_ASSERT(slot < 64); + bits.lo |= 1ULL << slot; + break; + default: + SOKOL_UNREACHABLE; + break; + } + return bits; +} + +_SOKOL_PRIVATE bool _sg_validate_slot_bits(_sg_u128_t bits, sg_shader_stage stage, uint8_t slot) { + _sg_u128_t mask = _sg_u128(); + switch (stage) { + case SG_SHADERSTAGE_NONE: + SOKOL_ASSERT(slot < 128); + if (slot < 64) { + mask.lo = 1ULL << slot; + } else { + mask.hi = 1ULL << (slot - 64); + } + break; + case SG_SHADERSTAGE_VERTEX: + SOKOL_ASSERT(slot < 64); + mask.lo = 1ULL << slot; + break; + case SG_SHADERSTAGE_FRAGMENT: + SOKOL_ASSERT(slot < 64); + mask.hi = 1ULL << slot; + break; + case SG_SHADERSTAGE_COMPUTE: + SOKOL_ASSERT(slot < 64); + mask.lo = 1ULL << slot; + break; + default: + SOKOL_UNREACHABLE; + break; + } + return ((bits.lo & mask.lo) == 0) && ((bits.hi & mask.hi) == 0); +} + +_SOKOL_PRIVATE bool _sg_validate_shader_desc(const sg_shader_desc* desc) { + #if !defined(SOKOL_DEBUG) + _SOKOL_UNUSED(desc); + return true; + #else + if (_sg.desc.disable_validation) { + return true; + } + SOKOL_ASSERT(desc); + bool is_compute_shader = (desc->compute_func.source != 0) || (desc->compute_func.bytecode.ptr != 0); + _sg_validate_begin(); + _SG_VALIDATE(desc->_start_canary == 0, VALIDATE_SHADERDESC_CANARY); + _SG_VALIDATE(desc->_end_canary == 0, VALIDATE_SHADERDESC_CANARY); + #if defined(SOKOL_GLCORE) || defined(SOKOL_GLES3) || defined(SOKOL_WGPU) + // on GL or WebGPU, must provide shader source code + if (is_compute_shader) { + _SG_VALIDATE(0 != desc->compute_func.source, VALIDATE_SHADERDESC_COMPUTE_SOURCE); + } else { + _SG_VALIDATE(0 != desc->vertex_func.source, VALIDATE_SHADERDESC_VERTEX_SOURCE); + _SG_VALIDATE(0 != desc->fragment_func.source, VALIDATE_SHADERDESC_FRAGMENT_SOURCE); + } + #elif defined(SOKOL_METAL) || defined(SOKOL_D3D11) + // on Metal or D3D11, must provide shader source code or byte code + if (is_compute_shader) { + _SG_VALIDATE((0 != desc->compute_func.source) || (0 != desc->compute_func.bytecode.ptr), VALIDATE_SHADERDESC_COMPUTE_SOURCE_OR_BYTECODE); + } else { + _SG_VALIDATE((0 != desc->vertex_func.source)|| (0 != desc->vertex_func.bytecode.ptr), VALIDATE_SHADERDESC_VERTEX_SOURCE_OR_BYTECODE); + _SG_VALIDATE((0 != desc->fragment_func.source) || (0 != desc->fragment_func.bytecode.ptr), VALIDATE_SHADERDESC_FRAGMENT_SOURCE_OR_BYTECODE); + } + #else + // Dummy Backend, don't require source or bytecode + #endif + if (is_compute_shader) { + _SG_VALIDATE((0 == desc->vertex_func.source) && (0 == desc->vertex_func.bytecode.ptr), VALIDATE_SHADERDESC_INVALID_SHADER_COMBO); + _SG_VALIDATE((0 == desc->fragment_func.source) && (0 == desc->fragment_func.bytecode.ptr), VALIDATE_SHADERDESC_INVALID_SHADER_COMBO); + } else { + _SG_VALIDATE((0 == desc->compute_func.source) && (0 == desc->compute_func.bytecode.ptr), VALIDATE_SHADERDESC_INVALID_SHADER_COMBO); + } + #if defined(SOKOL_METAL) + if (is_compute_shader) { + int x = desc->mtl_threads_per_threadgroup.x; + int y = desc->mtl_threads_per_threadgroup.y; + int z = desc->mtl_threads_per_threadgroup.z; + _SG_VALIDATE((x > 0) && (y > 0) && (z > 0), VALIDATE_SHADERDESC_METAL_THREADS_PER_THREADGROUP_INITIALIZED); + _SG_VALIDATE(((x * y * z) & 31) == 0, VALIDATE_SHADERDESC_METAL_THREADS_PER_THREADGROUP_MULTIPLE_32); + } + #endif + for (size_t i = 0; i < SG_MAX_VERTEX_ATTRIBUTES; i++) { + if (desc->attrs[i].glsl_name) { + _SG_VALIDATE(strlen(desc->attrs[i].glsl_name) < _SG_STRING_SIZE, VALIDATE_SHADERDESC_ATTR_STRING_TOO_LONG); + } + if (desc->attrs[i].hlsl_sem_name) { + _SG_VALIDATE(strlen(desc->attrs[i].hlsl_sem_name) < _SG_STRING_SIZE, VALIDATE_SHADERDESC_ATTR_STRING_TOO_LONG); + } + } + // if shader byte code, the size must also be provided + if (0 != desc->vertex_func.bytecode.ptr) { + _SG_VALIDATE(desc->vertex_func.bytecode.size > 0, VALIDATE_SHADERDESC_NO_BYTECODE_SIZE); + } + if (0 != desc->fragment_func.bytecode.ptr) { + _SG_VALIDATE(desc->fragment_func.bytecode.size > 0, VALIDATE_SHADERDESC_NO_BYTECODE_SIZE); + } + if (0 != desc->compute_func.bytecode.ptr) { + _SG_VALIDATE(desc->compute_func.bytecode.size > 0, VALIDATE_SHADERDESC_NO_BYTECODE_SIZE); + } + + #if defined(SOKOL_METAL) + _sg_u128_t msl_buf_bits = _sg_u128(); + _sg_u128_t msl_tex_bits = _sg_u128(); + _sg_u128_t msl_smp_bits = _sg_u128(); + #elif defined(SOKOL_D3D11) + _sg_u128_t hlsl_buf_bits = _sg_u128(); + _sg_u128_t hlsl_srv_bits = _sg_u128(); + _sg_u128_t hlsl_uav_bits = _sg_u128(); + _sg_u128_t hlsl_smp_bits = _sg_u128(); + #elif defined(_SOKOL_ANY_GL) + _sg_u128_t glsl_sbuf_bnd_bits = _sg_u128(); + _sg_u128_t glsl_simg_bnd_bits = _sg_u128(); + #elif defined(SOKOL_WGPU) + _sg_u128_t wgsl_group0_bits = _sg_u128(); + _sg_u128_t wgsl_group1_bits = _sg_u128(); + #endif + for (size_t ub_idx = 0; ub_idx < SG_MAX_UNIFORMBLOCK_BINDSLOTS; ub_idx++) { + const sg_shader_uniform_block* ub_desc = &desc->uniform_blocks[ub_idx]; + if (ub_desc->stage == SG_SHADERSTAGE_NONE) { + continue; + } + _SG_VALIDATE(ub_desc->size > 0, VALIDATE_SHADERDESC_UNIFORMBLOCK_SIZE_IS_ZERO); + #if defined(SOKOL_METAL) + _SG_VALIDATE(_sg_validate_slot_bits(msl_buf_bits, ub_desc->stage, ub_desc->msl_buffer_n), VALIDATE_SHADERDESC_UNIFORMBLOCK_METAL_BUFFER_SLOT_COLLISION); + msl_buf_bits = _sg_validate_set_slot_bit(msl_buf_bits, ub_desc->stage, ub_desc->msl_buffer_n); + #elif defined(SOKOL_D3D11) + _SG_VALIDATE(_sg_validate_slot_bits(hlsl_buf_bits, ub_desc->stage, ub_desc->hlsl_register_b_n), VALIDATE_SHADERDESC_UNIFORMBLOCK_HLSL_REGISTER_B_COLLISION); + hlsl_buf_bits = _sg_validate_set_slot_bit(hlsl_buf_bits, ub_desc->stage, ub_desc->hlsl_register_b_n); + #elif defined(SOKOL_WGPU) + _SG_VALIDATE(_sg_validate_slot_bits(wgsl_group0_bits, SG_SHADERSTAGE_NONE, ub_desc->wgsl_group0_binding_n), VALIDATE_SHADERDESC_UNIFORMBLOCK_WGSL_GROUP0_BINDING_COLLISION); + wgsl_group0_bits = _sg_validate_set_slot_bit(wgsl_group0_bits, SG_SHADERSTAGE_NONE, ub_desc->wgsl_group0_binding_n); + #endif + #if defined(_SOKOL_ANY_GL) + bool uniforms_continuous = true; + uint32_t uniform_offset = 0; + int num_uniforms = 0; + for (size_t u_index = 0; u_index < SG_MAX_UNIFORMBLOCK_MEMBERS; u_index++) { + const sg_glsl_shader_uniform* u_desc = &ub_desc->glsl_uniforms[u_index]; + if (u_desc->type != SG_UNIFORMTYPE_INVALID) { + _SG_VALIDATE(uniforms_continuous, VALIDATE_SHADERDESC_UNIFORMBLOCK_NO_CONT_MEMBERS); + _SG_VALIDATE(u_desc->glsl_name, VALIDATE_SHADERDESC_UNIFORMBLOCK_UNIFORM_GLSL_NAME); + const int array_count = u_desc->array_count; + _SG_VALIDATE(array_count > 0, VALIDATE_SHADERDESC_UNIFORMBLOCK_ARRAY_COUNT); + const uint32_t u_align = _sg_uniform_alignment(u_desc->type, array_count, ub_desc->layout); + const uint32_t u_size = _sg_uniform_size(u_desc->type, array_count, ub_desc->layout); + uniform_offset = _sg_align_u32(uniform_offset, u_align); + uniform_offset += u_size; + num_uniforms++; + // with std140, arrays are only allowed for FLOAT4, INT4, MAT4 + if (ub_desc->layout == SG_UNIFORMLAYOUT_STD140) { + if (array_count > 1) { + _SG_VALIDATE((u_desc->type == SG_UNIFORMTYPE_FLOAT4) || (u_desc->type == SG_UNIFORMTYPE_INT4) || (u_desc->type == SG_UNIFORMTYPE_MAT4), VALIDATE_SHADERDESC_UNIFORMBLOCK_STD140_ARRAY_TYPE); + } + } + } else { + uniforms_continuous = false; + } + } + if (ub_desc->layout == SG_UNIFORMLAYOUT_STD140) { + uniform_offset = _sg_align_u32(uniform_offset, 16); + } + _SG_VALIDATE((size_t)uniform_offset == ub_desc->size, VALIDATE_SHADERDESC_UNIFORMBLOCK_SIZE_MISMATCH); + _SG_VALIDATE(num_uniforms > 0, VALIDATE_SHADERDESC_UNIFORMBLOCK_NO_MEMBERS); + #endif + } + + uint32_t texview_slot_mask = 0; + for (size_t view_idx = 0; view_idx < SG_MAX_VIEW_BINDSLOTS; view_idx++) { + const sg_shader_view* view_desc = &desc->views[view_idx]; + if (view_desc->texture.stage != SG_SHADERSTAGE_NONE) { + const sg_shader_texture_view* tex_desc = &view_desc->texture; + texview_slot_mask |= (1 << view_idx); + #if defined(SOKOL_METAL) + _SG_VALIDATE(_sg_validate_slot_bits(msl_tex_bits, tex_desc->stage, tex_desc->msl_texture_n), VALIDATE_SHADERDESC_VIEW_TEXTURE_METAL_TEXTURE_SLOT_COLLISION); + msl_tex_bits = _sg_validate_set_slot_bit(msl_tex_bits, tex_desc->stage, tex_desc->msl_texture_n); + #elif defined(SOKOL_D3D11) + _SG_VALIDATE(_sg_validate_slot_bits(hlsl_srv_bits, tex_desc->stage, tex_desc->hlsl_register_t_n), VALIDATE_SHADERDESC_VIEW_TEXTURE_HLSL_REGISTER_T_COLLISION); + hlsl_srv_bits = _sg_validate_set_slot_bit(hlsl_srv_bits, tex_desc->stage, tex_desc->hlsl_register_t_n); + #elif defined(SOKOL_WGPU) + _SG_VALIDATE(_sg_validate_slot_bits(wgsl_group1_bits, SG_SHADERSTAGE_NONE, tex_desc->wgsl_group1_binding_n), VALIDATE_SHADERDESC_VIEW_TEXTURE_WGSL_GROUP1_BINDING_COLLISION); + wgsl_group1_bits = _sg_validate_set_slot_bit(wgsl_group1_bits, SG_SHADERSTAGE_NONE, tex_desc->wgsl_group1_binding_n); + #elif defined(SOKOL_DUMMY_BACKEND) || defined(_SOKOL_ANY_GL) + _SOKOL_UNUSED(tex_desc); + #endif + } else if (view_desc->storage_buffer.stage != SG_SHADERSTAGE_NONE) { + const sg_shader_storage_buffer_view* sbuf_desc = &view_desc->storage_buffer; + #if defined(SOKOL_METAL) + _SG_VALIDATE(_sg_validate_slot_bits(msl_buf_bits, sbuf_desc->stage, sbuf_desc->msl_buffer_n), VALIDATE_SHADERDESC_VIEW_STORAGEBUFFER_METAL_BUFFER_SLOT_COLLISION); + msl_buf_bits = _sg_validate_set_slot_bit(msl_buf_bits, sbuf_desc->stage, sbuf_desc->msl_buffer_n); + #elif defined(SOKOL_D3D11) + if (sbuf_desc->readonly) { + _SG_VALIDATE(_sg_validate_slot_bits(hlsl_srv_bits, sbuf_desc->stage, sbuf_desc->hlsl_register_t_n), VALIDATE_SHADERDESC_VIEW_STORAGEBUFFER_HLSL_REGISTER_T_COLLISION); + hlsl_srv_bits = _sg_validate_set_slot_bit(hlsl_srv_bits, sbuf_desc->stage, sbuf_desc->hlsl_register_t_n); + } else { + _SG_VALIDATE(_sg_validate_slot_bits(hlsl_uav_bits, sbuf_desc->stage, sbuf_desc->hlsl_register_u_n), VALIDATE_SHADERDESC_VIEW_STORAGEBUFFER_HLSL_REGISTER_U_COLLISION); + hlsl_uav_bits = _sg_validate_set_slot_bit(hlsl_uav_bits, sbuf_desc->stage, sbuf_desc->hlsl_register_u_n); + } + #elif defined(_SOKOL_ANY_GL) + _SG_VALIDATE(_sg_validate_slot_bits(glsl_sbuf_bnd_bits, SG_SHADERSTAGE_NONE, sbuf_desc->glsl_binding_n), VALIDATE_SHADERDESC_VIEW_STORAGEBUFFER_GLSL_BINDING_COLLISION); + glsl_sbuf_bnd_bits = _sg_validate_set_slot_bit(glsl_sbuf_bnd_bits, SG_SHADERSTAGE_NONE, sbuf_desc->glsl_binding_n); + #elif defined(SOKOL_WGPU) + _SG_VALIDATE(_sg_validate_slot_bits(wgsl_group1_bits, SG_SHADERSTAGE_NONE, sbuf_desc->wgsl_group1_binding_n), VALIDATE_SHADERDESC_VIEW_STORAGEBUFFER_WGSL_GROUP1_BINDING_COLLISION); + wgsl_group1_bits = _sg_validate_set_slot_bit(wgsl_group1_bits, SG_SHADERSTAGE_NONE, sbuf_desc->wgsl_group1_binding_n); + #elif defined(SOKOL_DUMMY_BACKEND) + _SOKOL_UNUSED(sbuf_desc); + #endif + } else if (view_desc->storage_image.stage != SG_SHADERSTAGE_NONE) { + const sg_shader_storage_image_view* simg_desc = &view_desc->storage_image; + _SG_VALIDATE(simg_desc->stage == SG_SHADERSTAGE_COMPUTE, VALIDATE_SHADERDESC_VIEW_STORAGEIMAGE_EXPECT_COMPUTE_STAGE); + #if defined(SOKOL_METAL) + _SG_VALIDATE(_sg_validate_slot_bits(msl_tex_bits, simg_desc->stage, simg_desc->msl_texture_n), VALIDATE_SHADERDESC_VIEW_STORAGEIMAGE_METAL_TEXTURE_SLOT_COLLISION); + msl_tex_bits = _sg_validate_set_slot_bit(msl_tex_bits, simg_desc->stage, simg_desc->msl_texture_n); + #elif defined(SOKOL_D3D11) + _SG_VALIDATE(_sg_validate_slot_bits(hlsl_uav_bits, simg_desc->stage, simg_desc->hlsl_register_u_n), VALIDATE_SHADERDESC_VIEW_STORAGEIMAGE_HLSL_REGISTER_U_COLLISION); + hlsl_uav_bits = _sg_validate_set_slot_bit(hlsl_uav_bits, simg_desc->stage, simg_desc->hlsl_register_u_n); + #elif defined(_SOKOL_ANY_GL) + _SG_VALIDATE(_sg_validate_slot_bits(glsl_simg_bnd_bits, SG_SHADERSTAGE_NONE, simg_desc->glsl_binding_n), VALIDATE_SHADERDESC_VIEW_STORAGEIMAGE_GLSL_BINDING_COLLISION); + glsl_simg_bnd_bits = _sg_validate_set_slot_bit(glsl_simg_bnd_bits, SG_SHADERSTAGE_NONE, simg_desc->glsl_binding_n); + #elif defined(SOKOL_WGPU) + _SG_VALIDATE(_sg_validate_slot_bits(wgsl_group1_bits, SG_SHADERSTAGE_NONE, simg_desc->wgsl_group1_binding_n), VALIDATE_SHADERDESC_VIEW_STORAGEIMAGE_WGSL_GROUP1_BINDING_COLLISION); + wgsl_group1_bits = _sg_validate_set_slot_bit(wgsl_group1_bits, SG_SHADERSTAGE_NONE, simg_desc->wgsl_group1_binding_n); + #endif + } + } + + uint32_t smp_slot_mask = 0; + for (size_t smp_idx = 0; smp_idx < SG_MAX_SAMPLER_BINDSLOTS; smp_idx++) { + const sg_shader_sampler* smp_desc = &desc->samplers[smp_idx]; + if (smp_desc->stage == SG_SHADERSTAGE_NONE) { + continue; + } + smp_slot_mask |= (1 << smp_idx); + #if defined(SOKOL_METAL) + _SG_VALIDATE(_sg_validate_slot_bits(msl_smp_bits, smp_desc->stage, smp_desc->msl_sampler_n), VALIDATE_SHADERDESC_SAMPLER_METAL_SAMPLER_SLOT_COLLISION); + msl_smp_bits = _sg_validate_set_slot_bit(msl_smp_bits, smp_desc->stage, smp_desc->msl_sampler_n); + #elif defined(SOKOL_D3D11) + _SG_VALIDATE(_sg_validate_slot_bits(hlsl_smp_bits, smp_desc->stage, smp_desc->hlsl_register_s_n), VALIDATE_SHADERDESC_SAMPLER_HLSL_REGISTER_S_COLLISION); + hlsl_smp_bits = _sg_validate_set_slot_bit(hlsl_smp_bits, smp_desc->stage, smp_desc->hlsl_register_s_n); + #elif defined(SOKOL_WGPU) + _SG_VALIDATE(_sg_validate_slot_bits(wgsl_group1_bits, SG_SHADERSTAGE_NONE, smp_desc->wgsl_group1_binding_n), VALIDATE_SHADERDESC_SAMPLER_WGSL_GROUP1_BINDING_COLLISION); + wgsl_group1_bits = _sg_validate_set_slot_bit(wgsl_group1_bits, SG_SHADERSTAGE_NONE, smp_desc->wgsl_group1_binding_n); + #endif + } + + uint32_t ref_texview_slot_mask = 0; + uint32_t ref_smp_slot_mask = 0; + for (size_t tex_smp_idx = 0; tex_smp_idx < SG_MAX_TEXTURE_SAMPLER_PAIRS; tex_smp_idx++) { + const sg_shader_texture_sampler_pair* tex_smp_desc = &desc->texture_sampler_pairs[tex_smp_idx]; + if (tex_smp_desc->stage == SG_SHADERSTAGE_NONE) { + continue; + } + #if defined(_SOKOL_ANY_GL) + _SG_VALIDATE(tex_smp_desc->glsl_name != 0, VALIDATE_SHADERDESC_TEXTURE_SAMPLER_PAIR_GLSL_NAME); + #endif + const bool view_slot_in_range = tex_smp_desc->view_slot < SG_MAX_VIEW_BINDSLOTS; + const bool smp_slot_in_range = tex_smp_desc->sampler_slot < SG_MAX_SAMPLER_BINDSLOTS; + _SG_VALIDATE(view_slot_in_range, VALIDATE_SHADERDESC_TEXTURE_SAMPLER_PAIR_VIEW_SLOT_OUT_OF_RANGE); + _SG_VALIDATE(smp_slot_in_range, VALIDATE_SHADERDESC_TEXTURE_SAMPLER_PAIR_SAMPLER_SLOT_OUT_OF_RANGE); + if (view_slot_in_range && smp_slot_in_range) { + ref_texview_slot_mask |= 1 << tex_smp_desc->view_slot; + ref_smp_slot_mask |= 1 << tex_smp_desc->sampler_slot; + const sg_shader_view* view_desc = &desc->views[tex_smp_desc->view_slot]; + const sg_shader_sampler* smp_desc = &desc->samplers[tex_smp_desc->sampler_slot]; + _SG_VALIDATE(view_desc->texture.stage != SG_SHADERSTAGE_NONE, VALIDATE_SHADERDESC_TEXTURE_SAMPLER_PAIR_EXPECT_TEXTURE_VIEW); + _SG_VALIDATE(view_desc->texture.stage == tex_smp_desc->stage, VALIDATE_SHADERDESC_TEXTURE_SAMPLER_PAIR_TEXTURE_STAGE_MISMATCH); + _SG_VALIDATE(smp_desc->stage == tex_smp_desc->stage, VALIDATE_SHADERDESC_TEXTURE_SAMPLER_PAIR_SAMPLER_STAGE_MISMATCH); + const bool needs_nonfiltering = (view_desc->texture.sample_type == SG_IMAGESAMPLETYPE_UINT) + || (view_desc->texture.sample_type == SG_IMAGESAMPLETYPE_SINT) + || (view_desc->texture.sample_type == SG_IMAGESAMPLETYPE_UNFILTERABLE_FLOAT); + const bool needs_comparison = view_desc->texture.sample_type == SG_IMAGESAMPLETYPE_DEPTH; + if (needs_nonfiltering) { + _SG_VALIDATE(needs_nonfiltering && (smp_desc->sampler_type == SG_SAMPLERTYPE_NONFILTERING), VALIDATE_SHADERDESC_NONFILTERING_SAMPLER_REQUIRED); + } + if (needs_comparison) { + _SG_VALIDATE(needs_comparison && (smp_desc->sampler_type == SG_SAMPLERTYPE_COMPARISON), VALIDATE_SHADERDESC_COMPARISON_SAMPLER_REQUIRED); + } + } + } + // each image and sampler must be referenced by an image sampler + _SG_VALIDATE(texview_slot_mask == ref_texview_slot_mask, VALIDATE_SHADERDESC_TEXVIEW_NOT_REFERENCED_BY_TEXTURE_SAMPLER_PAIRS); + _SG_VALIDATE(smp_slot_mask == ref_smp_slot_mask, VALIDATE_SHADERDESC_SAMPLER_NOT_REFERENCED_BY_TEXTURE_SAMPLER_PAIRS); + + return _sg_validate_end(); + #endif +} + +_SOKOL_PRIVATE bool _sg_validate_pipeline_desc(const sg_pipeline_desc* desc) { + #if !defined(SOKOL_DEBUG) + _SOKOL_UNUSED(desc); + return true; + #else + if (_sg.desc.disable_validation) { + return true; + } + SOKOL_ASSERT(desc); + _sg_validate_begin(); + _SG_VALIDATE(desc->_start_canary == 0, VALIDATE_PIPELINEDESC_CANARY); + _SG_VALIDATE(desc->_end_canary == 0, VALIDATE_PIPELINEDESC_CANARY); + _SG_VALIDATE(desc->shader.id != SG_INVALID_ID, VALIDATE_PIPELINEDESC_SHADER); + const _sg_shader_t* shd = _sg_lookup_shader(desc->shader.id); + _SG_VALIDATE(0 != shd, VALIDATE_PIPELINEDESC_SHADER); + if (shd) { + _SG_VALIDATE(shd->slot.state == SG_RESOURCESTATE_VALID, VALIDATE_PIPELINEDESC_SHADER); + if (desc->compute) { + _SG_VALIDATE(shd->cmn.is_compute, VALIDATE_PIPELINEDESC_COMPUTE_SHADER_EXPECTED); + } else { + _SG_VALIDATE(!shd->cmn.is_compute, VALIDATE_PIPELINEDESC_NO_COMPUTE_SHADER_EXPECTED); + bool attrs_cont = true; + for (size_t attr_index = 0; attr_index < SG_MAX_VERTEX_ATTRIBUTES; attr_index++) { + const sg_vertex_attr_state* a_state = &desc->layout.attrs[attr_index]; + if (a_state->format == SG_VERTEXFORMAT_INVALID) { + attrs_cont = false; + continue; + } + _SG_VALIDATE(attrs_cont, VALIDATE_PIPELINEDESC_NO_CONT_ATTRS); + SOKOL_ASSERT(a_state->buffer_index < SG_MAX_VERTEXBUFFER_BINDSLOTS); + // vertex format must match expected shader attribute base type (if provided) + if (shd->cmn.attrs[attr_index].base_type != SG_SHADERATTRBASETYPE_UNDEFINED) { + if (_sg_vertexformat_basetype(a_state->format) != shd->cmn.attrs[attr_index].base_type) { + _SG_VALIDATE(false, VALIDATE_PIPELINEDESC_ATTR_BASETYPE_MISMATCH); + _SG_LOGMSG(VALIDATE_PIPELINEDESC_ATTR_BASETYPE_MISMATCH, "attr format:"); + _SG_LOGMSG(VALIDATE_PIPELINEDESC_ATTR_BASETYPE_MISMATCH, _sg_vertexformat_to_string(a_state->format)); + _SG_LOGMSG(VALIDATE_PIPELINEDESC_ATTR_BASETYPE_MISMATCH, "shader attr base type:"); + _SG_LOGMSG(VALIDATE_PIPELINEDESC_ATTR_BASETYPE_MISMATCH, _sg_shaderattrbasetype_to_string(shd->cmn.attrs[attr_index].base_type)); + } + } + #if defined(SOKOL_D3D11) + // on D3D11, semantic names (and semantic indices) must be provided + _SG_VALIDATE(!_sg_strempty(&shd->d3d11.attrs[attr_index].sem_name), VALIDATE_PIPELINEDESC_ATTR_SEMANTICS); + #endif + } + // must only use readonly storage buffer bindings in render pipelines + for (size_t i = 0; i < SG_MAX_VIEW_BINDSLOTS; i++) { + if (shd->cmn.views[i].view_type == SG_VIEWTYPE_STORAGEBUFFER) { + _SG_VALIDATE(shd->cmn.views[i].sbuf_readonly, VALIDATE_PIPELINEDESC_SHADER_READONLY_STORAGEBUFFERS); + } + } + for (int buf_index = 0; buf_index < SG_MAX_VERTEXBUFFER_BINDSLOTS; buf_index++) { + const sg_vertex_buffer_layout_state* l_state = &desc->layout.buffers[buf_index]; + if (l_state->stride == 0) { + continue; + } + _SG_VALIDATE(_sg_multiple_u64((uint64_t)l_state->stride, 4), VALIDATE_PIPELINEDESC_LAYOUT_STRIDE4); + } + } + } + for (size_t color_index = 0; color_index < (size_t)desc->color_count; color_index++) { + const sg_blend_state* bs = &desc->colors[color_index].blend; + if ((bs->op_rgb == SG_BLENDOP_MIN) || (bs->op_rgb == SG_BLENDOP_MAX)) { + _SG_VALIDATE((bs->src_factor_rgb == SG_BLENDFACTOR_ONE) && (bs->dst_factor_rgb == SG_BLENDFACTOR_ONE), VALIDATE_PIPELINEDESC_BLENDOP_MINMAX_REQUIRES_BLENDFACTOR_ONE); + } + if ((bs->op_alpha == SG_BLENDOP_MIN) || (bs->op_alpha == SG_BLENDOP_MAX)) { + _SG_VALIDATE((bs->src_factor_alpha == SG_BLENDFACTOR_ONE) && (bs->dst_factor_alpha == SG_BLENDFACTOR_ONE), VALIDATE_PIPELINEDESC_BLENDOP_MINMAX_REQUIRES_BLENDFACTOR_ONE); + } + } + return _sg_validate_end(); + #endif +} + +_SOKOL_PRIVATE bool _sg_validate_view_desc(const sg_view_desc* desc) { + #if !defined(SOKOL_DEBUG) + _SOKOL_UNUSED(desc); + return true; + #else + if (_sg.desc.disable_validation) { + return true; + } + SOKOL_ASSERT(desc); + _sg_validate_begin(); + _SG_VALIDATE(desc->_start_canary == 0, VALIDATE_VIEWDESC_CANARY); + _SG_VALIDATE(desc->_end_canary == 0, VALIDATE_VIEWDESC_CANARY); + + // only one view type can be define + sg_view_type view_type = SG_VIEWTYPE_INVALID; + const sg_image_view_desc* img_desc = 0; + const sg_texture_view_desc* tex_desc = 0; + const sg_buffer_view_desc* buf_desc = 0; + if (desc->texture.image.id != SG_INVALID_ID) { + view_type = SG_VIEWTYPE_TEXTURE; + tex_desc = &desc->texture; + } + if (desc->storage_buffer.buffer.id != SG_INVALID_ID) { + _SG_VALIDATE(SG_VIEWTYPE_INVALID == view_type, VALIDATE_VIEWDESC_UNIQUE_VIEWTYPE); + view_type = SG_VIEWTYPE_STORAGEBUFFER; + buf_desc = &desc->storage_buffer; + } + if (desc->storage_image.image.id != SG_INVALID_ID) { + _SG_VALIDATE(SG_VIEWTYPE_INVALID == view_type, VALIDATE_VIEWDESC_UNIQUE_VIEWTYPE); + view_type = SG_VIEWTYPE_STORAGEIMAGE; + img_desc = &desc->storage_image; + } + if (desc->color_attachment.image.id != SG_INVALID_ID) { + _SG_VALIDATE(SG_VIEWTYPE_INVALID == view_type, VALIDATE_VIEWDESC_UNIQUE_VIEWTYPE); + view_type = SG_VIEWTYPE_COLORATTACHMENT; + img_desc = &desc->color_attachment; + } + if (desc->resolve_attachment.image.id != SG_INVALID_ID) { + _SG_VALIDATE(SG_VIEWTYPE_INVALID == view_type, VALIDATE_VIEWDESC_UNIQUE_VIEWTYPE); + view_type = SG_VIEWTYPE_RESOLVEATTACHMENT; + img_desc = &desc->resolve_attachment; + } + if (desc->depth_stencil_attachment.image.id != SG_INVALID_ID) { + _SG_VALIDATE(SG_VIEWTYPE_INVALID == view_type, VALIDATE_VIEWDESC_UNIQUE_VIEWTYPE); + view_type = SG_VIEWTYPE_DEPTHSTENCILATTACHMENT; + img_desc = &desc->depth_stencil_attachment; + } + _SG_VALIDATE(SG_VIEWTYPE_INVALID != view_type, VALIDATE_VIEWDESC_ANY_VIEWTYPE); + + const _sg_buffer_t* buf = 0; + const _sg_image_t* img = 0; + bool res_valid = false; + if (buf_desc) { + SOKOL_ASSERT((img_desc == 0) && (tex_desc == 0)); + buf = _sg_lookup_buffer(buf_desc->buffer.id); + _SG_VALIDATE(buf, VALIDATE_VIEWDESC_RESOURCE_ALIVE); + if (buf) { + _SG_VALIDATE(buf->slot.state == SG_RESOURCESTATE_VALID, VALIDATE_VIEWDESC_RESOURCE_FAILED); + res_valid = buf->slot.state == SG_RESOURCESTATE_VALID; + } + } else if (img_desc) { + SOKOL_ASSERT((tex_desc == 0) && (buf_desc == 0)); + img = _sg_lookup_image(img_desc->image.id); + _SG_VALIDATE(img, VALIDATE_VIEWDESC_RESOURCE_ALIVE); + if (img) { + _SG_VALIDATE(img->slot.state == SG_RESOURCESTATE_VALID, VALIDATE_VIEWDESC_RESOURCE_FAILED); + res_valid = img->slot.state == SG_RESOURCESTATE_VALID; + } + } else { + SOKOL_ASSERT(tex_desc && (img_desc == 0) && (buf_desc == 0)); + img = _sg_lookup_image(tex_desc->image.id); + _SG_VALIDATE(img, VALIDATE_VIEWDESC_RESOURCE_ALIVE); + if (img) { + _SG_VALIDATE(img->slot.state == SG_RESOURCESTATE_VALID, VALIDATE_VIEWDESC_RESOURCE_FAILED); + res_valid = img->slot.state == SG_RESOURCESTATE_VALID; + } + } + if (res_valid) { + // check usage flags + switch (view_type) { + case SG_VIEWTYPE_STORAGEBUFFER: + SOKOL_ASSERT(buf); + _SG_VALIDATE(buf->cmn.usage.storage_buffer, VALIDATE_VIEWDESC_STORAGEBUFFER_USAGE); + break; + case SG_VIEWTYPE_STORAGEIMAGE: + SOKOL_ASSERT(img); + _SG_VALIDATE(img->cmn.usage.storage_image, VALIDATE_VIEWDESC_STORAGEIMAGE_USAGE); + _SG_VALIDATE(_sg_is_valid_storage_image_format(img->cmn.pixel_format), VALIDATE_VIEWDESC_STORAGEIMAGE_PIXELFORMAT); + break; + case SG_VIEWTYPE_TEXTURE: + if (!_sg.features.msaa_texture_bindings) { + _SG_VALIDATE(img->cmn.sample_count == 1, VALIDATE_VIEWDESC_TEXTURE_EXPECT_NO_MSAA); + } + break; + case SG_VIEWTYPE_COLORATTACHMENT: + SOKOL_ASSERT(img); + _SG_VALIDATE(img->cmn.usage.color_attachment, VALIDATE_VIEWDESC_COLORATTACHMENT_USAGE); + _SG_VALIDATE(_sg_is_valid_attachment_color_format(img->cmn.pixel_format), VALIDATE_VIEWDESC_COLORATTACHMENT_PIXELFORMAT); + break; + case SG_VIEWTYPE_RESOLVEATTACHMENT: + SOKOL_ASSERT(img); + _SG_VALIDATE(img->cmn.usage.resolve_attachment, VALIDATE_VIEWDESC_RESOLVEATTACHMENT_USAGE); + _SG_VALIDATE(img->cmn.sample_count == 1, VALIDATE_VIEWDESC_RESOLVEATTACHMENT_SAMPLECOUNT); + break; + case SG_VIEWTYPE_DEPTHSTENCILATTACHMENT: + SOKOL_ASSERT(img); + _SG_VALIDATE(img->cmn.usage.depth_stencil_attachment, VALIDATE_VIEWDESC_DEPTHSTENCILATTACHMENT_USAGE); + _SG_VALIDATE(_sg_is_valid_attachment_depth_format(img->cmn.pixel_format), VALIDATE_VIEWDESC_DEPTHSTENCILATTACHMENT_PIXELFORMAT); + break; + default: + SOKOL_UNREACHABLE; + break; + } + if (buf_desc) { + SOKOL_ASSERT(buf); + _SG_VALIDATE(buf_desc->offset < buf->cmn.size, VALIDATE_VIEWDESC_STORAGEBUFFER_OFFSET_VS_BUFFER_SIZE); + _SG_VALIDATE(_sg_multiple_u64((uint64_t)buf_desc->offset, 256), VALIDATE_VIEWDESC_STORAGEBUFFER_OFFSET_MULTIPLE_256); + } else if (img_desc) { + SOKOL_ASSERT(img); + _SG_VALIDATE((img_desc->mip_level >= 0) && (img_desc->mip_level < img->cmn.num_mipmaps), VALIDATE_VIEWDESC_IMAGE_MIPLEVEL); + if (img->cmn.type == SG_IMAGETYPE_2D) { + _SG_VALIDATE(img_desc->slice == 0, VALIDATE_VIEWDESC_IMAGE_2D_SLICE); + } else if (img->cmn.type == SG_IMAGETYPE_CUBE) { + _SG_VALIDATE((img_desc->slice >= 0) && (img_desc->slice < 6), VALIDATE_VIEWDESC_IMAGE_CUBEMAP_SLICE); + } else if (img->cmn.type == SG_IMAGETYPE_ARRAY) { + _SG_VALIDATE((img_desc->slice >= 0) && (img_desc->slice < img->cmn.num_slices), VALIDATE_VIEWDESC_IMAGE_ARRAY_SLICE); + } else if (img->cmn.type == SG_IMAGETYPE_3D) { + _SG_VALIDATE(img_desc->slice == 0, VALIDATE_VIEWDESC_IMAGE_3D_SLICE); + } + } else if (tex_desc) { + SOKOL_ASSERT(img); + // NOTE: it doesn't matter here if the mip/slice count is default-zero! + int max_mip_level = tex_desc->mip_levels.base + tex_desc->mip_levels.count; + int max_slice = tex_desc->slices.base + tex_desc->slices.count; + _SG_VALIDATE((tex_desc->mip_levels.base >= 0) && (max_mip_level <= img->cmn.num_mipmaps), VALIDATE_VIEWDESC_TEXTURE_MIPLEVELS); + if (img->cmn.type == SG_IMAGETYPE_2D) { + _SG_VALIDATE((tex_desc->slices.base == 0) && (max_slice <= 1), VALIDATE_VIEWDESC_TEXTURE_2D_SLICES); + } else if (img->cmn.type == SG_IMAGETYPE_CUBE) { + _SG_VALIDATE((tex_desc->slices.base == 0) && (max_slice <= 1), VALIDATE_VIEWDESC_TEXTURE_CUBEMAP_SLICES); + } else if (img->cmn.type == SG_IMAGETYPE_ARRAY) { + _SG_VALIDATE((tex_desc->slices.base >= 0) && (max_slice <= img->cmn.num_slices), VALIDATE_VIEWDESC_TEXTURE_ARRAY_SLICES); + } else if (img->cmn.type == SG_IMAGETYPE_3D) { + _SG_VALIDATE((tex_desc->slices.base == 0) && (max_slice <= 1), VALIDATE_VIEWDESC_TEXTURE_3D_SLICES); + } + } + } + return _sg_validate_end(); + #endif +} + +_SOKOL_PRIVATE bool _sg_validate_begin_pass(const sg_pass* pass) { + #if !defined(SOKOL_DEBUG) + _SOKOL_UNUSED(pass); + return true; + #else + if (_sg.desc.disable_validation) { + return true; + } + const bool is_compute_pass = pass->compute; + const bool is_swapchain_pass = !is_compute_pass && _sg_attachments_empty(&pass->attachments); + const bool is_offscreen_pass = !(is_compute_pass || is_swapchain_pass); + _sg_validate_begin(); + _SG_VALIDATE(pass->_start_canary == 0, VALIDATE_BEGINPASS_CANARY); + _SG_VALIDATE(pass->_end_canary == 0, VALIDATE_BEGINPASS_CANARY); + if (is_compute_pass) { + _SG_VALIDATE(_sg_attachments_empty(&pass->attachments), VALIDATE_BEGINPASS_COMPUTEPASS_EXPECT_NO_ATTACHMENTS); + } else if (is_swapchain_pass) { + // this is a swapchain pass + _SG_VALIDATE(pass->swapchain.width > 0, VALIDATE_BEGINPASS_SWAPCHAIN_EXPECT_WIDTH); + _SG_VALIDATE(pass->swapchain.height > 0, VALIDATE_BEGINPASS_SWAPCHAIN_EXPECT_HEIGHT); + _SG_VALIDATE(pass->swapchain.sample_count > 0, VALIDATE_BEGINPASS_SWAPCHAIN_EXPECT_SAMPLECOUNT); + _SG_VALIDATE(pass->swapchain.color_format > SG_PIXELFORMAT_NONE, VALIDATE_BEGINPASS_SWAPCHAIN_EXPECT_COLORFORMAT); + // NOTE: depth buffer is optional, so depth_format is allowed to be invalid + // NOTE: the GL framebuffer handle may actually be 0 + #if defined(SOKOL_METAL) + _SG_VALIDATE(pass->swapchain.metal.current_drawable != 0, VALIDATE_BEGINPASS_SWAPCHAIN_METAL_EXPECT_CURRENTDRAWABLE); + if (pass->swapchain.depth_format == SG_PIXELFORMAT_NONE) { + _SG_VALIDATE(pass->swapchain.metal.depth_stencil_texture == 0, VALIDATE_BEGINPASS_SWAPCHAIN_METAL_EXPECT_DEPTHSTENCILTEXTURE_NOTSET); + } else { + _SG_VALIDATE(pass->swapchain.metal.depth_stencil_texture != 0, VALIDATE_BEGINPASS_SWAPCHAIN_METAL_EXPECT_DEPTHSTENCILTEXTURE); + } + if (pass->swapchain.sample_count > 1) { + _SG_VALIDATE(pass->swapchain.metal.msaa_color_texture != 0, VALIDATE_BEGINPASS_SWAPCHAIN_METAL_EXPECT_MSAACOLORTEXTURE); + } else { + _SG_VALIDATE(pass->swapchain.metal.msaa_color_texture == 0, VALIDATE_BEGINPASS_SWAPCHAIN_METAL_EXPECT_MSAACOLORTEXTURE_NOTSET); + } + #elif defined(SOKOL_D3D11) + _SG_VALIDATE(pass->swapchain.d3d11.render_view != 0, VALIDATE_BEGINPASS_SWAPCHAIN_D3D11_EXPECT_RENDERVIEW); + if (pass->swapchain.depth_format == SG_PIXELFORMAT_NONE) { + _SG_VALIDATE(pass->swapchain.d3d11.depth_stencil_view == 0, VALIDATE_BEGINPASS_SWAPCHAIN_D3D11_EXPECT_DEPTHSTENCILVIEW_NOTSET); + } else { + _SG_VALIDATE(pass->swapchain.d3d11.depth_stencil_view != 0, VALIDATE_BEGINPASS_SWAPCHAIN_D3D11_EXPECT_DEPTHSTENCILVIEW); + } + if (pass->swapchain.sample_count > 1) { + _SG_VALIDATE(pass->swapchain.d3d11.resolve_view != 0, VALIDATE_BEGINPASS_SWAPCHAIN_D3D11_EXPECT_RESOLVEVIEW); + } else { + _SG_VALIDATE(pass->swapchain.d3d11.resolve_view == 0, VALIDATE_BEGINPASS_SWAPCHAIN_D3D11_EXPECT_RESOLVEVIEW_NOTSET); + } + #elif defined(SOKOL_WGPU) + _SG_VALIDATE(pass->swapchain.wgpu.render_view != 0, VALIDATE_BEGINPASS_SWAPCHAIN_WGPU_EXPECT_RENDERVIEW); + if (pass->swapchain.depth_format == SG_PIXELFORMAT_NONE) { + _SG_VALIDATE(pass->swapchain.wgpu.depth_stencil_view == 0, VALIDATE_BEGINPASS_SWAPCHAIN_WGPU_EXPECT_DEPTHSTENCILVIEW_NOTSET); + } else { + _SG_VALIDATE(pass->swapchain.wgpu.depth_stencil_view != 0, VALIDATE_BEGINPASS_SWAPCHAIN_WGPU_EXPECT_DEPTHSTENCILVIEW); + } + if (pass->swapchain.sample_count > 1) { + _SG_VALIDATE(pass->swapchain.wgpu.resolve_view != 0, VALIDATE_BEGINPASS_SWAPCHAIN_WGPU_EXPECT_RESOLVEVIEW); + } else { + _SG_VALIDATE(pass->swapchain.wgpu.resolve_view == 0, VALIDATE_BEGINPASS_SWAPCHAIN_WGPU_EXPECT_RESOLVEVIEW_NOTSET); + } + #endif + } else { + // this is an 'offscreen pass' + bool has_color_atts = false; + bool has_depth_stencil_atts = false; + bool atts_cont = true; + int color_width = -1, color_height = -1, color_sample_count = -1; + for (int att_index = 0; att_index < SG_MAX_COLOR_ATTACHMENTS; att_index++) { + if (pass->attachments.colors[att_index].id == SG_INVALID_ID) { + atts_cont = false; + continue; + } + has_color_atts = true; + _SG_VALIDATE(atts_cont, VALIDATE_BEGINPASS_COLORATTACHMENTVIEWS_CONTINUOUS); + const _sg_view_t* view = _sg_lookup_view(pass->attachments.colors[att_index].id); + // the view object must be alive + _SG_VALIDATE(view != 0, VALIDATE_BEGINPASS_COLORATTACHMENTVIEW_ALIVE); + if (view) { + // the view object must be in valid state + _SG_VALIDATE(view->slot.state == SG_RESOURCESTATE_VALID, VALIDATE_BEGINPASS_COLORATTACHMENTVIEW_VALID); + if (view->slot.state == SG_RESOURCESTATE_VALID) { + // the view object must be a color attachment view + _SG_VALIDATE(view->cmn.type == SG_VIEWTYPE_COLORATTACHMENT, VALIDATE_BEGINPASS_COLORATTACHMENTVIEW_TYPE); + // the view's image object must be alive and valid + const _sg_image_t* img = _sg_image_ref_ptr_or_null(&view->cmn.img.ref); + _SG_VALIDATE(img, VALIDATE_BEGINPASS_COLORATTACHMENTVIEW_IMAGE_ALIVE); + if (img) { + _SG_VALIDATE(img->slot.state == SG_RESOURCESTATE_VALID, VALIDATE_BEGINPASS_COLORATTACHMENTVIEW_IMAGE_VALID); + if (img->slot.state == SG_RESOURCESTATE_VALID) { + if (color_width == -1) { + color_width = _sg_image_view_dim(view).width; + color_height = _sg_image_view_dim(view).height; + color_sample_count = img->cmn.sample_count; + } else { + _SG_VALIDATE(color_width == _sg_image_view_dim(view).width, VALIDATE_BEGINPASS_COLORATTACHMENTVIEW_SIZES); + _SG_VALIDATE(color_height == _sg_image_view_dim(view).height, VALIDATE_BEGINPASS_COLORATTACHMENTVIEW_SIZES); + _SG_VALIDATE(color_sample_count == img->cmn.sample_count, VALIDATE_BEGINPASS_COLORATTACHMENTVIEW_SAMPLECOUNTS_EQUAL); + } + } + } + } + } + } + // check resolve views + for (int att_index = 0; att_index < SG_MAX_COLOR_ATTACHMENTS; att_index++) { + if (pass->attachments.resolves[att_index].id == SG_INVALID_ID) { + continue; + } + _SG_VALIDATE(pass->attachments.colors[att_index].id != SG_INVALID_ID, VALIDATE_BEGINPASS_RESOLVEATTACHMENTVIEW_NO_COLORATTACHMENTVIEW); + const _sg_view_t* view = _sg_lookup_view(pass->attachments.resolves[att_index].id); + // the view object must be alive + _SG_VALIDATE(view != 0, VALIDATE_BEGINPASS_RESOLVEATTACHMENTVIEW_ALIVE); + if (view) { + // the view object must be in valid state + _SG_VALIDATE(view->slot.state == SG_RESOURCESTATE_VALID, VALIDATE_BEGINPASS_RESOLVEATTACHMENTVIEW_VALID); + if (view->slot.state == SG_RESOURCESTATE_VALID) { + // the view object must be a resolve attachment view + _SG_VALIDATE(view->cmn.type == SG_VIEWTYPE_RESOLVEATTACHMENT, VALIDATE_BEGINPASS_RESOLVEATTACHMENTVIEW_TYPE); + // the view's image object must be alive and valid + const _sg_image_t* img = _sg_image_ref_ptr_or_null(&view->cmn.img.ref); + _SG_VALIDATE(img, VALIDATE_BEGINPASS_RESOLVEATTACHMENTVIEW_IMAGE_ALIVE); + if (img) { + _SG_VALIDATE(img->slot.state == SG_RESOURCESTATE_VALID, VALIDATE_BEGINPASS_RESOLVEATTACHMENTVIEW_IMAGE_VALID); + if (img->slot.state == SG_RESOURCESTATE_VALID) { + if (color_width != -1) { + _SG_VALIDATE(color_sample_count > 1, VALIDATE_BEGINPASS_COLORATTACHMENTVIEW_SAMPLECOUNT); + _SG_VALIDATE(color_width == _sg_image_view_dim(view).width, VALIDATE_BEGINPASS_RESOLVEATTACHMENTVIEW_SIZES); + _SG_VALIDATE(color_height == _sg_image_view_dim(view).height, VALIDATE_BEGINPASS_RESOLVEATTACHMENTVIEW_SIZES); + } + } + } + } + } + } + // check depth-stencil view + if (pass->attachments.depth_stencil.id != SG_INVALID_ID) { + has_depth_stencil_atts = true; + const _sg_view_t* view = _sg_lookup_view(pass->attachments.depth_stencil.id); + // the view object must be valid + _SG_VALIDATE(view != 0, VALIDATE_BEGINPASS_DEPTHSTENCILATTACHMENTVIEW_ALIVE); + if (view) { + // the view object must be in valid state + _SG_VALIDATE(view->slot.state == SG_RESOURCESTATE_VALID, VALIDATE_BEGINPASS_DEPTHSTENCILATTACHMENTVIEW_VALID); + if (view->slot.state == SG_RESOURCESTATE_VALID) { + // the view object must be a depth stencil attachment view + _SG_VALIDATE(view->cmn.type == SG_VIEWTYPE_DEPTHSTENCILATTACHMENT, VALIDATE_BEGINPASS_DEPTHSTENCILATTACHMENTVIEW_TYPE); + // the view's image object must be alive and valid + const _sg_image_t* img = _sg_image_ref_ptr_or_null(&view->cmn.img.ref); + _SG_VALIDATE(img, VALIDATE_BEGINPASS_DEPTHSTENCILATTACHMENTVIEW_IMAGE_ALIVE); + if (img) { + _SG_VALIDATE(img->slot.state == SG_RESOURCESTATE_VALID, VALIDATE_BEGINPASS_DEPTHSTENCILATTACHMENTVIEW_IMAGE_VALID); + if (img->slot.state == SG_RESOURCESTATE_VALID) { + if (color_width != -1) { + _SG_VALIDATE(color_width == _sg_image_view_dim(view).width, VALIDATE_BEGINPASS_DEPTHSTENCILATTACHMENTVIEW_SIZES); + _SG_VALIDATE(color_height == _sg_image_view_dim(view).height, VALIDATE_BEGINPASS_DEPTHSTENCILATTACHMENTVIEW_SIZES); + _SG_VALIDATE(color_sample_count == img->cmn.sample_count, VALIDATE_BEGINPASS_DEPTHSTENCILATTACHMENTVIEW_SAMPLECOUNT); + } + } + } + } + } + } + // must have at least color- or depth-stencil-attachments + _SG_VALIDATE(has_color_atts || has_depth_stencil_atts, VALIDATE_BEGINPASS_ATTACHMENTS_EXPECTED); + } + if (is_compute_pass || is_offscreen_pass) { + _SG_VALIDATE(pass->swapchain.width == 0, VALIDATE_BEGINPASS_SWAPCHAIN_EXPECT_WIDTH_NOTSET); + _SG_VALIDATE(pass->swapchain.height == 0, VALIDATE_BEGINPASS_SWAPCHAIN_EXPECT_HEIGHT_NOTSET); + _SG_VALIDATE(pass->swapchain.sample_count == 0, VALIDATE_BEGINPASS_SWAPCHAIN_EXPECT_SAMPLECOUNT_NOTSET); + _SG_VALIDATE(pass->swapchain.color_format == _SG_PIXELFORMAT_DEFAULT, VALIDATE_BEGINPASS_SWAPCHAIN_EXPECT_COLORFORMAT_NOTSET); + _SG_VALIDATE(pass->swapchain.depth_format == _SG_PIXELFORMAT_DEFAULT, VALIDATE_BEGINPASS_SWAPCHAIN_EXPECT_DEPTHFORMAT_NOTSET); + #if defined(SOKOL_METAL) + _SG_VALIDATE(pass->swapchain.metal.current_drawable == 0, VALIDATE_BEGINPASS_SWAPCHAIN_METAL_EXPECT_CURRENTDRAWABLE_NOTSET); + _SG_VALIDATE(pass->swapchain.metal.depth_stencil_texture == 0, VALIDATE_BEGINPASS_SWAPCHAIN_METAL_EXPECT_DEPTHSTENCILTEXTURE_NOTSET); + _SG_VALIDATE(pass->swapchain.metal.msaa_color_texture == 0, VALIDATE_BEGINPASS_SWAPCHAIN_METAL_EXPECT_MSAACOLORTEXTURE_NOTSET); + #elif defined(SOKOL_D3D11) + _SG_VALIDATE(pass->swapchain.d3d11.render_view == 0, VALIDATE_BEGINPASS_SWAPCHAIN_D3D11_EXPECT_RENDERVIEW_NOTSET); + _SG_VALIDATE(pass->swapchain.d3d11.depth_stencil_view == 0, VALIDATE_BEGINPASS_SWAPCHAIN_D3D11_EXPECT_DEPTHSTENCILVIEW_NOTSET); + _SG_VALIDATE(pass->swapchain.d3d11.resolve_view == 0, VALIDATE_BEGINPASS_SWAPCHAIN_D3D11_EXPECT_RESOLVEVIEW_NOTSET); + #elif defined(SOKOL_WGPU) + _SG_VALIDATE(pass->swapchain.wgpu.render_view == 0, VALIDATE_BEGINPASS_SWAPCHAIN_WGPU_EXPECT_RENDERVIEW_NOTSET); + _SG_VALIDATE(pass->swapchain.wgpu.depth_stencil_view == 0, VALIDATE_BEGINPASS_SWAPCHAIN_WGPU_EXPECT_DEPTHSTENCILVIEW_NOTSET); + _SG_VALIDATE(pass->swapchain.wgpu.resolve_view == 0, VALIDATE_BEGINPASS_SWAPCHAIN_WGPU_EXPECT_RESOLVEVIEW_NOTSET); + #elif defined(_SOKOL_ANY_GL) + _SG_VALIDATE(pass->swapchain.gl.framebuffer == 0, VALIDATE_BEGINPASS_SWAPCHAIN_GL_EXPECT_FRAMEBUFFER_NOTSET); + #endif + } + return _sg_validate_end(); + #endif +} + +_SOKOL_PRIVATE bool _sg_validate_apply_viewport(int x, int y, int width, int height, bool origin_top_left) { + _SOKOL_UNUSED(x); + _SOKOL_UNUSED(y); + _SOKOL_UNUSED(width); + _SOKOL_UNUSED(height); + _SOKOL_UNUSED(origin_top_left); + #if !defined(SOKOL_DEBUG) + return true; + #else + if (_sg.desc.disable_validation) { + return true; + } + _sg_validate_begin(); + _SG_VALIDATE(_sg.cur_pass.in_pass && !_sg.cur_pass.is_compute, VALIDATE_AVP_RENDERPASS_EXPECTED); + return _sg_validate_end(); + #endif +} + +_SOKOL_PRIVATE bool _sg_validate_apply_scissor_rect(int x, int y, int width, int height, bool origin_top_left) { + _SOKOL_UNUSED(x); + _SOKOL_UNUSED(y); + _SOKOL_UNUSED(width); + _SOKOL_UNUSED(height); + _SOKOL_UNUSED(origin_top_left); + #if !defined(SOKOL_DEBUG) + return true; + #else + if (_sg.desc.disable_validation) { + return true; + } + _sg_validate_begin(); + _SG_VALIDATE(_sg.cur_pass.in_pass && !_sg.cur_pass.is_compute, VALIDATE_ASR_RENDERPASS_EXPECTED); + return _sg_validate_end(); + #endif +} + +_SOKOL_PRIVATE bool _sg_validate_apply_pipeline(sg_pipeline pip_id) { + #if !defined(SOKOL_DEBUG) + _SOKOL_UNUSED(pip_id); + return true; + #else + if (_sg.desc.disable_validation) { + return true; + } + _sg_validate_begin(); + // the pipeline object must be alive and valid + _SG_VALIDATE(pip_id.id != SG_INVALID_ID, VALIDATE_APIP_PIPELINE_VALID_ID); + const _sg_pipeline_t* pip = _sg_lookup_pipeline(pip_id.id); + _SG_VALIDATE(pip != 0, VALIDATE_APIP_PIPELINE_EXISTS); + if (!pip) { + return _sg_validate_end(); + } + _SG_VALIDATE(pip->slot.state == SG_RESOURCESTATE_VALID, VALIDATE_APIP_PIPELINE_VALID); + + // the pipeline's shader must be alive and valid + _SG_VALIDATE(_sg.cur_pass.in_pass, VALIDATE_APIP_PASS_EXPECTED); + const bool shd_alive = _sg_shader_ref_alive(&pip->cmn.shader); + const _sg_shader_t* shd = shd_alive ? _sg_shader_ref_ptr(&pip->cmn.shader) : 0; + _SG_VALIDATE(shd_alive, VALIDATE_APIP_PIPELINE_SHADER_ALIVE); + if (shd_alive) { + _SG_VALIDATE(shd->slot.state == SG_RESOURCESTATE_VALID, VALIDATE_APIP_PIPELINE_SHADER_VALID); + } else { + return _sg_validate_end(); + } + + if (pip->cmn.is_compute) { + _SG_VALIDATE(_sg.cur_pass.is_compute, VALIDATE_APIP_COMPUTEPASS_EXPECTED); + } else { + _SG_VALIDATE(!_sg.cur_pass.is_compute, VALIDATE_APIP_RENDERPASS_EXPECTED); + if (_sg_attachments_empty(&_sg.cur_pass.atts)) { + // a swapchain pass + _SG_VALIDATE(pip->cmn.color_count == 1, VALIDATE_APIP_SWAPCHAIN_COLOR_COUNT); + _SG_VALIDATE(pip->cmn.colors[0].pixel_format == _sg.cur_pass.swapchain.color_fmt, VALIDATE_APIP_SWAPCHAIN_COLOR_FORMAT); + _SG_VALIDATE(pip->cmn.depth.pixel_format == _sg.cur_pass.swapchain.depth_fmt, VALIDATE_APIP_SWAPCHAIN_DEPTH_FORMAT); + _SG_VALIDATE(pip->cmn.sample_count == _sg.cur_pass.swapchain.sample_count, VALIDATE_APIP_SWAPCHAIN_SAMPLE_COUNT); + } else { + // an offscreen render pass check that pipeline attributes match current pass attachment attributes + const _sg_attachments_ptrs_t atts_ptrs = _sg_attachments_ptrs(&_sg.cur_pass.atts); + const bool alive = _sg_attachments_alive(&atts_ptrs); + _SG_VALIDATE(alive, VALIDATE_APIP_ATTACHMENTS_ALIVE); + if (alive) { + _SG_VALIDATE(pip->cmn.color_count == atts_ptrs.num_color_views, VALIDATE_APIP_COLORATTACHMENTS_COUNT); + for (int i = 0; i < pip->cmn.color_count; i++) { + const _sg_view_t* clr_view = atts_ptrs.color_views[i]; + SOKOL_ASSERT(clr_view); + _SG_VALIDATE(clr_view->slot.state == SG_RESOURCESTATE_VALID, VALIDATE_APIP_COLORATTACHMENTS_VIEW_VALID); + const _sg_image_t* clr_img = _sg_image_ref_ptr(&clr_view->cmn.img.ref); + SOKOL_ASSERT(clr_img); + _SG_VALIDATE(clr_img->slot.state == SG_RESOURCESTATE_VALID, VALIDATE_APIP_COLORATTACHMENTS_IMAGE_VALID); + _SG_VALIDATE(pip->cmn.colors[i].pixel_format == clr_img->cmn.pixel_format, VALIDATE_APIP_COLORATTACHMENTS_FORMAT); + _SG_VALIDATE(pip->cmn.sample_count == clr_img->cmn.sample_count, VALIDATE_APIP_ATTACHMENT_SAMPLE_COUNT); + } + const _sg_view_t* ds_view = atts_ptrs.ds_view; + if (ds_view) { + _SG_VALIDATE(ds_view->slot.state == SG_RESOURCESTATE_VALID, VALIDATE_APIP_DEPTHSTENCILATTACHMENT_VIEW_VALID); + const _sg_image_t* ds_img = _sg_image_ref_ptr(&ds_view->cmn.img.ref); + SOKOL_ASSERT(ds_img); + _SG_VALIDATE(ds_img->slot.state == SG_RESOURCESTATE_VALID, VALIDATE_APIP_DEPTHSTENCILATTACHMENT_IMAGE_VALID); + _SG_VALIDATE(pip->cmn.depth.pixel_format == ds_img->cmn.pixel_format, VALIDATE_APIP_DEPTHSTENCILATTACHMENT_FORMAT); + _SG_VALIDATE(pip->cmn.sample_count == ds_img->cmn.sample_count, VALIDATE_APIP_ATTACHMENT_SAMPLE_COUNT); + } else { + _SG_VALIDATE(pip->cmn.depth.pixel_format == SG_PIXELFORMAT_NONE, VALIDATE_APIP_DEPTHSTENCILATTACHMENT_FORMAT); + } + } + } + } + return _sg_validate_end(); + #endif +} + +_SOKOL_PRIVATE bool _sg_validate_apply_bindings(const sg_bindings* bindings) { + #if !defined(SOKOL_DEBUG) + _SOKOL_UNUSED(bindings); + return true; + #else + if (_sg.desc.disable_validation) { + return true; + } + _sg_validate_begin(); + + // must be called in a pass + _SG_VALIDATE(_sg.cur_pass.in_pass, VALIDATE_ABND_PASS_EXPECTED); + + // bindings must not be empty + bool has_any_bindings = bindings->index_buffer.id != SG_INVALID_ID; + if (!has_any_bindings) for (size_t i = 0; i < SG_MAX_VERTEXBUFFER_BINDSLOTS; i++) { + has_any_bindings |= bindings->vertex_buffers[i].id != SG_INVALID_ID; + } + if (!has_any_bindings) for (size_t i = 0; i < SG_MAX_VIEW_BINDSLOTS; i++) { + has_any_bindings |= bindings->views[i].id != SG_INVALID_ID; + } + if (!has_any_bindings) for (size_t i = 0; i < SG_MAX_SAMPLER_BINDSLOTS; i++) { + has_any_bindings |= bindings->samplers[i].id != SG_INVALID_ID; + } + _SG_VALIDATE(has_any_bindings, VALIDATE_ABND_EMPTY_BINDINGS); + + // a pipeline object must have been applied + const bool pip_null = _sg_pipeline_ref_null(&_sg.cur_pip); + const bool pip_alive = _sg_pipeline_ref_alive(&_sg.cur_pip); + _SG_VALIDATE(!pip_null, VALIDATE_ABND_NO_PIPELINE); + _SG_VALIDATE(pip_alive, VALIDATE_ABND_PIPELINE_ALIVE); + if (!pip_alive) { + return _sg_validate_end(); + } + const _sg_pipeline_t* pip = _sg_pipeline_ref_ptr(&_sg.cur_pip); + _SG_VALIDATE(pip->slot.state == SG_RESOURCESTATE_VALID, VALIDATE_ABND_PIPELINE_VALID); + + const bool shd_alive = _sg_shader_ref_alive(&pip->cmn.shader); + _SG_VALIDATE(shd_alive, VALIDATE_ABND_PIPELINE_SHADER_ALIVE); + if (!shd_alive) { + return _sg_validate_end(); + } + const _sg_shader_t* shd = _sg_shader_ref_ptr(&pip->cmn.shader); + _SG_VALIDATE(shd->slot.state == SG_RESOURCESTATE_VALID, VALIDATE_ABND_PIPELINE_SHADER_VALID); + + if (_sg.cur_pass.is_compute) { + for (size_t i = 0; i < SG_MAX_VERTEXBUFFER_BINDSLOTS; i++) { + _SG_VALIDATE(bindings->vertex_buffers[i].id == SG_INVALID_ID, VALIDATE_ABND_COMPUTE_EXPECTED_NO_VBUFS); + } + } else { + for (size_t i = 0; i < SG_MAX_VERTEXBUFFER_BINDSLOTS; i++) { + if (pip->cmn.vertex_buffer_layout_active[i]) { + _SG_VALIDATE(bindings->vertex_buffers[i].id != SG_INVALID_ID, VALIDATE_ABND_EXPECTED_VBUF); + if (bindings->vertex_buffers[i].id != SG_INVALID_ID) { + const _sg_buffer_t* buf = _sg_lookup_buffer(bindings->vertex_buffers[i].id); + _SG_VALIDATE(buf != 0, VALIDATE_ABND_VBUF_ALIVE); + // NOTE: state != VALID is legal and skips rendering! + if (buf && buf->slot.state == SG_RESOURCESTATE_VALID) { + _SG_VALIDATE(buf->cmn.usage.vertex_buffer, VALIDATE_ABND_VBUF_USAGE); + _SG_VALIDATE(!buf->cmn.append_overflow, VALIDATE_ABND_VBUF_OVERFLOW); + } + } + } + } + } + + if (_sg.cur_pass.is_compute) { + _SG_VALIDATE(bindings->index_buffer.id == SG_INVALID_ID, VALIDATE_ABND_COMPUTE_EXPECTED_NO_IBUF); + } else { + // index buffer expected or not, and index buffer still exists + if (pip->cmn.index_type == SG_INDEXTYPE_NONE) { + // pipeline defines non-indexed rendering, but index buffer provided + _SG_VALIDATE(bindings->index_buffer.id == SG_INVALID_ID, VALIDATE_ABND_EXPECTED_NO_IBUF); + } else { + // pipeline defines indexed rendering, but no index buffer provided + _SG_VALIDATE(bindings->index_buffer.id != SG_INVALID_ID, VALIDATE_ABND_EXPECTED_IBUF); + } + if (bindings->index_buffer.id != SG_INVALID_ID) { + // buffer in index-buffer-slot must have index buffer usage + const _sg_buffer_t* buf = _sg_lookup_buffer(bindings->index_buffer.id); + _SG_VALIDATE(buf != 0, VALIDATE_ABND_IBUF_ALIVE); + // NOTE: state != VALID is legal and skips rendering! + if (buf && buf->slot.state == SG_RESOURCESTATE_VALID) { + _SG_VALIDATE(buf->cmn.usage.index_buffer, VALIDATE_ABND_IBUF_USAGE); + _SG_VALIDATE(!buf->cmn.append_overflow, VALIDATE_ABND_IBUF_OVERFLOW); + } + } + } + + // has expected view bindings + for (size_t i = 0; i < SG_MAX_VIEW_BINDSLOTS; i++) { + if (shd->cmn.views[i].view_type != SG_VIEWTYPE_INVALID) { + _SG_VALIDATE(bindings->views[i].id != SG_INVALID_ID, VALIDATE_ABND_EXPECTED_VIEW_BINDING); + if (bindings->views[i].id != SG_INVALID_ID) { + const _sg_view_t* view = _sg_lookup_view(bindings->views[i].id); + _SG_VALIDATE(view != 0, VALIDATE_ABND_VIEW_ALIVE); + // the view object must be alive + if (view) { + // NOTE: an invalid view state is allowed and skips rendering + if (view->slot.state == SG_RESOURCESTATE_VALID) { + if (shd->cmn.views[i].view_type == SG_VIEWTYPE_TEXTURE) { + // the view object must be a texture view + _SG_VALIDATE(view->cmn.type == SG_VIEWTYPE_TEXTURE, VALIDATE_ABND_EXPECT_TEXVIEW); + // NOTE: an invalid image ref is allowed and skips rendering + if (_sg_image_ref_valid(&view->cmn.img.ref)) { + const _sg_image_t* img = _sg_image_ref_ptr(&view->cmn.img.ref); + _SG_VALIDATE(img->cmn.type == shd->cmn.views[i].image_type, VALIDATE_ABND_TEXVIEW_IMAGETYPE_MISMATCH); + if (shd->cmn.views[i].multisampled) { + _SG_VALIDATE(img->cmn.sample_count > 1, VALIDATE_ABND_TEXVIEW_EXPECTED_MULTISAMPLED_IMAGE); + } else { + _SG_VALIDATE(img->cmn.sample_count == 1, VALIDATE_ABND_TEXVIEW_EXPECTED_NON_MULTISAMPLED_IMAGE); + } + const _sg_pixelformat_info_t* info = &_sg.formats[img->cmn.pixel_format]; + switch (shd->cmn.views[i].sample_type) { + case SG_IMAGESAMPLETYPE_FLOAT: + _SG_VALIDATE(info->filter, VALIDATE_ABND_TEXVIEW_EXPECTED_FILTERABLE_IMAGE); + break; + case SG_IMAGESAMPLETYPE_DEPTH: + _SG_VALIDATE(info->depth, VALIDATE_ABND_TEXVIEW_EXPECTED_DEPTH_IMAGE); + break; + default: + break; + } + } + } else if (shd->cmn.views[i].view_type == SG_VIEWTYPE_STORAGEBUFFER) { + // the view object must be a storage buffer view + _SG_VALIDATE(view->cmn.type == SG_VIEWTYPE_STORAGEBUFFER, VALIDATE_ABND_EXPECT_SBVIEW); + // NOTE: an invalid buffer ref is allowed and skips rendering + if (_sg_buffer_ref_valid(&view->cmn.buf.ref)) { + const _sg_buffer_t* buf = _sg_buffer_ref_ptr(&view->cmn.buf.ref); + if (!shd->cmn.views[i].sbuf_readonly) { + _SG_VALIDATE(buf->cmn.usage.immutable, VALIDATE_ABND_SBVIEW_READWRITE_IMMUTABLE); + } + } + } else if (shd->cmn.views[i].view_type == SG_VIEWTYPE_STORAGEIMAGE) { + // the view object must be a storage-image-view + _SG_VALIDATE(view->cmn.type == SG_VIEWTYPE_STORAGEIMAGE, VALIDATE_ABND_EXPECT_SIMGVIEW); + // storage images only allowed in compute passes + _SG_VALIDATE(_sg.cur_pass.is_compute, VALIDATE_ABND_SIMGVIEW_COMPUTE_PASS_EXPECTED); + // NOTE: an invalid image ref is allowed and skips rendering + if (_sg_image_ref_valid(&view->cmn.img.ref)) { + const _sg_image_t* img = _sg_image_ref_ptr(&view->cmn.img.ref); + _SG_VALIDATE(img->cmn.type == shd->cmn.views[i].image_type, VALIDATE_ABND_SIMGVIEW_IMAGETYPE_MISMATCH); + _SG_VALIDATE(img->cmn.pixel_format == shd->cmn.views[i].access_format, VALIDATE_ABND_SIMGVIEW_ACCESSFORMAT); + } + } + } + } + } + } + } + + // has expected samplers + for (size_t i = 0; i < SG_MAX_SAMPLER_BINDSLOTS; i++) { + if (shd->cmn.samplers[i].stage != SG_SHADERSTAGE_NONE) { + _SG_VALIDATE(bindings->samplers[i].id != SG_INVALID_ID, VALIDATE_ABND_EXPECTED_SAMPLER_BINDING); + if (bindings->samplers[i].id != SG_INVALID_ID) { + const _sg_sampler_t* smp = _sg_lookup_sampler(bindings->samplers[i].id); + _SG_VALIDATE(smp != 0, VALIDATE_ABND_SAMPLER_ALIVE); + if (smp) { + // NOTE: for invalid samplers don't skip rendering, but are actually an error + _SG_VALIDATE(smp->slot.state == SG_RESOURCESTATE_VALID, VALIDATE_ABND_SAMPLER_VALID); + if (shd->cmn.samplers[i].sampler_type == SG_SAMPLERTYPE_COMPARISON) { + _SG_VALIDATE(smp->cmn.compare != SG_COMPAREFUNC_NEVER, VALIDATE_ABND_UNEXPECTED_SAMPLER_COMPARE_NEVER); + } else { + _SG_VALIDATE(smp->cmn.compare == SG_COMPAREFUNC_NEVER, VALIDATE_ABND_EXPECTED_SAMPLER_COMPARE_NEVER); + } + if (shd->cmn.samplers[i].sampler_type == SG_SAMPLERTYPE_NONFILTERING) { + const bool nonfiltering = (smp->cmn.min_filter != SG_FILTER_LINEAR) + && (smp->cmn.mag_filter != SG_FILTER_LINEAR) + && (smp->cmn.mipmap_filter != SG_FILTER_LINEAR); + _SG_VALIDATE(nonfiltering, VALIDATE_ABND_EXPECTED_NONFILTERING_SAMPLER); + } + } + } + } + } + + // the same image cannot be used as texture binding and pass attachment or storage image binding + for (size_t tex_view_idx = 0; tex_view_idx < SG_MAX_VIEW_BINDSLOTS; tex_view_idx++) { + if (shd->cmn.views[tex_view_idx].view_type == SG_VIEWTYPE_TEXTURE) { + if (bindings->views[tex_view_idx].id == SG_INVALID_ID) { + continue; + } + const _sg_view_t* tex_view = _sg_lookup_view(bindings->views[tex_view_idx].id); + if (tex_view) { + const uint32_t img_id = tex_view->cmn.img.ref.sref.id; + if (!_sg_attachments_empty(&_sg.cur_pass.atts)) { + const _sg_view_t* ds_view = _sg_lookup_view(_sg.cur_pass.atts.depth_stencil.id); + if (ds_view) { + _SG_VALIDATE(img_id != ds_view->cmn.img.ref.sref.id, VALIDATE_ABND_TEXTURE_BINDING_VS_DEPTHSTENCIL_ATTACHMENT); + } + for (size_t att_idx = 0; att_idx < SG_MAX_COLOR_ATTACHMENTS; att_idx++) { + const _sg_view_t* color_view = _sg_lookup_view(_sg.cur_pass.atts.colors[att_idx].id); + if (color_view) { + _SG_VALIDATE(img_id != color_view->cmn.img.ref.sref.id, VALIDATE_ABND_TEXTURE_BINDING_VS_COLOR_ATTACHMENT); + } + const _sg_view_t* resolve_view = _sg_lookup_view(_sg.cur_pass.atts.resolves[att_idx].id); + if (resolve_view) { + _SG_VALIDATE(img_id != resolve_view->cmn.img.ref.sref.id, VALIDATE_ABND_TEXTURE_BINDING_VS_RESOLVE_ATTACHMENT); + } + } + } + for (size_t simg_view_idx = 0; simg_view_idx < SG_MAX_VIEW_BINDSLOTS; simg_view_idx++) { + if (shd->cmn.views[simg_view_idx].view_type == SG_VIEWTYPE_STORAGEIMAGE) { + if (bindings->views[simg_view_idx].id == SG_INVALID_ID) { + continue; + } + const _sg_view_t* simg_view = _sg_lookup_view(bindings->views[simg_view_idx].id); + if (simg_view) { + _SG_VALIDATE(img_id != simg_view->cmn.img.ref.sref.id, VALIDATE_ABND_TEXTURE_VS_STORAGEIMAGE_BINDING); + } + } + } + } + } + } + return _sg_validate_end(); + #endif +} + +_SOKOL_PRIVATE bool _sg_validate_apply_uniforms(int ub_slot, const sg_range* data) { + #if !defined(SOKOL_DEBUG) + _SOKOL_UNUSED(ub_slot); + _SOKOL_UNUSED(data); + return true; + #else + if (_sg.desc.disable_validation) { + return true; + } + SOKOL_ASSERT((ub_slot >= 0) && (ub_slot < SG_MAX_UNIFORMBLOCK_BINDSLOTS)); + _sg_validate_begin(); + _SG_VALIDATE(_sg.cur_pass.in_pass, VALIDATE_AU_PASS_EXPECTED); + const _sg_pipeline_ref_t* pip_ref = &_sg.cur_pip; + const bool pip_null = _sg_pipeline_ref_null(pip_ref); + const bool pip_alive = _sg_pipeline_ref_alive(pip_ref); + _SG_VALIDATE(!pip_null, VALIDATE_AU_NO_PIPELINE); + _SG_VALIDATE(pip_alive, VALIDATE_AU_PIPELINE_ALIVE); + if (pip_alive) { + const _sg_pipeline_t* pip = _sg_pipeline_ref_ptr(pip_ref); + _SG_VALIDATE(pip->slot.state == SG_RESOURCESTATE_VALID, VALIDATE_AU_PIPELINE_VALID); + const _sg_shader_ref_t* shd_ref = &pip->cmn.shader; + const bool shd_alive = _sg_shader_ref_alive(shd_ref); + _SG_VALIDATE(shd_alive, VALIDATE_AU_PIPELINE_SHADER_ALIVE); + if (shd_alive) { + const _sg_shader_t* shd = _sg_shader_ref_ptr(shd_ref); + _SG_VALIDATE(shd->slot.state == SG_RESOURCESTATE_VALID, VALIDATE_AU_PIPELINE_SHADER_VALID); + _SG_VALIDATE(shd->cmn.uniform_blocks[ub_slot].stage != SG_SHADERSTAGE_NONE, VALIDATE_AU_NO_UNIFORMBLOCK_AT_SLOT); + _SG_VALIDATE(data->size == shd->cmn.uniform_blocks[ub_slot].size, VALIDATE_AU_SIZE); + } + } + return _sg_validate_end(); + #endif +} + +_SOKOL_PRIVATE bool _sg_validate_draw(int base_element, int num_elements, int num_instances) { + #if !defined(SOKOL_DEBUG) + _SOKOL_UNUSED(base_element); + _SOKOL_UNUSED(num_elements); + _SOKOL_UNUSED(num_instances); + return true; + #else + if (_sg.desc.disable_validation) { + return true; + } + _sg_validate_begin(); + _SG_VALIDATE(_sg.cur_pass.in_pass && !_sg.cur_pass.is_compute, VALIDATE_DRAW_RENDERPASS_EXPECTED); + _SG_VALIDATE(base_element >= 0, VALIDATE_DRAW_BASEELEMENT_GE_ZERO); + _SG_VALIDATE(num_elements >= 0, VALIDATE_DRAW_NUMELEMENTS_GE_ZERO); + _SG_VALIDATE(num_instances >= 0, VALIDATE_DRAW_NUMINSTANCES_GE_ZERO); + _SG_VALIDATE(_sg.required_bindings_and_uniforms == _sg.applied_bindings_and_uniforms, VALIDATE_DRAW_REQUIRED_BINDINGS_OR_UNIFORMS_MISSING); + return _sg_validate_end(); + #endif +} + +_SOKOL_PRIVATE bool _sg_validate_draw_ex(int base_element, int num_elements, int num_instances, int base_vertex, int base_instance) { + #if !defined(SOKOL_DEBUG) + _SOKOL_UNUSED(base_element); + _SOKOL_UNUSED(num_elements); + _SOKOL_UNUSED(num_instances); + _SOKOL_UNUSED(base_vertex); + _SOKOL_UNUSED(base_instance); + return true; + #else + if (_sg.desc.disable_validation) { + return true; + } + _sg_validate_begin(); + _SG_VALIDATE(_sg.cur_pass.in_pass && !_sg.cur_pass.is_compute, VALIDATE_DRAW_EX_RENDERPASS_EXPECTED); + // NOTE: base_vertex is allowed to be < 0 + _SG_VALIDATE(base_element >= 0, VALIDATE_DRAW_EX_BASEELEMENT_GE_ZERO); + _SG_VALIDATE(num_elements >= 0, VALIDATE_DRAW_EX_NUMELEMENTS_GE_ZERO); + _SG_VALIDATE(num_instances >= 0, VALIDATE_DRAW_EX_NUMINSTANCES_GE_ZERO); + _SG_VALIDATE(base_instance >= 0, VALIDATE_DRAW_EX_BASEINSTANCE_GE_ZERO); + if (base_vertex != 0) { + _SG_VALIDATE(_sg.features.draw_base_vertex, VALIDATE_DRAW_EX_BASEVERTEX_NOT_SUPPORTED); + } + if (base_instance > 0) { + _SG_VALIDATE(_sg.features.draw_base_instance, VALIDATE_DRAW_EX_BASEINSTANCE_NOT_SUPPORTED); + } + if (!_sg.use_indexed_draw) { + _SG_VALIDATE(base_vertex == 0, VALIDATE_DRAW_EX_BASEVERTEX_VS_INDEXED); + } + const bool use_instanced_draw = (num_instances > 1) || _sg.use_instanced_draw; + if (!use_instanced_draw) { + _SG_VALIDATE(base_instance == 0, VALIDATE_DRAW_EX_BASEINSTANCE_VS_INSTANCED); + } + _SG_VALIDATE(_sg.required_bindings_and_uniforms == _sg.applied_bindings_and_uniforms, VALIDATE_DRAW_REQUIRED_BINDINGS_OR_UNIFORMS_MISSING); + return _sg_validate_end(); + #endif +} + +_SOKOL_PRIVATE bool _sg_validate_dispatch(int num_groups_x, int num_groups_y, int num_groups_z) { + #if !defined(SOKOL_DEBUG) + _SOKOL_UNUSED(num_groups_x); + _SOKOL_UNUSED(num_groups_y); + _SOKOL_UNUSED(num_groups_z); + return true; + #else + if (_sg.desc.disable_validation) { + return true; + } + _sg_validate_begin(); + _SG_VALIDATE(_sg.cur_pass.in_pass && _sg.cur_pass.is_compute, VALIDATE_DISPATCH_COMPUTEPASS_EXPECTED); + _SG_VALIDATE((num_groups_x >= 0) && (num_groups_x < (1<<16)), VALIDATE_DISPATCH_NUMGROUPSX); + _SG_VALIDATE((num_groups_y >= 0) && (num_groups_y < (1<<16)), VALIDATE_DISPATCH_NUMGROUPSY); + _SG_VALIDATE((num_groups_z >= 0) && (num_groups_z < (1<<16)), VALIDATE_DISPATCH_NUMGROUPSZ); + _SG_VALIDATE(_sg.required_bindings_and_uniforms == _sg.applied_bindings_and_uniforms, VALIDATE_DRAW_REQUIRED_BINDINGS_OR_UNIFORMS_MISSING); + return _sg_validate_end(); + #endif +} + +_SOKOL_PRIVATE bool _sg_validate_update_buffer(const _sg_buffer_t* buf, const sg_range* data) { + #if !defined(SOKOL_DEBUG) + _SOKOL_UNUSED(buf); + _SOKOL_UNUSED(data); + return true; + #else + if (_sg.desc.disable_validation) { + return true; + } + SOKOL_ASSERT(buf && data && data->ptr); + _sg_validate_begin(); + _SG_VALIDATE(!buf->cmn.usage.immutable, VALIDATE_UPDATEBUF_USAGE); + _SG_VALIDATE(buf->cmn.size >= (int)data->size, VALIDATE_UPDATEBUF_SIZE); + _SG_VALIDATE(buf->cmn.update_frame_index != _sg.frame_index, VALIDATE_UPDATEBUF_ONCE); + _SG_VALIDATE(buf->cmn.append_frame_index != _sg.frame_index, VALIDATE_UPDATEBUF_APPEND); + return _sg_validate_end(); + #endif +} + +_SOKOL_PRIVATE bool _sg_validate_append_buffer(const _sg_buffer_t* buf, const sg_range* data) { + #if !defined(SOKOL_DEBUG) + _SOKOL_UNUSED(buf); + _SOKOL_UNUSED(data); + return true; + #else + if (_sg.desc.disable_validation) { + return true; + } + SOKOL_ASSERT(buf && data && data->ptr); + _sg_validate_begin(); + _SG_VALIDATE(!buf->cmn.usage.immutable, VALIDATE_APPENDBUF_USAGE); + _SG_VALIDATE(buf->cmn.size >= (buf->cmn.append_pos + (int)data->size), VALIDATE_APPENDBUF_SIZE); + _SG_VALIDATE(buf->cmn.update_frame_index != _sg.frame_index, VALIDATE_APPENDBUF_UPDATE); + return _sg_validate_end(); + #endif +} + +_SOKOL_PRIVATE bool _sg_validate_update_image(const _sg_image_t* img, const sg_image_data* data) { + #if !defined(SOKOL_DEBUG) + _SOKOL_UNUSED(img); + _SOKOL_UNUSED(data); + return true; + #else + if (_sg.desc.disable_validation) { + return true; + } + SOKOL_ASSERT(img && data); + _sg_validate_begin(); + _SG_VALIDATE(!img->cmn.usage.immutable, VALIDATE_UPDIMG_USAGE); + _SG_VALIDATE(img->cmn.upd_frame_index != _sg.frame_index, VALIDATE_UPDIMG_ONCE); + _sg_validate_image_data(data, + img->cmn.pixel_format, + img->cmn.width, + img->cmn.height, + img->cmn.num_mipmaps, + img->cmn.num_slices); + return _sg_validate_end(); + #endif +} + +_SOKOL_PRIVATE bool _sg_validate_shader_binding_limits(const sg_shader_desc* desc) { + SOKOL_ASSERT(desc); + + // NOTE: this validation check is also active in release mode, if a shader uses + // more bindings than allowed, shader creation will fail + int vs_num_tex = 0; + int fs_num_tex = 0; + int cs_num_tex = 0; + int vs_num_sbuf = 0; + int fs_num_sbuf = 0; + int cs_num_sbuf = 0; + int vs_num_simg = 0; + int fs_num_simg = 0; + int cs_num_simg = 0; + int vs_num_texsmp = 0; + int fs_num_texsmp = 0; + int cs_num_texsmp = 0; + for (size_t i = 0; i < SG_MAX_VIEW_BINDSLOTS; i++) { + switch (desc->views[i].texture.stage) { + case SG_SHADERSTAGE_VERTEX: vs_num_tex++; break; + case SG_SHADERSTAGE_FRAGMENT: fs_num_tex++; break; + case SG_SHADERSTAGE_COMPUTE: cs_num_tex++; break; + default: break; + } + switch (desc->views[i].storage_buffer.stage) { + case SG_SHADERSTAGE_VERTEX: vs_num_sbuf++; break; + case SG_SHADERSTAGE_FRAGMENT: fs_num_sbuf++; break; + case SG_SHADERSTAGE_COMPUTE: cs_num_sbuf++; break; + default: break; + } + switch (desc->views[i].storage_image.stage) { + case SG_SHADERSTAGE_VERTEX: vs_num_simg++; break; + case SG_SHADERSTAGE_FRAGMENT: fs_num_simg++; break; + case SG_SHADERSTAGE_COMPUTE: cs_num_simg++; break; + default: break; + } + } + for (size_t i = 0; i < SG_MAX_TEXTURE_SAMPLER_PAIRS; i++) { + switch (desc->texture_sampler_pairs[i].stage) { + case SG_SHADERSTAGE_VERTEX: vs_num_texsmp++; break; + case SG_SHADERSTAGE_FRAGMENT: fs_num_texsmp++; break; + case SG_SHADERSTAGE_COMPUTE: cs_num_texsmp++; break; + default: break; + } + } + const int max_tex = _sg.limits.max_texture_bindings_per_stage; + const int max_sbuf = _sg.limits.max_storage_buffer_bindings_per_stage; + const int max_simg = _sg.limits.max_storage_image_bindings_per_stage; + bool retval = true; + if (vs_num_tex > max_tex) { + _SG_ERROR(SHADERDESC_TOO_MANY_VERTEXSTAGE_TEXTURES); + retval = false; + } + if (fs_num_tex > max_tex) { + _SG_ERROR(SHADERDESC_TOO_MANY_FRAGMENTSTAGE_TEXTURES); + retval = false; + } + if (cs_num_tex > max_tex) { + _SG_ERROR(SHADERDESC_TOO_MANY_COMPUTESTAGE_TEXTURES); + retval = false; + } + if (vs_num_sbuf > max_sbuf) { + _SG_ERROR(SHADERDESC_TOO_MANY_VERTEXSTAGE_STORAGEBUFFERS); + retval = false; + } + if (fs_num_sbuf > max_sbuf) { + _SG_ERROR(SHADERDESC_TOO_MANY_FRAGMENTSTAGE_STORAGEBUFFERS); + retval = false; + } + if (cs_num_sbuf > max_sbuf) { + _SG_ERROR(SHADERDESC_TOO_MANY_COMPUTESTAGE_STORAGEBUFFERS); + retval = false; + } + if (vs_num_simg > max_simg) { + _SG_ERROR(SHADERDESC_TOO_MANY_VERTEXSTAGE_STORAGEIMAGES); + retval = false; + } + if (fs_num_simg > max_simg) { + _SG_ERROR(SHADERDESC_TOO_MANY_FRAGMENTSTAGE_STORAGEIMAGES); + retval = false; + } + if (cs_num_simg > max_simg) { + _SG_ERROR(SHADERDESC_TOO_MANY_COMPUTESTAGE_STORAGEIMAGES); + retval = false; + } + if (vs_num_texsmp > max_tex) { + _SG_ERROR(SHADERDESC_TOO_MANY_VERTEXSTAGE_TEXTURESAMPLERPAIRS); + retval = false; + } + if (fs_num_texsmp > max_tex) { + _SG_ERROR(SHADERDESC_TOO_MANY_FRAGMENTSTAGE_TEXTURESAMPLERPAIRS); + retval = false; + } + if (cs_num_texsmp > max_tex) { + _SG_ERROR(SHADERDESC_TOO_MANY_COMPUTESTAGE_TEXTURESAMPLERPAIRS); + retval = false; + } + return retval; +} + +_SOKOL_PRIVATE bool _sg_validate_pass_attachment_limits(const sg_pass* pass) { + SOKOL_ASSERT(pass); + int num_color_atts = 0; + int num_resolve_atts = 0; + for (int att_index = 0; att_index < SG_MAX_COLOR_ATTACHMENTS; att_index++) { + if (pass->attachments.colors[att_index].id != SG_INVALID_ID) { + num_color_atts += 1; + } + if (pass->attachments.resolves[att_index].id != SG_INVALID_ID) { + num_resolve_atts += 1; + } + } + bool retval = true; + int max_color_atts = _sg.limits.max_color_attachments; + if (num_color_atts > max_color_atts) { + _SG_ERROR(BEGINPASS_TOO_MANY_COLOR_ATTACHMENTS); + retval = false; + } + // max_color_attachments not a bug + if (num_resolve_atts > max_color_atts) { + _SG_ERROR(BEGINPASS_TOO_MANY_RESOLVE_ATTACHMENTS); + retval = false; + } + return retval; +} + +// ██████ ███████ ███████ ██████ ██ ██ ██████ ██████ ███████ ███████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██████ █████ ███████ ██ ██ ██ ██ ██████ ██ █████ ███████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██ ██ ███████ ███████ ██████ ██████ ██ ██ ██████ ███████ ███████ +// +// >>resources +_SOKOL_PRIVATE sg_buffer_usage _sg_buffer_usage_defaults(const sg_buffer_usage* usg) { + sg_buffer_usage def = *usg; + if (!(def.vertex_buffer || def.index_buffer || def.storage_buffer)) { + def.vertex_buffer = true; + } + if (!(def.immutable || def.stream_update || def.dynamic_update)) { + def.immutable = true; + } + return def; +} + + +_SOKOL_PRIVATE sg_buffer_desc _sg_buffer_desc_defaults(const sg_buffer_desc* desc) { + sg_buffer_desc def = *desc; + def.usage = _sg_buffer_usage_defaults(&def.usage); + if (def.size == 0) { + def.size = def.data.size; + } + return def; +} + +_SOKOL_PRIVATE sg_image_usage _sg_image_usage_defaults(const sg_image_usage *usg) { + sg_image_usage def = *usg; + if (!(def.immutable || def.stream_update || def.dynamic_update)) { + def.immutable = true; + } + return def; +} + +_SOKOL_PRIVATE sg_image_desc _sg_image_desc_defaults(const sg_image_desc* desc) { + sg_image_desc def = *desc; + def.type = _sg_def(def.type, SG_IMAGETYPE_2D); + def.usage = _sg_image_usage_defaults(&def.usage); + def.num_slices = _sg_def(def.num_slices, def.type == SG_IMAGETYPE_CUBE ? 6 : 1); + def.num_mipmaps = _sg_def(def.num_mipmaps, 1); + if (def.usage.color_attachment || def.usage.resolve_attachment) { + def.pixel_format = _sg_def(def.pixel_format, _sg.desc.environment.defaults.color_format); + def.sample_count = _sg_def(def.sample_count, _sg.desc.environment.defaults.sample_count); + } else if (def.usage.depth_stencil_attachment) { + def.pixel_format = _sg_def(def.pixel_format, _sg.desc.environment.defaults.depth_format); + def.sample_count = _sg_def(def.sample_count, _sg.desc.environment.defaults.sample_count); + } else { + def.pixel_format = _sg_def(def.pixel_format, SG_PIXELFORMAT_RGBA8); + def.sample_count = _sg_def(def.sample_count, 1); + } + return def; +} + +_SOKOL_PRIVATE sg_sampler_desc _sg_sampler_desc_defaults(const sg_sampler_desc* desc) { + sg_sampler_desc def = *desc; + def.min_filter = _sg_def(def.min_filter, SG_FILTER_NEAREST); + def.mag_filter = _sg_def(def.mag_filter, SG_FILTER_NEAREST); + def.mipmap_filter = _sg_def(def.mipmap_filter, SG_FILTER_NEAREST); + def.wrap_u = _sg_def(def.wrap_u, SG_WRAP_REPEAT); + def.wrap_v = _sg_def(def.wrap_v, SG_WRAP_REPEAT); + def.wrap_w = _sg_def(def.wrap_w, SG_WRAP_REPEAT); + def.max_lod = _sg_def_flt(def.max_lod, FLT_MAX); + def.border_color = _sg_def(def.border_color, SG_BORDERCOLOR_OPAQUE_BLACK); + def.compare = _sg_def(def.compare, SG_COMPAREFUNC_NEVER); + def.max_anisotropy = _sg_def(def.max_anisotropy, 1); + return def; +} + +_SOKOL_PRIVATE sg_shader_desc _sg_shader_desc_defaults(const sg_shader_desc* desc) { + sg_shader_desc def = *desc; + #if defined(SOKOL_METAL) + def.vertex_func.entry = _sg_def(def.vertex_func.entry, "_main"); + def.fragment_func.entry = _sg_def(def.fragment_func.entry, "_main"); + def.compute_func.entry = _sg_def(def.compute_func.entry, "_main"); + #else + def.vertex_func.entry = _sg_def(def.vertex_func.entry, "main"); + def.fragment_func.entry = _sg_def(def.fragment_func.entry, "main"); + def.compute_func.entry = _sg_def(def.compute_func.entry, "main"); + #endif + #if defined(SOKOL_D3D11) + if (def.vertex_func.source) { + def.vertex_func.d3d11_target = _sg_def(def.vertex_func.d3d11_target, "vs_4_0"); + } + if (def.fragment_func.source) { + def.fragment_func.d3d11_target = _sg_def(def.fragment_func.d3d11_target, "ps_4_0"); + } + if (def.compute_func.source) { + def.compute_func.d3d11_target = _sg_def(def.fragment_func.d3d11_target,"cs_5_0"); + } + #endif + def.mtl_threads_per_threadgroup.y = _sg_def(desc->mtl_threads_per_threadgroup.y, 1); + def.mtl_threads_per_threadgroup.z = _sg_def(desc->mtl_threads_per_threadgroup.z, 1); + for (size_t ub_index = 0; ub_index < SG_MAX_UNIFORMBLOCK_BINDSLOTS; ub_index++) { + sg_shader_uniform_block* ub_desc = &def.uniform_blocks[ub_index]; + if (ub_desc->stage != SG_SHADERSTAGE_NONE) { + ub_desc->layout = _sg_def(ub_desc->layout, SG_UNIFORMLAYOUT_NATIVE); + for (size_t u_index = 0; u_index < SG_MAX_UNIFORMBLOCK_MEMBERS; u_index++) { + sg_glsl_shader_uniform* u_desc = &ub_desc->glsl_uniforms[u_index]; + if (u_desc->type == SG_UNIFORMTYPE_INVALID) { + break; + } + u_desc->array_count = _sg_def(u_desc->array_count, 1); + } + } + } + for (size_t view_index = 0; view_index < SG_MAX_VIEW_BINDSLOTS; view_index++) { + sg_shader_view* view_desc = &def.views[view_index]; + if (view_desc->texture.stage != SG_SHADERSTAGE_NONE) { + view_desc->texture.image_type = _sg_def(view_desc->texture.image_type, SG_IMAGETYPE_2D); + view_desc->texture.sample_type = _sg_def(view_desc->texture.sample_type, SG_IMAGESAMPLETYPE_FLOAT); + } else if (view_desc->storage_image.stage != SG_SHADERSTAGE_NONE) { + view_desc->storage_image.image_type = _sg_def(view_desc->storage_image.image_type, SG_IMAGETYPE_2D); + } + } + for (size_t smp_index = 0; smp_index < SG_MAX_SAMPLER_BINDSLOTS; smp_index++) { + sg_shader_sampler* smp_desc = &def.samplers[smp_index]; + if (smp_desc->stage != SG_SHADERSTAGE_NONE) { + smp_desc->sampler_type = _sg_def(smp_desc->sampler_type, SG_SAMPLERTYPE_FILTERING); + } + } + return def; +} + +_SOKOL_PRIVATE sg_pipeline_desc _sg_pipeline_desc_defaults(const sg_pipeline_desc* desc) { + sg_pipeline_desc def = *desc; + + // FIXME: should we actually do all this stuff for a compute pipeline? + + def.primitive_type = _sg_def(def.primitive_type, SG_PRIMITIVETYPE_TRIANGLES); + def.index_type = _sg_def(def.index_type, SG_INDEXTYPE_NONE); + def.cull_mode = _sg_def(def.cull_mode, SG_CULLMODE_NONE); + def.face_winding = _sg_def(def.face_winding, SG_FACEWINDING_CW); + def.sample_count = _sg_def(def.sample_count, _sg.desc.environment.defaults.sample_count); + + def.stencil.front.compare = _sg_def(def.stencil.front.compare, SG_COMPAREFUNC_ALWAYS); + def.stencil.front.fail_op = _sg_def(def.stencil.front.fail_op, SG_STENCILOP_KEEP); + def.stencil.front.depth_fail_op = _sg_def(def.stencil.front.depth_fail_op, SG_STENCILOP_KEEP); + def.stencil.front.pass_op = _sg_def(def.stencil.front.pass_op, SG_STENCILOP_KEEP); + def.stencil.back.compare = _sg_def(def.stencil.back.compare, SG_COMPAREFUNC_ALWAYS); + def.stencil.back.fail_op = _sg_def(def.stencil.back.fail_op, SG_STENCILOP_KEEP); + def.stencil.back.depth_fail_op = _sg_def(def.stencil.back.depth_fail_op, SG_STENCILOP_KEEP); + def.stencil.back.pass_op = _sg_def(def.stencil.back.pass_op, SG_STENCILOP_KEEP); + + def.depth.compare = _sg_def(def.depth.compare, SG_COMPAREFUNC_ALWAYS); + def.depth.pixel_format = _sg_def(def.depth.pixel_format, _sg.desc.environment.defaults.depth_format); + if (def.colors[0].pixel_format == SG_PIXELFORMAT_NONE) { + // special case depth-only rendering, enforce a color count of 0 + def.color_count = 0; + } else { + def.color_count = _sg_def(def.color_count, 1); + } + if (def.color_count > SG_MAX_COLOR_ATTACHMENTS) { + def.color_count = SG_MAX_COLOR_ATTACHMENTS; + } + for (int i = 0; i < def.color_count; i++) { + sg_color_target_state* cs = &def.colors[i]; + cs->pixel_format = _sg_def(cs->pixel_format, _sg.desc.environment.defaults.color_format); + cs->write_mask = _sg_def(cs->write_mask, SG_COLORMASK_RGBA); + sg_blend_state* bs = &def.colors[i].blend; + bs->op_rgb = _sg_def(bs->op_rgb, SG_BLENDOP_ADD); + bs->src_factor_rgb = _sg_def(bs->src_factor_rgb, SG_BLENDFACTOR_ONE); + if ((bs->op_rgb == SG_BLENDOP_MIN) || (bs->op_rgb == SG_BLENDOP_MAX)) { + bs->dst_factor_rgb = _sg_def(bs->dst_factor_rgb, SG_BLENDFACTOR_ONE); + } else { + bs->dst_factor_rgb = _sg_def(bs->dst_factor_rgb, SG_BLENDFACTOR_ZERO); + } + bs->op_alpha = _sg_def(bs->op_alpha, SG_BLENDOP_ADD); + bs->src_factor_alpha = _sg_def(bs->src_factor_alpha, SG_BLENDFACTOR_ONE); + if ((bs->op_alpha == SG_BLENDOP_MIN) || (bs->op_alpha == SG_BLENDOP_MAX)) { + bs->dst_factor_alpha = _sg_def(bs->dst_factor_alpha, SG_BLENDFACTOR_ONE); + } else { + bs->dst_factor_alpha = _sg_def(bs->dst_factor_alpha, SG_BLENDFACTOR_ZERO); + } + } + + for (int attr_index = 0; attr_index < SG_MAX_VERTEX_ATTRIBUTES; attr_index++) { + sg_vertex_attr_state* a_state = &def.layout.attrs[attr_index]; + if (a_state->format == SG_VERTEXFORMAT_INVALID) { + break; + } + SOKOL_ASSERT(a_state->buffer_index < SG_MAX_VERTEXBUFFER_BINDSLOTS); + sg_vertex_buffer_layout_state* l_state = &def.layout.buffers[a_state->buffer_index]; + l_state->step_func = _sg_def(l_state->step_func, SG_VERTEXSTEP_PER_VERTEX); + l_state->step_rate = _sg_def(l_state->step_rate, 1); + } + + // resolve vertex layout strides and offsets + int auto_offset[SG_MAX_VERTEXBUFFER_BINDSLOTS]; + _sg_clear(auto_offset, sizeof(auto_offset)); + bool use_auto_offset = true; + for (int attr_index = 0; attr_index < SG_MAX_VERTEX_ATTRIBUTES; attr_index++) { + // to use computed offsets, *all* attr offsets must be 0 + if (def.layout.attrs[attr_index].offset != 0) { + use_auto_offset = false; + } + } + for (int attr_index = 0; attr_index < SG_MAX_VERTEX_ATTRIBUTES; attr_index++) { + sg_vertex_attr_state* a_state = &def.layout.attrs[attr_index]; + if (a_state->format == SG_VERTEXFORMAT_INVALID) { + break; + } + SOKOL_ASSERT(a_state->buffer_index < SG_MAX_VERTEXBUFFER_BINDSLOTS); + if (use_auto_offset) { + a_state->offset = auto_offset[a_state->buffer_index]; + } + auto_offset[a_state->buffer_index] += _sg_vertexformat_bytesize(a_state->format); + } + // compute vertex strides if needed + for (int buf_index = 0; buf_index < SG_MAX_VERTEXBUFFER_BINDSLOTS; buf_index++) { + sg_vertex_buffer_layout_state* l_state = &def.layout.buffers[buf_index]; + if (l_state->stride == 0) { + l_state->stride = auto_offset[buf_index]; + } + } + + return def; +} + +_SOKOL_PRIVATE sg_view_desc _sg_view_desc_defaults(const sg_view_desc* desc) { + sg_view_desc def = *desc; + return def; +} + +_SOKOL_PRIVATE sg_buffer _sg_alloc_buffer(void) { + sg_buffer res; + int slot_index = _sg_pool_alloc_index(&_sg.pools.buffer_pool); + if (_SG_INVALID_SLOT_INDEX != slot_index) { + res.id = _sg_slot_alloc(&_sg.pools.buffer_pool, &_sg.pools.buffers[slot_index].slot, slot_index); + _sg_stats_add(buffers.allocated, 1); + } else { + res.id = SG_INVALID_ID; + _SG_ERROR(BUFFER_POOL_EXHAUSTED); + } + return res; +} + +_SOKOL_PRIVATE sg_image _sg_alloc_image(void) { + sg_image res; + int slot_index = _sg_pool_alloc_index(&_sg.pools.image_pool); + if (_SG_INVALID_SLOT_INDEX != slot_index) { + res.id = _sg_slot_alloc(&_sg.pools.image_pool, &_sg.pools.images[slot_index].slot, slot_index); + _sg_stats_add(images.allocated, 1); + } else { + res.id = SG_INVALID_ID; + _SG_ERROR(IMAGE_POOL_EXHAUSTED); + } + return res; +} + +_SOKOL_PRIVATE sg_sampler _sg_alloc_sampler(void) { + sg_sampler res; + int slot_index = _sg_pool_alloc_index(&_sg.pools.sampler_pool); + if (_SG_INVALID_SLOT_INDEX != slot_index) { + res.id = _sg_slot_alloc(&_sg.pools.sampler_pool, &_sg.pools.samplers[slot_index].slot, slot_index); + _sg_stats_add(samplers.allocated, 1); + } else { + res.id = SG_INVALID_ID; + _SG_ERROR(SAMPLER_POOL_EXHAUSTED); + } + return res; +} + +_SOKOL_PRIVATE sg_shader _sg_alloc_shader(void) { + sg_shader res; + int slot_index = _sg_pool_alloc_index(&_sg.pools.shader_pool); + if (_SG_INVALID_SLOT_INDEX != slot_index) { + res.id = _sg_slot_alloc(&_sg.pools.shader_pool, &_sg.pools.shaders[slot_index].slot, slot_index); + _sg_stats_add(shaders.allocated, 1); + } else { + res.id = SG_INVALID_ID; + _SG_ERROR(SHADER_POOL_EXHAUSTED); + } + return res; +} + +_SOKOL_PRIVATE sg_pipeline _sg_alloc_pipeline(void) { + sg_pipeline res; + int slot_index = _sg_pool_alloc_index(&_sg.pools.pipeline_pool); + if (_SG_INVALID_SLOT_INDEX != slot_index) { + res.id =_sg_slot_alloc(&_sg.pools.pipeline_pool, &_sg.pools.pipelines[slot_index].slot, slot_index); + _sg_stats_add(pipelines.allocated, 1); + } else { + res.id = SG_INVALID_ID; + _SG_ERROR(PIPELINE_POOL_EXHAUSTED); + } + return res; +} + +_SOKOL_PRIVATE sg_view _sg_alloc_view(void) { + sg_view res; + int slot_index = _sg_pool_alloc_index(&_sg.pools.view_pool); + if (_SG_INVALID_SLOT_INDEX != slot_index) { + res.id = _sg_slot_alloc(&_sg.pools.view_pool, &_sg.pools.views[slot_index].slot, slot_index); + _sg_stats_add(views.allocated, 1); + } else { + res.id = SG_INVALID_ID; + _SG_ERROR(VIEW_POOL_EXHAUSTED); + } + return res; +} + +_SOKOL_PRIVATE void _sg_dealloc_buffer(_sg_buffer_t* buf) { + SOKOL_ASSERT(buf && (buf->slot.state == SG_RESOURCESTATE_ALLOC) && (buf->slot.id != SG_INVALID_ID)); + _sg_pool_free_index(&_sg.pools.buffer_pool, _sg_slot_index(buf->slot.id)); + _sg_slot_reset(&buf->slot); + _sg_stats_add(buffers.deallocated, 1); +} + +_SOKOL_PRIVATE void _sg_dealloc_image(_sg_image_t* img) { + SOKOL_ASSERT(img && (img->slot.state == SG_RESOURCESTATE_ALLOC) && (img->slot.id != SG_INVALID_ID)); + _sg_pool_free_index(&_sg.pools.image_pool, _sg_slot_index(img->slot.id)); + _sg_slot_reset(&img->slot); + _sg_stats_add(images.deallocated, 1); +} + +_SOKOL_PRIVATE void _sg_dealloc_sampler(_sg_sampler_t* smp) { + SOKOL_ASSERT(smp && (smp->slot.state == SG_RESOURCESTATE_ALLOC) && (smp->slot.id != SG_INVALID_ID)); + _sg_pool_free_index(&_sg.pools.sampler_pool, _sg_slot_index(smp->slot.id)); + _sg_slot_reset(&smp->slot); + _sg_stats_add(samplers.deallocated, 1); +} + +_SOKOL_PRIVATE void _sg_dealloc_shader(_sg_shader_t* shd) { + SOKOL_ASSERT(shd && (shd->slot.state == SG_RESOURCESTATE_ALLOC) && (shd->slot.id != SG_INVALID_ID)); + _sg_pool_free_index(&_sg.pools.shader_pool, _sg_slot_index(shd->slot.id)); + _sg_slot_reset(&shd->slot); + _sg_stats_add(shaders.deallocated, 1); +} + +_SOKOL_PRIVATE void _sg_dealloc_pipeline(_sg_pipeline_t* pip) { + SOKOL_ASSERT(pip && (pip->slot.state == SG_RESOURCESTATE_ALLOC) && (pip->slot.id != SG_INVALID_ID)); + _sg_pool_free_index(&_sg.pools.pipeline_pool, _sg_slot_index(pip->slot.id)); + _sg_slot_reset(&pip->slot); + _sg_stats_add(pipelines.deallocated, 1); +} + +_SOKOL_PRIVATE void _sg_dealloc_view(_sg_view_t* view) { + SOKOL_ASSERT(view && (view->slot.state == SG_RESOURCESTATE_ALLOC) && (view->slot.id != SG_INVALID_ID)); + _sg_pool_free_index(&_sg.pools.view_pool, _sg_slot_index(view->slot.id)); + _sg_slot_reset(&view->slot); + _sg_stats_add(views.deallocated, 1); +} + +_SOKOL_PRIVATE void _sg_init_buffer(_sg_buffer_t* buf, const sg_buffer_desc* desc) { + SOKOL_ASSERT(buf && (buf->slot.state == SG_RESOURCESTATE_ALLOC)); + SOKOL_ASSERT(desc); + if (_sg_validate_buffer_desc(desc)) { + _sg_buffer_common_init(&buf->cmn, desc); + buf->slot.state = _sg_create_buffer(buf, desc); + } else { + buf->slot.state = SG_RESOURCESTATE_FAILED; + } + SOKOL_ASSERT((buf->slot.state == SG_RESOURCESTATE_VALID)||(buf->slot.state == SG_RESOURCESTATE_FAILED)); + _sg_stats_add(buffers.inited, 1); +} + +_SOKOL_PRIVATE void _sg_init_image(_sg_image_t* img, const sg_image_desc* desc) { + SOKOL_ASSERT(img && (img->slot.state == SG_RESOURCESTATE_ALLOC)); + SOKOL_ASSERT(desc); + if (_sg_validate_image_desc(desc)) { + _sg_image_common_init(&img->cmn, desc); + img->slot.state = _sg_create_image(img, desc); + } else { + img->slot.state = SG_RESOURCESTATE_FAILED; + } + SOKOL_ASSERT((img->slot.state == SG_RESOURCESTATE_VALID)||(img->slot.state == SG_RESOURCESTATE_FAILED)); + _sg_stats_add(images.inited, 1); +} + +_SOKOL_PRIVATE void _sg_init_sampler(_sg_sampler_t* smp, const sg_sampler_desc* desc) { + SOKOL_ASSERT(smp && (smp->slot.state == SG_RESOURCESTATE_ALLOC)); + SOKOL_ASSERT(desc); + if (_sg_validate_sampler_desc(desc)) { + _sg_sampler_common_init(&smp->cmn, desc); + smp->slot.state = _sg_create_sampler(smp, desc); + } else { + smp->slot.state = SG_RESOURCESTATE_FAILED; + } + SOKOL_ASSERT((smp->slot.state == SG_RESOURCESTATE_VALID)||(smp->slot.state == SG_RESOURCESTATE_FAILED)); + _sg_stats_add(samplers.inited, 1); +} + +_SOKOL_PRIVATE void _sg_init_shader(_sg_shader_t* shd, const sg_shader_desc* desc) { + SOKOL_ASSERT(shd && (shd->slot.state == SG_RESOURCESTATE_ALLOC)); + SOKOL_ASSERT(desc); + if (!_sg_validate_shader_desc(desc)) { + shd->slot.state = SG_RESOURCESTATE_FAILED; + return; + } + if (!_sg_validate_shader_binding_limits(desc)) { + shd->slot.state = SG_RESOURCESTATE_FAILED; + return; + } + _sg_shader_common_init(&shd->cmn, desc); + shd->slot.state = _sg_create_shader(shd, desc); + SOKOL_ASSERT((shd->slot.state == SG_RESOURCESTATE_VALID)||(shd->slot.state == SG_RESOURCESTATE_FAILED)); + _sg_stats_add(shaders.inited, 1); +} + +_SOKOL_PRIVATE void _sg_init_pipeline(_sg_pipeline_t* pip, const sg_pipeline_desc* desc) { + SOKOL_ASSERT(pip && (pip->slot.state == SG_RESOURCESTATE_ALLOC)); + SOKOL_ASSERT(desc); + if (_sg_validate_pipeline_desc(desc)) { + _sg_shader_t* shd = _sg_lookup_shader(desc->shader.id); + if (shd && (shd->slot.state == SG_RESOURCESTATE_VALID)) { + _sg_pipeline_common_init(&pip->cmn, desc, shd); + pip->slot.state = _sg_create_pipeline(pip, desc); + } else { + pip->slot.state = SG_RESOURCESTATE_FAILED; + } + } else { + pip->slot.state = SG_RESOURCESTATE_FAILED; + } + SOKOL_ASSERT((pip->slot.state == SG_RESOURCESTATE_VALID)||(pip->slot.state == SG_RESOURCESTATE_FAILED)); + _sg_stats_add(pipelines.inited, 1); +} + +_SOKOL_PRIVATE void _sg_init_view(_sg_view_t* view, const sg_view_desc* desc) { + SOKOL_ASSERT(view && view->slot.state == SG_RESOURCESTATE_ALLOC); + SOKOL_ASSERT(desc); + if (_sg_validate_view_desc(desc)) { + uint32_t buf_id = desc->storage_buffer.buffer.id; + uint32_t img_id = desc->texture.image.id; + img_id = img_id ? img_id : desc->storage_image.image.id; + img_id = img_id ? img_id : desc->color_attachment.image.id; + img_id = img_id ? img_id : desc->resolve_attachment.image.id; + img_id = img_id ? img_id : desc->depth_stencil_attachment.image.id; + _sg_buffer_t* buf = buf_id ? _sg_lookup_buffer(buf_id) : 0; + _sg_image_t* img = img_id ? _sg_lookup_image(img_id) : 0; + sg_resource_state res_state = SG_RESOURCESTATE_INVALID; + if (buf) { + SOKOL_ASSERT(!img); + res_state = buf->slot.state; + } else if (img) { + SOKOL_ASSERT(!buf); + res_state = img->slot.state; + } + if (res_state == SG_RESOURCESTATE_VALID) { + _sg_view_common_init(&view->cmn, desc, buf, img); + view->slot.state = _sg_create_view(view, desc); + } else { + view->slot.state = SG_RESOURCESTATE_FAILED; + } + } else { + view->slot.state = SG_RESOURCESTATE_FAILED; + } + SOKOL_ASSERT((view->slot.state == SG_RESOURCESTATE_VALID) || (view->slot.state == SG_RESOURCESTATE_FAILED)); + _sg_stats_add(views.inited, 1); +} + +_SOKOL_PRIVATE void _sg_uninit_buffer(_sg_buffer_t* buf) { + SOKOL_ASSERT(buf && ((buf->slot.state == SG_RESOURCESTATE_VALID) || (buf->slot.state == SG_RESOURCESTATE_FAILED))); + _sg_discard_buffer(buf); + _sg_reset_buffer_to_alloc_state(buf); + _sg_stats_add(buffers.uninited, 1); +} + +_SOKOL_PRIVATE void _sg_uninit_image(_sg_image_t* img) { + SOKOL_ASSERT(img && ((img->slot.state == SG_RESOURCESTATE_VALID) || (img->slot.state == SG_RESOURCESTATE_FAILED))); + _sg_discard_image(img); + _sg_reset_image_to_alloc_state(img); + _sg_stats_add(images.uninited, 1); +} + +_SOKOL_PRIVATE void _sg_uninit_sampler(_sg_sampler_t* smp) { + SOKOL_ASSERT(smp && ((smp->slot.state == SG_RESOURCESTATE_VALID) || (smp->slot.state == SG_RESOURCESTATE_FAILED))); + _sg_discard_sampler(smp); + _sg_reset_sampler_to_alloc_state(smp); + _sg_stats_add(samplers.uninited, 1); +} + +_SOKOL_PRIVATE void _sg_uninit_shader(_sg_shader_t* shd) { + SOKOL_ASSERT(shd && ((shd->slot.state == SG_RESOURCESTATE_VALID) || (shd->slot.state == SG_RESOURCESTATE_FAILED))); + _sg_discard_shader(shd); + _sg_reset_shader_to_alloc_state(shd); + _sg_stats_add(shaders.uninited, 1); +} + +_SOKOL_PRIVATE void _sg_uninit_pipeline(_sg_pipeline_t* pip) { + SOKOL_ASSERT(pip && ((pip->slot.state == SG_RESOURCESTATE_VALID) || (pip->slot.state == SG_RESOURCESTATE_FAILED))); + _sg_discard_pipeline(pip); + _sg_reset_pipeline_to_alloc_state(pip); + _sg_stats_add(pipelines.uninited, 1); +} + +_SOKOL_PRIVATE void _sg_uninit_view(_sg_view_t* view) { + SOKOL_ASSERT(view && ((view->slot.state == SG_RESOURCESTATE_VALID) || (view->slot.state == SG_RESOURCESTATE_FAILED))); + _sg_discard_view(view); + _sg_reset_view_to_alloc_state(view); + _sg_stats_add(views.uninited, 1); +} + +_SOKOL_PRIVATE void _sg_setup_commit_listeners(const sg_desc* desc) { + SOKOL_ASSERT(desc->max_commit_listeners > 0); + SOKOL_ASSERT(0 == _sg.commit_listeners.items); + SOKOL_ASSERT(0 == _sg.commit_listeners.num); + SOKOL_ASSERT(0 == _sg.commit_listeners.upper); + _sg.commit_listeners.num = desc->max_commit_listeners; + const size_t size = (size_t)_sg.commit_listeners.num * sizeof(sg_commit_listener); + _sg.commit_listeners.items = (sg_commit_listener*)_sg_malloc_clear(size); +} + +_SOKOL_PRIVATE void _sg_discard_commit_listeners(void) { + SOKOL_ASSERT(0 != _sg.commit_listeners.items); + _sg_free(_sg.commit_listeners.items); + _sg.commit_listeners.items = 0; +} + +_SOKOL_PRIVATE void _sg_notify_commit_listeners(void) { + SOKOL_ASSERT(_sg.commit_listeners.items); + for (int i = 0; i < _sg.commit_listeners.upper; i++) { + const sg_commit_listener* listener = &_sg.commit_listeners.items[i]; + if (listener->func) { + listener->func(listener->user_data); + } + } +} + +_SOKOL_PRIVATE bool _sg_add_commit_listener(const sg_commit_listener* new_listener) { + SOKOL_ASSERT(new_listener && new_listener->func); + SOKOL_ASSERT(_sg.commit_listeners.items); + // first check if the listener hadn't been added already + for (int i = 0; i < _sg.commit_listeners.upper; i++) { + const sg_commit_listener* slot = &_sg.commit_listeners.items[i]; + if ((slot->func == new_listener->func) && (slot->user_data == new_listener->user_data)) { + _SG_ERROR(IDENTICAL_COMMIT_LISTENER); + return false; + } + } + // first try to plug a hole + sg_commit_listener* slot = 0; + for (int i = 0; i < _sg.commit_listeners.upper; i++) { + if (_sg.commit_listeners.items[i].func == 0) { + slot = &_sg.commit_listeners.items[i]; + break; + } + } + if (!slot) { + // append to end + if (_sg.commit_listeners.upper < _sg.commit_listeners.num) { + slot = &_sg.commit_listeners.items[_sg.commit_listeners.upper++]; + } + } + if (!slot) { + _SG_ERROR(COMMIT_LISTENER_ARRAY_FULL); + return false; + } + *slot = *new_listener; + return true; +} + +_SOKOL_PRIVATE bool _sg_remove_commit_listener(const sg_commit_listener* listener) { + SOKOL_ASSERT(listener && listener->func); + SOKOL_ASSERT(_sg.commit_listeners.items); + for (int i = 0; i < _sg.commit_listeners.upper; i++) { + sg_commit_listener* slot = &_sg.commit_listeners.items[i]; + // both the function pointer and user data must match! + if ((slot->func == listener->func) && (slot->user_data == listener->user_data)) { + slot->func = 0; + slot->user_data = 0; + // NOTE: since _sg_add_commit_listener() already catches duplicates, + // we don't need to worry about them here + return true; + } + } + return false; +} + +_SOKOL_PRIVATE sg_desc _sg_desc_defaults(const sg_desc* desc) { + /* + NOTE: on WebGPU, the default color pixel format MUST be provided, + it cannot be a default compile-time constant. + */ + sg_desc res = *desc; + #if defined(SOKOL_WGPU) + SOKOL_ASSERT(SG_PIXELFORMAT_NONE < res.environment.defaults.color_format); + #elif defined(SOKOL_METAL) || defined(SOKOL_D3D11) + res.environment.defaults.color_format = _sg_def(res.environment.defaults.color_format, SG_PIXELFORMAT_BGRA8); + #else + res.environment.defaults.color_format = _sg_def(res.environment.defaults.color_format, SG_PIXELFORMAT_RGBA8); + #endif + res.environment.defaults.depth_format = _sg_def(res.environment.defaults.depth_format, SG_PIXELFORMAT_DEPTH_STENCIL); + res.environment.defaults.sample_count = _sg_def(res.environment.defaults.sample_count, 1); + res.buffer_pool_size = _sg_def(res.buffer_pool_size, _SG_DEFAULT_BUFFER_POOL_SIZE); + res.image_pool_size = _sg_def(res.image_pool_size, _SG_DEFAULT_IMAGE_POOL_SIZE); + res.sampler_pool_size = _sg_def(res.sampler_pool_size, _SG_DEFAULT_SAMPLER_POOL_SIZE); + res.shader_pool_size = _sg_def(res.shader_pool_size, _SG_DEFAULT_SHADER_POOL_SIZE); + res.pipeline_pool_size = _sg_def(res.pipeline_pool_size, _SG_DEFAULT_PIPELINE_POOL_SIZE); + res.view_pool_size = _sg_def(res.view_pool_size, _SG_DEFAULT_VIEW_POOL_SIZE); + res.uniform_buffer_size = _sg_def(res.uniform_buffer_size, _SG_DEFAULT_UB_SIZE); + res.max_commit_listeners = _sg_def(res.max_commit_listeners, _SG_DEFAULT_MAX_COMMIT_LISTENERS); + res.wgpu_bindgroups_cache_size = _sg_def(res.wgpu_bindgroups_cache_size, _SG_DEFAULT_WGPU_BINDGROUP_CACHE_SIZE); + return res; +} + +_SOKOL_PRIVATE sg_pass _sg_pass_defaults(const sg_pass* pass) { + sg_pass res = *pass; + if (!res.compute) { + if (_sg_attachments_empty(&pass->attachments)) { + // this is a swapchain-pass + res.swapchain.sample_count = _sg_def(res.swapchain.sample_count, _sg.desc.environment.defaults.sample_count); + res.swapchain.color_format = _sg_def(res.swapchain.color_format, _sg.desc.environment.defaults.color_format); + res.swapchain.depth_format = _sg_def(res.swapchain.depth_format, _sg.desc.environment.defaults.depth_format); + } + res.action = _sg_pass_action_defaults(&res.action); + } + return res; +} + +_SOKOL_PRIVATE void _sg_discard_all_resources(void) { + /* this is a bit dumb since it loops over all pool slots to + find the occupied slots, on the other hand it is only ever + executed at shutdown + NOTE: ONLY EXECUTE THIS AT SHUTDOWN + ...because the free queues will not be reset + and the resource slots not be cleared! + */ + for (int i = 1; i < _sg.pools.buffer_pool.size; i++) { + sg_resource_state state = _sg.pools.buffers[i].slot.state; + if ((state == SG_RESOURCESTATE_VALID) || (state == SG_RESOURCESTATE_FAILED)) { + _sg_discard_buffer(&_sg.pools.buffers[i]); + } + } + for (int i = 1; i < _sg.pools.image_pool.size; i++) { + sg_resource_state state = _sg.pools.images[i].slot.state; + if ((state == SG_RESOURCESTATE_VALID) || (state == SG_RESOURCESTATE_FAILED)) { + _sg_discard_image(&_sg.pools.images[i]); + } + } + for (int i = 1; i < _sg.pools.sampler_pool.size; i++) { + sg_resource_state state = _sg.pools.samplers[i].slot.state; + if ((state == SG_RESOURCESTATE_VALID) || (state == SG_RESOURCESTATE_FAILED)) { + _sg_discard_sampler(&_sg.pools.samplers[i]); + } + } + for (int i = 1; i < _sg.pools.shader_pool.size; i++) { + sg_resource_state state = _sg.pools.shaders[i].slot.state; + if ((state == SG_RESOURCESTATE_VALID) || (state == SG_RESOURCESTATE_FAILED)) { + _sg_discard_shader(&_sg.pools.shaders[i]); + } + } + for (int i = 1; i < _sg.pools.pipeline_pool.size; i++) { + sg_resource_state state = _sg.pools.pipelines[i].slot.state; + if ((state == SG_RESOURCESTATE_VALID) || (state == SG_RESOURCESTATE_FAILED)) { + _sg_discard_pipeline(&_sg.pools.pipelines[i]); + } + } + for (int i = 1; i < _sg.pools.view_pool.size; i++) { + sg_resource_state state = _sg.pools.views[i].slot.state; + if ((state == SG_RESOURCESTATE_VALID) || (state == SG_RESOURCESTATE_FAILED)) { + _sg_discard_view(&_sg.pools.views[i]); + } + } +} + +_SOKOL_PRIVATE void _sg_override_portable_limits(void) { + if (_sg.desc.enforce_portable_limits) { + _sg.limits.max_color_attachments = SG_MAX_PORTABLE_COLOR_ATTACHMENTS; + _sg.limits.max_texture_bindings_per_stage = SG_MAX_PORTABLE_TEXTURE_BINDINGS_PER_STAGE; + if (_sg.features.compute) { + _sg.limits.max_storage_buffer_bindings_per_stage = SG_MAX_PORTABLE_STORAGEBUFFER_BINDINGS_PER_STAGE; + _sg.limits.max_storage_image_bindings_per_stage = SG_MAX_PORTABLE_STORAGEIMAGE_BINDINGS_PER_STAGE; + } + } +} + +// ██████ ██ ██ ██████ ██ ██ ██████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██████ ██ ██ ██████ ██ ██ ██ +// ██ ██ ██ ██ ██ ██ ██ ██ +// ██ ██████ ██████ ███████ ██ ██████ +// +// >>public +SOKOL_API_IMPL void sg_setup(const sg_desc* desc) { + SOKOL_ASSERT(desc); + SOKOL_ASSERT((desc->_start_canary == 0) && (desc->_end_canary == 0)); + SOKOL_ASSERT((desc->allocator.alloc_fn && desc->allocator.free_fn) || (!desc->allocator.alloc_fn && !desc->allocator.free_fn)); + _SG_CLEAR_ARC_STRUCT(_sg_state_t, _sg); + _sg.desc = _sg_desc_defaults(desc); + _sg_setup_pools(&_sg.pools, &_sg.desc); + _sg_setup_commit_listeners(&_sg.desc); + _sg.frame_index = 1; + _sg.stats_enabled = true; + _sg_setup_backend(&_sg.desc); + _sg_override_portable_limits(); + _sg.valid = true; +} + +SOKOL_API_IMPL void sg_shutdown(void) { + _sg_discard_all_resources(); + _sg_discard_backend(); + _sg_discard_commit_listeners(); + _sg_discard_pools(&_sg.pools); + _SG_CLEAR_ARC_STRUCT(_sg_state_t, _sg); +} + +SOKOL_API_IMPL bool sg_isvalid(void) { + return _sg.valid; +} + +SOKOL_API_IMPL sg_desc sg_query_desc(void) { + SOKOL_ASSERT(_sg.valid); + return _sg.desc; +} + +SOKOL_API_IMPL sg_backend sg_query_backend(void) { + SOKOL_ASSERT(_sg.valid); + return _sg.backend; +} + +SOKOL_API_IMPL sg_features sg_query_features(void) { + SOKOL_ASSERT(_sg.valid); + return _sg.features; +} + +SOKOL_API_IMPL sg_limits sg_query_limits(void) { + SOKOL_ASSERT(_sg.valid); + return _sg.limits; +} + +SOKOL_API_IMPL sg_pixelformat_info sg_query_pixelformat(sg_pixel_format fmt) { + SOKOL_ASSERT(_sg.valid); + int fmt_index = (int) fmt; + SOKOL_ASSERT((fmt_index > SG_PIXELFORMAT_NONE) && (fmt_index < _SG_PIXELFORMAT_NUM)); + const _sg_pixelformat_info_t* src = &_sg.formats[fmt_index]; + sg_pixelformat_info res; + _sg_clear(&res, sizeof(res)); + res.sample = src->sample; + res.filter = src->filter; + res.render = src->render; + res.blend = src->blend; + res.msaa = src->msaa; + res.depth = src->depth; + res.compressed = _sg_is_compressed_pixel_format(fmt); + res.read = src->read; + res.write = src->write; + if (!res.compressed) { + res.bytes_per_pixel = _sg_pixelformat_bytesize(fmt); + } + return res; +} + +SOKOL_API_IMPL int sg_query_row_pitch(sg_pixel_format fmt, int width, int row_align_bytes) { + SOKOL_ASSERT(_sg.valid); + SOKOL_ASSERT(width > 0); + SOKOL_ASSERT((row_align_bytes > 0) && _sg_ispow2(row_align_bytes)); + SOKOL_ASSERT(((int)fmt > SG_PIXELFORMAT_NONE) && ((int)fmt < _SG_PIXELFORMAT_NUM)); + return _sg_row_pitch(fmt, width, row_align_bytes); +} + +SOKOL_API_IMPL int sg_query_surface_pitch(sg_pixel_format fmt, int width, int height, int row_align_bytes) { + SOKOL_ASSERT(_sg.valid); + SOKOL_ASSERT((width > 0) && (height > 0)); + SOKOL_ASSERT((row_align_bytes > 0) && _sg_ispow2(row_align_bytes)); + SOKOL_ASSERT(((int)fmt > SG_PIXELFORMAT_NONE) && ((int)fmt < _SG_PIXELFORMAT_NUM)); + return _sg_surface_pitch(fmt, width, height, row_align_bytes); +} + +SOKOL_API_IMPL sg_frame_stats sg_query_frame_stats(void) { + SOKOL_ASSERT(_sg.valid); + return _sg.prev_stats; +} + +SOKOL_API_IMPL sg_trace_hooks sg_install_trace_hooks(const sg_trace_hooks* trace_hooks) { + SOKOL_ASSERT(_sg.valid); + SOKOL_ASSERT(trace_hooks); + _SOKOL_UNUSED(trace_hooks); + #if defined(SOKOL_TRACE_HOOKS) + sg_trace_hooks old_hooks = _sg.hooks; + _sg.hooks = *trace_hooks; + #else + static sg_trace_hooks old_hooks; + _SG_WARN(TRACE_HOOKS_NOT_ENABLED); + #endif + return old_hooks; +} + +SOKOL_API_IMPL sg_buffer sg_alloc_buffer(void) { + SOKOL_ASSERT(_sg.valid); + sg_buffer res = _sg_alloc_buffer(); + _SG_TRACE_ARGS(alloc_buffer, res); + return res; +} + +SOKOL_API_IMPL sg_image sg_alloc_image(void) { + SOKOL_ASSERT(_sg.valid); + sg_image res = _sg_alloc_image(); + _SG_TRACE_ARGS(alloc_image, res); + return res; +} + +SOKOL_API_IMPL sg_sampler sg_alloc_sampler(void) { + SOKOL_ASSERT(_sg.valid); + sg_sampler res = _sg_alloc_sampler(); + _SG_TRACE_ARGS(alloc_sampler, res); + return res; +} + +SOKOL_API_IMPL sg_shader sg_alloc_shader(void) { + SOKOL_ASSERT(_sg.valid); + sg_shader res = _sg_alloc_shader(); + _SG_TRACE_ARGS(alloc_shader, res); + return res; +} + +SOKOL_API_IMPL sg_pipeline sg_alloc_pipeline(void) { + SOKOL_ASSERT(_sg.valid); + sg_pipeline res = _sg_alloc_pipeline(); + _SG_TRACE_ARGS(alloc_pipeline, res); + return res; +} + +SOKOL_API_IMPL sg_view sg_alloc_view(void) { + SOKOL_ASSERT(_sg.valid); + sg_view res = _sg_alloc_view(); + _SG_TRACE_ARGS(alloc_view, res); + return res; +} + +SOKOL_API_IMPL void sg_dealloc_buffer(sg_buffer buf_id) { + SOKOL_ASSERT(_sg.valid); + _sg_buffer_t* buf = _sg_lookup_buffer(buf_id.id); + if (buf) { + if (buf->slot.state == SG_RESOURCESTATE_ALLOC) { + _sg_dealloc_buffer(buf); + } else { + _SG_ERROR(DEALLOC_BUFFER_INVALID_STATE); + } + } + _SG_TRACE_ARGS(dealloc_buffer, buf_id); +} + +SOKOL_API_IMPL void sg_dealloc_image(sg_image img_id) { + SOKOL_ASSERT(_sg.valid); + _sg_image_t* img = _sg_lookup_image(img_id.id); + if (img) { + if (img->slot.state == SG_RESOURCESTATE_ALLOC) { + _sg_dealloc_image(img); + } else { + _SG_ERROR(DEALLOC_IMAGE_INVALID_STATE); + } + } + _SG_TRACE_ARGS(dealloc_image, img_id); +} + +SOKOL_API_IMPL void sg_dealloc_sampler(sg_sampler smp_id) { + SOKOL_ASSERT(_sg.valid); + _sg_sampler_t* smp = _sg_lookup_sampler(smp_id.id); + if (smp) { + if (smp->slot.state == SG_RESOURCESTATE_ALLOC) { + _sg_dealloc_sampler(smp); + } else { + _SG_ERROR(DEALLOC_SAMPLER_INVALID_STATE); + } + } + _SG_TRACE_ARGS(dealloc_sampler, smp_id); +} + +SOKOL_API_IMPL void sg_dealloc_shader(sg_shader shd_id) { + SOKOL_ASSERT(_sg.valid); + _sg_shader_t* shd = _sg_lookup_shader(shd_id.id); + if (shd) { + if (shd->slot.state == SG_RESOURCESTATE_ALLOC) { + _sg_dealloc_shader(shd); + } else { + _SG_ERROR(DEALLOC_SHADER_INVALID_STATE); + } + } + _SG_TRACE_ARGS(dealloc_shader, shd_id); +} + +SOKOL_API_IMPL void sg_dealloc_pipeline(sg_pipeline pip_id) { + SOKOL_ASSERT(_sg.valid); + _sg_pipeline_t* pip = _sg_lookup_pipeline(pip_id.id); + if (pip) { + if (pip->slot.state == SG_RESOURCESTATE_ALLOC) { + _sg_dealloc_pipeline(pip); + } else { + _SG_ERROR(DEALLOC_PIPELINE_INVALID_STATE); + } + } + _SG_TRACE_ARGS(dealloc_pipeline, pip_id); +} + +SOKOL_API_IMPL void sg_dealloc_view(sg_view view_id) { + SOKOL_ASSERT(_sg.valid); + _sg_view_t* view = _sg_lookup_view(view_id.id); + if (view) { + if (view->slot.state == SG_RESOURCESTATE_ALLOC) { + _sg_dealloc_view(view); + } else { + _SG_ERROR(DEALLOC_VIEW_INVALID_STATE); + } + } + _SG_TRACE_ARGS(dealloc_view, view_id); +} + +SOKOL_API_IMPL void sg_init_buffer(sg_buffer buf_id, const sg_buffer_desc* desc) { + SOKOL_ASSERT(_sg.valid); + sg_buffer_desc desc_def = _sg_buffer_desc_defaults(desc); + _sg_buffer_t* buf = _sg_lookup_buffer(buf_id.id); + if (buf) { + if (buf->slot.state == SG_RESOURCESTATE_ALLOC) { + _sg_init_buffer(buf, &desc_def); + SOKOL_ASSERT((buf->slot.state == SG_RESOURCESTATE_VALID) || (buf->slot.state == SG_RESOURCESTATE_FAILED)); + } else { + _SG_ERROR(INIT_BUFFER_INVALID_STATE); + } + } + _SG_TRACE_ARGS(init_buffer, buf_id, &desc_def); +} + +SOKOL_API_IMPL void sg_init_image(sg_image img_id, const sg_image_desc* desc) { + SOKOL_ASSERT(_sg.valid); + sg_image_desc desc_def = _sg_image_desc_defaults(desc); + _sg_image_t* img = _sg_lookup_image(img_id.id); + if (img) { + if (img->slot.state == SG_RESOURCESTATE_ALLOC) { + _sg_init_image(img, &desc_def); + SOKOL_ASSERT((img->slot.state == SG_RESOURCESTATE_VALID) || (img->slot.state == SG_RESOURCESTATE_FAILED)); + } else { + _SG_ERROR(INIT_IMAGE_INVALID_STATE); + } + } + _SG_TRACE_ARGS(init_image, img_id, &desc_def); +} + +SOKOL_API_IMPL void sg_init_sampler(sg_sampler smp_id, const sg_sampler_desc* desc) { + SOKOL_ASSERT(_sg.valid); + sg_sampler_desc desc_def = _sg_sampler_desc_defaults(desc); + _sg_sampler_t* smp = _sg_lookup_sampler(smp_id.id); + if (smp) { + if (smp->slot.state == SG_RESOURCESTATE_ALLOC) { + _sg_init_sampler(smp, &desc_def); + SOKOL_ASSERT((smp->slot.state == SG_RESOURCESTATE_VALID) || (smp->slot.state == SG_RESOURCESTATE_FAILED)); + } else { + _SG_ERROR(INIT_SAMPLER_INVALID_STATE); + } + } + _SG_TRACE_ARGS(init_sampler, smp_id, &desc_def); +} + +SOKOL_API_IMPL void sg_init_shader(sg_shader shd_id, const sg_shader_desc* desc) { + SOKOL_ASSERT(_sg.valid); + sg_shader_desc desc_def = _sg_shader_desc_defaults(desc); + _sg_shader_t* shd = _sg_lookup_shader(shd_id.id); + if (shd) { + if (shd->slot.state == SG_RESOURCESTATE_ALLOC) { + _sg_init_shader(shd, &desc_def); + SOKOL_ASSERT((shd->slot.state == SG_RESOURCESTATE_VALID) || (shd->slot.state == SG_RESOURCESTATE_FAILED)); + } else { + _SG_ERROR(INIT_SHADER_INVALID_STATE); + } + } + _SG_TRACE_ARGS(init_shader, shd_id, &desc_def); +} + +SOKOL_API_IMPL void sg_init_pipeline(sg_pipeline pip_id, const sg_pipeline_desc* desc) { + SOKOL_ASSERT(_sg.valid); + sg_pipeline_desc desc_def = _sg_pipeline_desc_defaults(desc); + _sg_pipeline_t* pip = _sg_lookup_pipeline(pip_id.id); + if (pip) { + if (pip->slot.state == SG_RESOURCESTATE_ALLOC) { + _sg_init_pipeline(pip, &desc_def); + SOKOL_ASSERT((pip->slot.state == SG_RESOURCESTATE_VALID) || (pip->slot.state == SG_RESOURCESTATE_FAILED)); + } else { + _SG_ERROR(INIT_PIPELINE_INVALID_STATE); + } + } + _SG_TRACE_ARGS(init_pipeline, pip_id, &desc_def); +} + +SOKOL_API_IMPL void sg_init_view(sg_view view_id, const sg_view_desc* desc) { + SOKOL_ASSERT(_sg.valid); + sg_view_desc desc_def = _sg_view_desc_defaults(desc); + _sg_view_t* view = _sg_lookup_view(view_id.id); + if (view) { + if (view->slot.state == SG_RESOURCESTATE_ALLOC) { + _sg_init_view(view, &desc_def); + SOKOL_ASSERT((view->slot.state == SG_RESOURCESTATE_VALID) + || (view->slot.state == SG_RESOURCESTATE_FAILED) + || (view->slot.state == SG_RESOURCESTATE_ALLOC)); + } else { + _SG_ERROR(INIT_VIEW_INVALID_STATE); + } + } + _SG_TRACE_ARGS(init_view, view_id, &desc_def); +} + +SOKOL_API_IMPL void sg_uninit_buffer(sg_buffer buf_id) { + SOKOL_ASSERT(_sg.valid); + _sg_buffer_t* buf = _sg_lookup_buffer(buf_id.id); + if (buf) { + if ((buf->slot.state == SG_RESOURCESTATE_VALID) || (buf->slot.state == SG_RESOURCESTATE_FAILED)) { + _sg_uninit_buffer(buf); + SOKOL_ASSERT(buf->slot.state == SG_RESOURCESTATE_ALLOC); + } else if (buf->slot.state != SG_RESOURCESTATE_ALLOC) { + _SG_ERROR(UNINIT_BUFFER_INVALID_STATE); + } + } + _SG_TRACE_ARGS(uninit_buffer, buf_id); +} + +SOKOL_API_IMPL void sg_uninit_image(sg_image img_id) { + SOKOL_ASSERT(_sg.valid); + _sg_image_t* img = _sg_lookup_image(img_id.id); + if (img) { + if ((img->slot.state == SG_RESOURCESTATE_VALID) || (img->slot.state == SG_RESOURCESTATE_FAILED)) { + _sg_uninit_image(img); + SOKOL_ASSERT(img->slot.state == SG_RESOURCESTATE_ALLOC); + } else if (img->slot.state != SG_RESOURCESTATE_ALLOC) { + _SG_ERROR(UNINIT_IMAGE_INVALID_STATE); + } + } + _SG_TRACE_ARGS(uninit_image, img_id); +} + +SOKOL_API_IMPL void sg_uninit_sampler(sg_sampler smp_id) { + SOKOL_ASSERT(_sg.valid); + _sg_sampler_t* smp = _sg_lookup_sampler(smp_id.id); + if (smp) { + if ((smp->slot.state == SG_RESOURCESTATE_VALID) || (smp->slot.state == SG_RESOURCESTATE_FAILED)) { + _sg_uninit_sampler(smp); + SOKOL_ASSERT(smp->slot.state == SG_RESOURCESTATE_ALLOC); + } else if (smp->slot.state != SG_RESOURCESTATE_ALLOC) { + _SG_ERROR(UNINIT_SAMPLER_INVALID_STATE); + } + } + _SG_TRACE_ARGS(uninit_sampler, smp_id); +} + +SOKOL_API_IMPL void sg_uninit_shader(sg_shader shd_id) { + SOKOL_ASSERT(_sg.valid); + _sg_shader_t* shd = _sg_lookup_shader(shd_id.id); + if (shd) { + if ((shd->slot.state == SG_RESOURCESTATE_VALID) || (shd->slot.state == SG_RESOURCESTATE_FAILED)) { + _sg_uninit_shader(shd); + SOKOL_ASSERT(shd->slot.state == SG_RESOURCESTATE_ALLOC); + } else if (shd->slot.state != SG_RESOURCESTATE_ALLOC) { + _SG_ERROR(UNINIT_SHADER_INVALID_STATE); + } + } + _SG_TRACE_ARGS(uninit_shader, shd_id); +} + +SOKOL_API_IMPL void sg_uninit_pipeline(sg_pipeline pip_id) { + SOKOL_ASSERT(_sg.valid); + _sg_pipeline_t* pip = _sg_lookup_pipeline(pip_id.id); + if (pip) { + if ((pip->slot.state == SG_RESOURCESTATE_VALID) || (pip->slot.state == SG_RESOURCESTATE_FAILED)) { + _sg_uninit_pipeline(pip); + SOKOL_ASSERT(pip->slot.state == SG_RESOURCESTATE_ALLOC); + } else if (pip->slot.state != SG_RESOURCESTATE_ALLOC) { + _SG_ERROR(UNINIT_PIPELINE_INVALID_STATE); + } + } + _SG_TRACE_ARGS(uninit_pipeline, pip_id); +} + +SOKOL_API_IMPL void sg_uninit_view(sg_view view_id) { + SOKOL_ASSERT(_sg.valid); + _sg_view_t* view = _sg_lookup_view(view_id.id); + if (view) { + if ((view->slot.state == SG_RESOURCESTATE_VALID) || (view->slot.state == SG_RESOURCESTATE_FAILED)) { + _sg_uninit_view(view); + SOKOL_ASSERT(view->slot.state == SG_RESOURCESTATE_ALLOC); + } else if (view->slot.state != SG_RESOURCESTATE_ALLOC) { + _SG_ERROR(UNINIT_VIEW_INVALID_STATE); + } + } + _SG_TRACE_ARGS(uninit_view, view_id); +} + +SOKOL_API_IMPL void sg_fail_buffer(sg_buffer buf_id) { + SOKOL_ASSERT(_sg.valid); + _sg_buffer_t* buf = _sg_lookup_buffer(buf_id.id); + if (buf) { + if (buf->slot.state == SG_RESOURCESTATE_ALLOC) { + buf->slot.state = SG_RESOURCESTATE_FAILED; + } else { + _SG_ERROR(FAIL_BUFFER_INVALID_STATE); + } + } + _SG_TRACE_ARGS(fail_buffer, buf_id); +} + +SOKOL_API_IMPL void sg_fail_image(sg_image img_id) { + SOKOL_ASSERT(_sg.valid); + _sg_image_t* img = _sg_lookup_image(img_id.id); + if (img) { + if (img->slot.state == SG_RESOURCESTATE_ALLOC) { + img->slot.state = SG_RESOURCESTATE_FAILED; + } else { + _SG_ERROR(FAIL_IMAGE_INVALID_STATE); + } + } + _SG_TRACE_ARGS(fail_image, img_id); +} + +SOKOL_API_IMPL void sg_fail_sampler(sg_sampler smp_id) { + SOKOL_ASSERT(_sg.valid); + _sg_sampler_t* smp = _sg_lookup_sampler(smp_id.id); + if (smp) { + if (smp->slot.state == SG_RESOURCESTATE_ALLOC) { + smp->slot.state = SG_RESOURCESTATE_FAILED; + } else { + _SG_ERROR(FAIL_SAMPLER_INVALID_STATE); + } + } + _SG_TRACE_ARGS(fail_sampler, smp_id); +} + +SOKOL_API_IMPL void sg_fail_shader(sg_shader shd_id) { + SOKOL_ASSERT(_sg.valid); + _sg_shader_t* shd = _sg_lookup_shader(shd_id.id); + if (shd) { + if (shd->slot.state == SG_RESOURCESTATE_ALLOC) { + shd->slot.state = SG_RESOURCESTATE_FAILED; + } else { + _SG_ERROR(FAIL_SHADER_INVALID_STATE); + } + } + _SG_TRACE_ARGS(fail_shader, shd_id); +} + +SOKOL_API_IMPL void sg_fail_pipeline(sg_pipeline pip_id) { + SOKOL_ASSERT(_sg.valid); + _sg_pipeline_t* pip = _sg_lookup_pipeline(pip_id.id); + if (pip) { + if (pip->slot.state == SG_RESOURCESTATE_ALLOC) { + pip->slot.state = SG_RESOURCESTATE_FAILED; + } else { + _SG_ERROR(FAIL_PIPELINE_INVALID_STATE); + } + } + _SG_TRACE_ARGS(fail_pipeline, pip_id); +} + +SOKOL_API_IMPL void sg_fail_view(sg_view view_id) { + SOKOL_ASSERT(_sg.valid); + _sg_view_t* view = _sg_lookup_view(view_id.id); + if (view) { + if (view->slot.state == SG_RESOURCESTATE_ALLOC) { + view->slot.state = SG_RESOURCESTATE_FAILED; + } else { + _SG_ERROR(FAIL_VIEW_INVALID_STATE); + } + } + _SG_TRACE_ARGS(fail_view, view_id); +} + +SOKOL_API_IMPL sg_resource_state sg_query_buffer_state(sg_buffer buf_id) { + SOKOL_ASSERT(_sg.valid); + _sg_buffer_t* buf = _sg_lookup_buffer(buf_id.id); + sg_resource_state res = buf ? buf->slot.state : SG_RESOURCESTATE_INVALID; + return res; +} + +SOKOL_API_IMPL sg_resource_state sg_query_image_state(sg_image img_id) { + SOKOL_ASSERT(_sg.valid); + _sg_image_t* img = _sg_lookup_image(img_id.id); + sg_resource_state res = img ? img->slot.state : SG_RESOURCESTATE_INVALID; + return res; +} + +SOKOL_API_IMPL sg_resource_state sg_query_sampler_state(sg_sampler smp_id) { + SOKOL_ASSERT(_sg.valid); + _sg_sampler_t* smp = _sg_lookup_sampler(smp_id.id); + sg_resource_state res = smp ? smp->slot.state : SG_RESOURCESTATE_INVALID; + return res; +} + +SOKOL_API_IMPL sg_resource_state sg_query_shader_state(sg_shader shd_id) { + SOKOL_ASSERT(_sg.valid); + _sg_shader_t* shd = _sg_lookup_shader(shd_id.id); + sg_resource_state res = shd ? shd->slot.state : SG_RESOURCESTATE_INVALID; + return res; +} + +SOKOL_API_IMPL sg_resource_state sg_query_pipeline_state(sg_pipeline pip_id) { + SOKOL_ASSERT(_sg.valid); + _sg_pipeline_t* pip = _sg_lookup_pipeline(pip_id.id); + sg_resource_state res = pip ? pip->slot.state : SG_RESOURCESTATE_INVALID; + return res; +} + +SOKOL_API_IMPL sg_resource_state sg_query_view_state(sg_view view_id) { + SOKOL_ASSERT(_sg.valid); + _sg_view_t* view = _sg_lookup_view(view_id.id); + sg_resource_state res = view ? view->slot.state : SG_RESOURCESTATE_INVALID; + return res; +} + +SOKOL_API_IMPL sg_buffer sg_make_buffer(const sg_buffer_desc* desc) { + SOKOL_ASSERT(_sg.valid); + SOKOL_ASSERT(desc); + sg_buffer_desc desc_def = _sg_buffer_desc_defaults(desc); + sg_buffer buf_id = _sg_alloc_buffer(); + if (buf_id.id != SG_INVALID_ID) { + _sg_buffer_t* buf = _sg_buffer_at(buf_id.id); + SOKOL_ASSERT(buf && (buf->slot.state == SG_RESOURCESTATE_ALLOC)); + _sg_init_buffer(buf, &desc_def); + SOKOL_ASSERT((buf->slot.state == SG_RESOURCESTATE_VALID) || (buf->slot.state == SG_RESOURCESTATE_FAILED)); + } + _SG_TRACE_ARGS(make_buffer, &desc_def, buf_id); + return buf_id; +} + +SOKOL_API_IMPL sg_image sg_make_image(const sg_image_desc* desc) { + SOKOL_ASSERT(_sg.valid); + SOKOL_ASSERT(desc); + sg_image_desc desc_def = _sg_image_desc_defaults(desc); + sg_image img_id = _sg_alloc_image(); + if (img_id.id != SG_INVALID_ID) { + _sg_image_t* img = _sg_image_at(img_id.id); + SOKOL_ASSERT(img && (img->slot.state == SG_RESOURCESTATE_ALLOC)); + _sg_init_image(img, &desc_def); + SOKOL_ASSERT((img->slot.state == SG_RESOURCESTATE_VALID) || (img->slot.state == SG_RESOURCESTATE_FAILED)); + } + _SG_TRACE_ARGS(make_image, &desc_def, img_id); + return img_id; +} + +SOKOL_API_IMPL sg_sampler sg_make_sampler(const sg_sampler_desc* desc) { + SOKOL_ASSERT(_sg.valid); + SOKOL_ASSERT(desc); + sg_sampler_desc desc_def = _sg_sampler_desc_defaults(desc); + sg_sampler smp_id = _sg_alloc_sampler(); + if (smp_id.id != SG_INVALID_ID) { + _sg_sampler_t* smp = _sg_sampler_at(smp_id.id); + SOKOL_ASSERT(smp && (smp->slot.state == SG_RESOURCESTATE_ALLOC)); + _sg_init_sampler(smp, &desc_def); + SOKOL_ASSERT((smp->slot.state == SG_RESOURCESTATE_VALID) || (smp->slot.state == SG_RESOURCESTATE_FAILED)); + } + _SG_TRACE_ARGS(make_sampler, &desc_def, smp_id); + return smp_id; +} + +SOKOL_API_IMPL sg_shader sg_make_shader(const sg_shader_desc* desc) { + SOKOL_ASSERT(_sg.valid); + SOKOL_ASSERT(desc); + sg_shader_desc desc_def = _sg_shader_desc_defaults(desc); + sg_shader shd_id = _sg_alloc_shader(); + if (shd_id.id != SG_INVALID_ID) { + _sg_shader_t* shd = _sg_shader_at(shd_id.id); + SOKOL_ASSERT(shd && (shd->slot.state == SG_RESOURCESTATE_ALLOC)); + _sg_init_shader(shd, &desc_def); + SOKOL_ASSERT((shd->slot.state == SG_RESOURCESTATE_VALID) || (shd->slot.state == SG_RESOURCESTATE_FAILED)); + } + _SG_TRACE_ARGS(make_shader, &desc_def, shd_id); + return shd_id; +} + +SOKOL_API_IMPL sg_pipeline sg_make_pipeline(const sg_pipeline_desc* desc) { + SOKOL_ASSERT(_sg.valid); + SOKOL_ASSERT(desc); + sg_pipeline_desc desc_def = _sg_pipeline_desc_defaults(desc); + sg_pipeline pip_id = _sg_alloc_pipeline(); + if (pip_id.id != SG_INVALID_ID) { + _sg_pipeline_t* pip = _sg_pipeline_at(pip_id.id); + SOKOL_ASSERT(pip && (pip->slot.state == SG_RESOURCESTATE_ALLOC)); + _sg_init_pipeline(pip, &desc_def); + SOKOL_ASSERT((pip->slot.state == SG_RESOURCESTATE_VALID) || (pip->slot.state == SG_RESOURCESTATE_FAILED)); + } + _SG_TRACE_ARGS(make_pipeline, &desc_def, pip_id); + return pip_id; +} + +SOKOL_API_IMPL sg_view sg_make_view(const sg_view_desc* desc) { + SOKOL_ASSERT(_sg.valid); + SOKOL_ASSERT(desc); + sg_view_desc desc_def = _sg_view_desc_defaults(desc); + sg_view view_id = _sg_alloc_view(); + if (view_id.id != SG_INVALID_ID) { + _sg_view_t* view = _sg_view_at(view_id.id); + SOKOL_ASSERT(view && (view->slot.state == SG_RESOURCESTATE_ALLOC)); + _sg_init_view(view, &desc_def); + SOKOL_ASSERT((view->slot.state == SG_RESOURCESTATE_VALID) || (view->slot.state == SG_RESOURCESTATE_FAILED)); + } + _SG_TRACE_ARGS(make_view, &desc_def, view_id); + return view_id; +} + +SOKOL_API_IMPL void sg_destroy_buffer(sg_buffer buf_id) { + SOKOL_ASSERT(_sg.valid); + _SG_TRACE_ARGS(destroy_buffer, buf_id); + _sg_buffer_t* buf = _sg_lookup_buffer(buf_id.id); + if (buf) { + if ((buf->slot.state == SG_RESOURCESTATE_VALID) || (buf->slot.state == SG_RESOURCESTATE_FAILED)) { + _sg_uninit_buffer(buf); + SOKOL_ASSERT(buf->slot.state == SG_RESOURCESTATE_ALLOC); + } + if (buf->slot.state == SG_RESOURCESTATE_ALLOC) { + _sg_dealloc_buffer(buf); + SOKOL_ASSERT(buf->slot.state == SG_RESOURCESTATE_INITIAL); + } + } +} + +SOKOL_API_IMPL void sg_destroy_image(sg_image img_id) { + SOKOL_ASSERT(_sg.valid); + _SG_TRACE_ARGS(destroy_image, img_id); + _sg_image_t* img = _sg_lookup_image(img_id.id); + if (img) { + if ((img->slot.state == SG_RESOURCESTATE_VALID) || (img->slot.state == SG_RESOURCESTATE_FAILED)) { + _sg_uninit_image(img); + SOKOL_ASSERT(img->slot.state == SG_RESOURCESTATE_ALLOC); + } + if (img->slot.state == SG_RESOURCESTATE_ALLOC) { + _sg_dealloc_image(img); + SOKOL_ASSERT(img->slot.state == SG_RESOURCESTATE_INITIAL); + } + } +} + +SOKOL_API_IMPL void sg_destroy_sampler(sg_sampler smp_id) { + SOKOL_ASSERT(_sg.valid); + _SG_TRACE_ARGS(destroy_sampler, smp_id); + _sg_sampler_t* smp = _sg_lookup_sampler(smp_id.id); + if (smp) { + if ((smp->slot.state == SG_RESOURCESTATE_VALID) || (smp->slot.state == SG_RESOURCESTATE_FAILED)) { + _sg_uninit_sampler(smp); + SOKOL_ASSERT(smp->slot.state == SG_RESOURCESTATE_ALLOC); + } + if (smp->slot.state == SG_RESOURCESTATE_ALLOC) { + _sg_dealloc_sampler(smp); + SOKOL_ASSERT(smp->slot.state == SG_RESOURCESTATE_INITIAL); + } + } +} + +SOKOL_API_IMPL void sg_destroy_shader(sg_shader shd_id) { + SOKOL_ASSERT(_sg.valid); + _SG_TRACE_ARGS(destroy_shader, shd_id); + _sg_shader_t* shd = _sg_lookup_shader(shd_id.id); + if (shd) { + if ((shd->slot.state == SG_RESOURCESTATE_VALID) || (shd->slot.state == SG_RESOURCESTATE_FAILED)) { + _sg_uninit_shader(shd); + SOKOL_ASSERT(shd->slot.state == SG_RESOURCESTATE_ALLOC); + } + if (shd->slot.state == SG_RESOURCESTATE_ALLOC) { + _sg_dealloc_shader(shd); + SOKOL_ASSERT(shd->slot.state == SG_RESOURCESTATE_INITIAL); + } + } +} + +SOKOL_API_IMPL void sg_destroy_pipeline(sg_pipeline pip_id) { + SOKOL_ASSERT(_sg.valid); + _SG_TRACE_ARGS(destroy_pipeline, pip_id); + _sg_pipeline_t* pip = _sg_lookup_pipeline(pip_id.id); + if (pip) { + if ((pip->slot.state == SG_RESOURCESTATE_VALID) || (pip->slot.state == SG_RESOURCESTATE_FAILED)) { + _sg_uninit_pipeline(pip); + SOKOL_ASSERT(pip->slot.state == SG_RESOURCESTATE_ALLOC); + } + if (pip->slot.state == SG_RESOURCESTATE_ALLOC) { + _sg_dealloc_pipeline(pip); + SOKOL_ASSERT(pip->slot.state == SG_RESOURCESTATE_INITIAL); + } + } +} + +SOKOL_API_IMPL void sg_destroy_view(sg_view view_id) { + SOKOL_ASSERT(_sg.valid); + _SG_TRACE_ARGS(destroy_view, view_id); + _sg_view_t* view = _sg_lookup_view(view_id.id); + if (view) { + if ((view->slot.state == SG_RESOURCESTATE_VALID) || (view->slot.state == SG_RESOURCESTATE_FAILED)) { + _sg_uninit_view(view); + SOKOL_ASSERT(view->slot.state == SG_RESOURCESTATE_ALLOC); + } + if (view->slot.state == SG_RESOURCESTATE_ALLOC) { + _sg_dealloc_view(view); + SOKOL_ASSERT(view->slot.state == SG_RESOURCESTATE_INITIAL); + } + } +} + +SOKOL_API_IMPL void sg_begin_pass(const sg_pass* pass) { + SOKOL_ASSERT(_sg.valid); + SOKOL_ASSERT(!_sg.cur_pass.valid); + SOKOL_ASSERT(!_sg.cur_pass.in_pass); + SOKOL_ASSERT(_sg_attachments_empty(&_sg.cur_pass.atts)); + SOKOL_ASSERT(pass); + SOKOL_ASSERT((pass->_start_canary == 0) && (pass->_end_canary == 0)); + _sg.cur_pass.in_pass = true; + const sg_pass pass_def = _sg_pass_defaults(pass); + if (!_sg_validate_pass_attachment_limits(&pass_def)) { + return; + } + if (!_sg_validate_begin_pass(&pass_def)) { + return; + } + const _sg_attachments_ptrs_t atts_ptrs = _sg_attachments_ptrs(&pass_def.attachments); + if (!atts_ptrs.empty) { + if (!_sg_attachments_alive(&atts_ptrs)) { + _SG_ERROR(BEGINPASS_ATTACHMENTS_ALIVE); + return; + } + _sg.cur_pass.atts = pass->attachments; + _sg.cur_pass.dim = _sg_attachments_dim(&atts_ptrs); + } else if (!pass_def.compute) { + // a swapchain pass + SOKOL_ASSERT(pass_def.swapchain.width > 0); + SOKOL_ASSERT(pass_def.swapchain.height > 0); + SOKOL_ASSERT(pass_def.swapchain.color_format > SG_PIXELFORMAT_NONE); + SOKOL_ASSERT(pass_def.swapchain.sample_count > 0); + _sg.cur_pass.dim.width = pass_def.swapchain.width; + _sg.cur_pass.dim.height = pass_def.swapchain.height; + _sg.cur_pass.swapchain.color_fmt = pass_def.swapchain.color_format; + _sg.cur_pass.swapchain.depth_fmt = pass_def.swapchain.depth_format; + _sg.cur_pass.swapchain.sample_count = pass_def.swapchain.sample_count; + } + _sg.cur_pass.valid = true; // may be overruled by backend begin-pass functions + _sg.cur_pass.is_compute = pass_def.compute; + _sg_begin_pass(&pass_def, &atts_ptrs); + _SG_TRACE_ARGS(begin_pass, &pass_def); +} + +SOKOL_API_IMPL void sg_apply_viewport(int x, int y, int width, int height, bool origin_top_left) { + SOKOL_ASSERT(_sg.valid); + #if defined(SOKOL_DEBUG) + if (!_sg_validate_apply_viewport(x, y, width, height, origin_top_left)) { + return; + } + #endif + _sg_stats_add(num_apply_viewport, 1); + if (!_sg.cur_pass.valid) { + return; + } + _sg_apply_viewport(x, y, width, height, origin_top_left); + _SG_TRACE_ARGS(apply_viewport, x, y, width, height, origin_top_left); +} + +SOKOL_API_IMPL void sg_apply_viewportf(float x, float y, float width, float height, bool origin_top_left) { + sg_apply_viewport((int)x, (int)y, (int)width, (int)height, origin_top_left); +} + +SOKOL_API_IMPL void sg_apply_scissor_rect(int x, int y, int width, int height, bool origin_top_left) { + SOKOL_ASSERT(_sg.valid); + #if defined(SOKOL_DEBUG) + if (!_sg_validate_apply_scissor_rect(x, y, width, height, origin_top_left)) { + return; + } + #endif + _sg_stats_add(num_apply_scissor_rect, 1); + if (!_sg.cur_pass.valid) { + return; + } + _sg_apply_scissor_rect(x, y, width, height, origin_top_left); + _SG_TRACE_ARGS(apply_scissor_rect, x, y, width, height, origin_top_left); +} + +SOKOL_API_IMPL void sg_apply_scissor_rectf(float x, float y, float width, float height, bool origin_top_left) { + sg_apply_scissor_rect((int)x, (int)y, (int)width, (int)height, origin_top_left); +} + +SOKOL_API_IMPL void sg_apply_pipeline(sg_pipeline pip_id) { + SOKOL_ASSERT(_sg.valid); + _sg_stats_add(num_apply_pipeline, 1); + if (!_sg_validate_apply_pipeline(pip_id)) { + _sg.next_draw_valid = false; + return; + } + if (!_sg.cur_pass.valid) { + return; + } + _sg_pipeline_t* pip = _sg_lookup_pipeline(pip_id.id); + SOKOL_ASSERT(pip); + _sg.cur_pip = _sg_pipeline_ref(pip); + + _sg.next_draw_valid = (SG_RESOURCESTATE_VALID == pip->slot.state); + if (!_sg.next_draw_valid) { + return; + } + _sg.use_indexed_draw = pip->cmn.index_type != SG_INDEXTYPE_NONE; + _sg.use_instanced_draw = pip->cmn.use_instanced_draw; + + _sg_apply_pipeline(pip); + + // set the expected bindings and uniform block flags + const _sg_shader_t* shd = _sg_shader_ref_ptr(&pip->cmn.shader); + _sg.required_bindings_and_uniforms = pip->cmn.required_bindings_and_uniforms | shd->cmn.required_bindings_and_uniforms; + _sg.applied_bindings_and_uniforms = 0; + + _SG_TRACE_ARGS(apply_pipeline, pip_id); +} + +SOKOL_API_IMPL void sg_apply_bindings(const sg_bindings* bindings) { + SOKOL_ASSERT(_sg.valid); + SOKOL_ASSERT(bindings); + _sg_stats_add(num_apply_bindings, 1); + _sg.applied_bindings_and_uniforms |= (1 << SG_MAX_UNIFORMBLOCK_BINDSLOTS); + if (!_sg_validate_apply_bindings(bindings)) { + _sg.next_draw_valid = false; + } + SOKOL_ASSERT((bindings->_start_canary == 0) && (bindings->_end_canary==0)); + if (!_sg_pipeline_ref_alive(&_sg.cur_pip)) { + _sg.next_draw_valid = false; + } + if (!_sg.cur_pass.valid) { + return; + } + if (!_sg.next_draw_valid) { + return; + } + + _sg_bindings_ptrs_t bnd; + _sg_clear(&bnd, sizeof(bnd)); + bnd.pip = _sg_pipeline_ref_ptr(&_sg.cur_pip); + const _sg_shader_t* shd = _sg_shader_ref_ptr(&bnd.pip->cmn.shader); + if (!_sg.cur_pass.is_compute) { + for (size_t i = 0; i < SG_MAX_VERTEXBUFFER_BINDSLOTS; i++) { + if (bnd.pip->cmn.vertex_buffer_layout_active[i]) { + SOKOL_ASSERT(bindings->vertex_buffers[i].id != SG_INVALID_ID); + bnd.vbs[i] = _sg_lookup_buffer(bindings->vertex_buffers[i].id); + bnd.vb_offsets[i] = bindings->vertex_buffer_offsets[i]; + _sg.next_draw_valid &= bnd.vbs[i] && (SG_RESOURCESTATE_VALID == bnd.vbs[i]->slot.state); + } + } + if (bindings->index_buffer.id) { + bnd.ib = _sg_lookup_buffer(bindings->index_buffer.id); + bnd.ib_offset = bindings->index_buffer_offset; + _sg.next_draw_valid &= bnd.ib && (SG_RESOURCESTATE_VALID == bnd.ib->slot.state); + } + } + + for (int i = 0; i < SG_MAX_VIEW_BINDSLOTS; i++) { + if (shd->cmn.views[i].view_type != SG_VIEWTYPE_INVALID) { + SOKOL_ASSERT(bindings->views[i].id != SG_INVALID_ID); + bnd.views[i] = _sg_lookup_view(bindings->views[i].id); + if (bnd.views[i]) { + if (bnd.views[i]->cmn.type == SG_VIEWTYPE_STORAGEBUFFER) { + _sg.next_draw_valid &= _sg_buffer_ref_valid(&bnd.views[i]->cmn.buf.ref); + } else { + _sg.next_draw_valid &= _sg_image_ref_valid(&bnd.views[i]->cmn.img.ref); + } + } else { + _sg.next_draw_valid = false; + } + } + } + + for (size_t i = 0; i < SG_MAX_SAMPLER_BINDSLOTS; i++) { + if (shd->cmn.samplers[i].stage != SG_SHADERSTAGE_NONE) { + SOKOL_ASSERT(bindings->samplers[i].id != SG_INVALID_ID); + bnd.smps[i] = _sg_lookup_sampler(bindings->samplers[i].id); + SOKOL_ASSERT(bnd.smps[i]); + } + } + + if (_sg.next_draw_valid) { + _sg.next_draw_valid &= _sg_apply_bindings(&bnd); + _SG_TRACE_ARGS(apply_bindings, bindings); + } +} + +SOKOL_API_IMPL void sg_apply_uniforms(int ub_slot, const sg_range* data) { + SOKOL_ASSERT(_sg.valid); + SOKOL_ASSERT((ub_slot >= 0) && (ub_slot < SG_MAX_UNIFORMBLOCK_BINDSLOTS)); + SOKOL_ASSERT(data && data->ptr && (data->size > 0)); + _sg_stats_add(num_apply_uniforms, 1); + _sg_stats_add(size_apply_uniforms, (uint32_t)data->size); + _sg.applied_bindings_and_uniforms |= 1 << ub_slot; + if (!_sg_validate_apply_uniforms(ub_slot, data)) { + _sg.next_draw_valid = false; + return; + } + if (!_sg.cur_pass.valid) { + return; + } + if (!_sg.next_draw_valid) { + return; + } + _sg_apply_uniforms(ub_slot, data); + _SG_TRACE_ARGS(apply_uniforms, ub_slot, data); +} + +_SOKOL_PRIVATE bool _sg_check_skip_draw(int num_elements, int num_instances) { + if (!_sg.cur_pass.valid) { + return true; + } + if (!_sg.next_draw_valid) { + return true; + } + // skip no-op draws + if ((0 == num_elements) || (0 == num_instances)) { + return true; + } + return false; +} + +SOKOL_API_IMPL void sg_draw(int base_element, int num_elements, int num_instances) { + SOKOL_ASSERT(_sg.valid); + #if defined(SOKOL_DEBUG) + if (!_sg_validate_draw(base_element, num_elements, num_instances)) { + return; + } + #endif + _sg_stats_add(num_draw, 1); + if (_sg_check_skip_draw(num_elements, num_instances)) { + return; + } + _sg_draw(base_element, num_elements, num_instances, 0, 0); + _SG_TRACE_ARGS(draw, base_element, num_elements, num_instances); +} + +SOKOL_API_IMPL void sg_draw_ex(int base_element, int num_elements, int num_instances, int base_vertex, int base_instance) { + SOKOL_ASSERT(_sg.valid); + #if defined(SOKOL_DEBUG) + if (!_sg_validate_draw_ex(base_element, num_elements, num_instances, base_vertex, base_instance)) { + return; + } + #endif + _sg_stats_add(num_draw_ex, 1); + if (_sg_check_skip_draw(num_elements, num_instances)) { + return; + } + _sg_draw(base_element, num_elements, num_instances, base_vertex, base_instance); + _SG_TRACE_ARGS(draw_ex, base_element, num_elements, num_instances, base_vertex, base_instance); +} + +SOKOL_API_IMPL void sg_dispatch(int num_groups_x, int num_groups_y, int num_groups_z) { + SOKOL_ASSERT(_sg.valid); + #if defined(SOKOL_DEBUG) + if (!_sg_validate_dispatch(num_groups_x, num_groups_y, num_groups_z)) { + return; + } + #endif + _sg_stats_add(num_dispatch, 1); + if (!_sg.cur_pass.valid) { + return; + } + if (!_sg.next_draw_valid) { + return; + } + // skip no-op dispatches + if ((0 == num_groups_x) || (0 == num_groups_y) || (0 == num_groups_z)) { + return; + } + _sg_dispatch(num_groups_x, num_groups_y, num_groups_z); + _SG_TRACE_ARGS(dispatch, num_groups_x, num_groups_y, num_groups_z); +} + +SOKOL_API_IMPL void sg_end_pass(void) { + SOKOL_ASSERT(_sg.valid); + SOKOL_ASSERT(_sg.cur_pass.in_pass); + _sg_stats_add(num_passes, 1); + // NOTE: don't exit early if !_sg.cur_pass.valid + const _sg_attachments_ptrs_t atts_ptrs = _sg_attachments_ptrs(&_sg.cur_pass.atts); + _sg_end_pass(&atts_ptrs); + _sg.cur_pip = _sg_pipeline_ref(0); + _sg_clear(&_sg.cur_pass, sizeof(_sg.cur_pass)); + _SG_TRACE_NOARGS(end_pass); +} + +SOKOL_API_IMPL void sg_commit(void) { + SOKOL_ASSERT(_sg.valid); + SOKOL_ASSERT(!_sg.cur_pass.valid); + SOKOL_ASSERT(!_sg.cur_pass.in_pass); + _sg_commit(); + _sg_update_frame_stats(); + _sg_notify_commit_listeners(); + _SG_TRACE_NOARGS(commit); + _sg.frame_index++; +} + +SOKOL_API_IMPL void sg_reset_state_cache(void) { + SOKOL_ASSERT(_sg.valid); + _sg_reset_state_cache(); + _SG_TRACE_NOARGS(reset_state_cache); +} + +SOKOL_API_IMPL void sg_update_buffer(sg_buffer buf_id, const sg_range* data) { + SOKOL_ASSERT(_sg.valid); + SOKOL_ASSERT(data && data->ptr && (data->size > 0)); + _sg_stats_add(num_update_buffer, 1); + _sg_stats_add(size_update_buffer, (uint32_t)data->size); + _sg_buffer_t* buf = _sg_lookup_buffer(buf_id.id); + if ((data->size > 0) && buf && (buf->slot.state == SG_RESOURCESTATE_VALID)) { + if (_sg_validate_update_buffer(buf, data)) { + SOKOL_ASSERT(data->size <= (size_t)buf->cmn.size); + // only one update allowed per buffer and frame + SOKOL_ASSERT(buf->cmn.update_frame_index != _sg.frame_index); + // update and append on same buffer in same frame not allowed + SOKOL_ASSERT(buf->cmn.append_frame_index != _sg.frame_index); + _sg_update_buffer(buf, data); + buf->cmn.update_frame_index = _sg.frame_index; + } + } + _SG_TRACE_ARGS(update_buffer, buf_id, data); +} + +SOKOL_API_IMPL int sg_append_buffer(sg_buffer buf_id, const sg_range* data) { + SOKOL_ASSERT(_sg.valid); + SOKOL_ASSERT(data && data->ptr); + _sg_stats_add(num_append_buffer, 1); + _sg_stats_add(size_append_buffer, (uint32_t)data->size); + _sg_buffer_t* buf = _sg_lookup_buffer(buf_id.id); + int result; + if (buf) { + // rewind append cursor in a new frame + if (buf->cmn.append_frame_index != _sg.frame_index) { + buf->cmn.append_pos = 0; + buf->cmn.append_overflow = false; + } + if (((size_t)buf->cmn.append_pos + data->size) > (size_t)buf->cmn.size) { + buf->cmn.append_overflow = true; + } + const int start_pos = buf->cmn.append_pos; + // NOTE: the multiple-of-4 requirement for the buffer offset is coming + // from WebGPU, but we want identical behaviour between backends + SOKOL_ASSERT(_sg_multiple_u64((uint64_t)start_pos, 4)); + if (buf->slot.state == SG_RESOURCESTATE_VALID) { + if (_sg_validate_append_buffer(buf, data)) { + if (!buf->cmn.append_overflow && (data->size > 0)) { + // update and append on same buffer in same frame not allowed + SOKOL_ASSERT(buf->cmn.update_frame_index != _sg.frame_index); + _sg_append_buffer(buf, data, buf->cmn.append_frame_index != _sg.frame_index); + buf->cmn.append_pos += (int) _sg_roundup_u64(data->size, 4); + buf->cmn.append_frame_index = _sg.frame_index; + } + } + } + result = start_pos; + } else { + // FIXME: should we return -1 here? + result = 0; + } + _SG_TRACE_ARGS(append_buffer, buf_id, data, result); + return result; +} + +SOKOL_API_IMPL bool sg_query_buffer_overflow(sg_buffer buf_id) { + SOKOL_ASSERT(_sg.valid); + _sg_buffer_t* buf = _sg_lookup_buffer(buf_id.id); + bool result = buf ? buf->cmn.append_overflow : false; + return result; +} + +SOKOL_API_IMPL bool sg_query_buffer_will_overflow(sg_buffer buf_id, size_t size) { + SOKOL_ASSERT(_sg.valid); + _sg_buffer_t* buf = _sg_lookup_buffer(buf_id.id); + bool result = false; + if (buf) { + int append_pos = buf->cmn.append_pos; + // rewind append cursor in a new frame + if (buf->cmn.append_frame_index != _sg.frame_index) { + append_pos = 0; + } + if ((append_pos + _sg_roundup((int)size, 4)) > buf->cmn.size) { + result = true; + } + } + return result; +} + +SOKOL_API_IMPL void sg_update_image(sg_image img_id, const sg_image_data* data) { + SOKOL_ASSERT(_sg.valid); + _sg_stats_add(num_update_image, 1); + for (int mip_index = 0; mip_index < SG_MAX_MIPMAPS; mip_index++) { + if (data->mip_levels[mip_index].size == 0) { + break; + } + _sg_stats_add(size_update_image, (uint32_t)data->mip_levels[mip_index].size); + } + _sg_image_t* img = _sg_lookup_image(img_id.id); + if (img && img->slot.state == SG_RESOURCESTATE_VALID) { + if (_sg_validate_update_image(img, data)) { + SOKOL_ASSERT(img->cmn.upd_frame_index != _sg.frame_index); + _sg_update_image(img, data); + img->cmn.upd_frame_index = _sg.frame_index; + } + } + _SG_TRACE_ARGS(update_image, img_id, data); +} + +SOKOL_API_IMPL void sg_push_debug_group(const char* name) { + SOKOL_ASSERT(_sg.valid); + SOKOL_ASSERT(name); + _sg_push_debug_group(name); + _SG_TRACE_ARGS(push_debug_group, name); +} + +SOKOL_API_IMPL void sg_pop_debug_group(void) { + SOKOL_ASSERT(_sg.valid); + _sg_pop_debug_group(); + _SG_TRACE_NOARGS(pop_debug_group); +} + +SOKOL_API_IMPL bool sg_add_commit_listener(sg_commit_listener listener) { + SOKOL_ASSERT(_sg.valid); + return _sg_add_commit_listener(&listener); +} + +SOKOL_API_IMPL bool sg_remove_commit_listener(sg_commit_listener listener) { + SOKOL_ASSERT(_sg.valid); + return _sg_remove_commit_listener(&listener); +} + +SOKOL_API_IMPL void sg_enable_frame_stats(void) { + SOKOL_ASSERT(_sg.valid); + _sg.stats_enabled = true; +} + +SOKOL_API_IMPL void sg_disable_frame_stats(void) { + SOKOL_ASSERT(_sg.valid); + _sg.stats_enabled = false; +} + +SOKOL_API_IMPL bool sg_frame_stats_enabled(void) { + return _sg.stats_enabled; +} + +SOKOL_API_IMPL sg_buffer_info sg_query_buffer_info(sg_buffer buf_id) { + SOKOL_ASSERT(_sg.valid); + sg_buffer_info info; + _sg_clear(&info, sizeof(info)); + const _sg_buffer_t* buf = _sg_lookup_buffer(buf_id.id); + if (buf) { + info.slot.state = buf->slot.state; + info.slot.res_id = buf->slot.id; + info.slot.uninit_count = buf->slot.uninit_count; + info.update_frame_index = buf->cmn.update_frame_index; + info.append_frame_index = buf->cmn.append_frame_index; + info.append_pos = buf->cmn.append_pos; + info.append_overflow = buf->cmn.append_overflow; + #if defined(SOKOL_D3D11) + info.num_slots = 1; + info.active_slot = 0; + #else + info.num_slots = buf->cmn.num_slots; + info.active_slot = buf->cmn.active_slot; + #endif + } + return info; +} + +SOKOL_API_IMPL sg_image_info sg_query_image_info(sg_image img_id) { + SOKOL_ASSERT(_sg.valid); + sg_image_info info; + _sg_clear(&info, sizeof(info)); + const _sg_image_t* img = _sg_lookup_image(img_id.id); + if (img) { + info.slot.state = img->slot.state; + info.slot.res_id = img->slot.id; + info.slot.uninit_count = img->slot.uninit_count; + info.upd_frame_index = img->cmn.upd_frame_index; + #if defined(SOKOL_D3D11) + info.num_slots = 1; + info.active_slot = 0; + #else + info.num_slots = img->cmn.num_slots; + info.active_slot = img->cmn.active_slot; + #endif + } + return info; +} + +SOKOL_API_IMPL sg_sampler_info sg_query_sampler_info(sg_sampler smp_id) { + SOKOL_ASSERT(_sg.valid); + sg_sampler_info info; + _sg_clear(&info, sizeof(info)); + const _sg_sampler_t* smp = _sg_lookup_sampler(smp_id.id); + if (smp) { + info.slot.state = smp->slot.state; + info.slot.res_id = smp->slot.id; + info.slot.uninit_count = smp->slot.uninit_count; + } + return info; +} + +SOKOL_API_IMPL sg_shader_info sg_query_shader_info(sg_shader shd_id) { + SOKOL_ASSERT(_sg.valid); + sg_shader_info info; + _sg_clear(&info, sizeof(info)); + const _sg_shader_t* shd = _sg_lookup_shader(shd_id.id); + if (shd) { + info.slot.state = shd->slot.state; + info.slot.res_id = shd->slot.id; + info.slot.uninit_count = shd->slot.uninit_count; + } + return info; +} + +SOKOL_API_IMPL sg_pipeline_info sg_query_pipeline_info(sg_pipeline pip_id) { + SOKOL_ASSERT(_sg.valid); + sg_pipeline_info info; + _sg_clear(&info, sizeof(info)); + const _sg_pipeline_t* pip = _sg_lookup_pipeline(pip_id.id); + if (pip) { + info.slot.state = pip->slot.state; + info.slot.res_id = pip->slot.id; + info.slot.uninit_count = pip->slot.uninit_count; + } + return info; +} + +SOKOL_API_IMPL sg_view_info sg_query_view_info(sg_view view_id) { + SOKOL_ASSERT(_sg.valid); + sg_view_info info; + _sg_clear(&info, sizeof(info)); + const _sg_view_t* view = _sg_lookup_view(view_id.id); + if (view) { + info.slot.state = view->slot.state; + info.slot.res_id = view->slot.id; + info.slot.uninit_count = view->slot.uninit_count; + } + return info; +} + +SOKOL_API_IMPL sg_buffer_desc sg_query_buffer_desc(sg_buffer buf_id) { + SOKOL_ASSERT(_sg.valid); + sg_buffer_desc desc; + _sg_clear(&desc, sizeof(desc)); + const _sg_buffer_t* buf = _sg_lookup_buffer(buf_id.id); + if (buf) { + desc.size = (size_t)buf->cmn.size; + desc.usage = buf->cmn.usage; + } + return desc; +} + +SOKOL_API_IMPL size_t sg_query_buffer_size(sg_buffer buf_id) { + SOKOL_ASSERT(_sg.valid); + const _sg_buffer_t* buf = _sg_lookup_buffer(buf_id.id); + if (buf) { + return (size_t)buf->cmn.size; + } + return 0; +} + +SOKOL_API_IMPL sg_buffer_usage sg_query_buffer_usage(sg_buffer buf_id) { + SOKOL_ASSERT(_sg.valid); + sg_buffer_usage usg; + _sg_clear(&usg, sizeof(usg)); + const _sg_buffer_t* buf = _sg_lookup_buffer(buf_id.id); + if (buf) { + usg = buf->cmn.usage; + } + return usg; +} + +SOKOL_API_IMPL sg_image_desc sg_query_image_desc(sg_image img_id) { + SOKOL_ASSERT(_sg.valid); + sg_image_desc desc; + _sg_clear(&desc, sizeof(desc)); + const _sg_image_t* img = _sg_lookup_image(img_id.id); + if (img) { + desc.type = img->cmn.type; + desc.width = img->cmn.width; + desc.height = img->cmn.height; + desc.num_slices = img->cmn.num_slices; + desc.num_mipmaps = img->cmn.num_mipmaps; + desc.usage = img->cmn.usage; + desc.pixel_format = img->cmn.pixel_format; + desc.sample_count = img->cmn.sample_count; + } + return desc; +} + +SOKOL_API_IMPL sg_image_type sg_query_image_type(sg_image img_id) { + SOKOL_ASSERT(_sg.valid); + const _sg_image_t* img = _sg_lookup_image(img_id.id); + if (img) { + return img->cmn.type; + } + return _SG_IMAGETYPE_DEFAULT; +} + +SOKOL_API_IMPL int sg_query_image_width(sg_image img_id) { + SOKOL_ASSERT(_sg.valid); + const _sg_image_t* img = _sg_lookup_image(img_id.id); + if (img) { + return img->cmn.width; + } + return 0; +} + +SOKOL_API_IMPL int sg_query_image_height(sg_image img_id) { + SOKOL_ASSERT(_sg.valid); + const _sg_image_t* img = _sg_lookup_image(img_id.id); + if (img) { + return img->cmn.height; + } + return 0; +} + +SOKOL_API_IMPL int sg_query_image_num_slices(sg_image img_id) { + SOKOL_ASSERT(_sg.valid); + const _sg_image_t* img = _sg_lookup_image(img_id.id); + if (img) { + return img->cmn.num_slices; + } + return 0; +} + +SOKOL_API_IMPL int sg_query_image_num_mipmaps(sg_image img_id) { + SOKOL_ASSERT(_sg.valid); + const _sg_image_t* img = _sg_lookup_image(img_id.id); + if (img) { + return img->cmn.num_mipmaps; + } + return 0; +} + +SOKOL_API_IMPL sg_pixel_format sg_query_image_pixelformat(sg_image img_id) { + SOKOL_ASSERT(_sg.valid); + const _sg_image_t* img = _sg_lookup_image(img_id.id); + if (img) { + return img->cmn.pixel_format; + } + return _SG_PIXELFORMAT_DEFAULT; +} + +SOKOL_API_IMPL sg_image_usage sg_query_image_usage(sg_image img_id) { + SOKOL_ASSERT(_sg.valid); + sg_image_usage usg; + _sg_clear(&usg, sizeof(usg)); + const _sg_image_t* img = _sg_lookup_image(img_id.id); + if (img) { + usg = img->cmn.usage; + } + return usg; +} + +SOKOL_API_IMPL int sg_query_image_sample_count(sg_image img_id) { + SOKOL_ASSERT(_sg.valid); + const _sg_image_t* img = _sg_lookup_image(img_id.id); + if (img) { + return img->cmn.sample_count; + } + return 0; +} + +SOKOL_API_IMPL sg_view_type sg_query_view_type(sg_view view_id) { + SOKOL_ASSERT(_sg.valid); + const _sg_view_t* view = _sg_lookup_view(view_id.id); + if (view) { + return view->cmn.type; + } else { + return SG_VIEWTYPE_INVALID; + } +} + +// NOTE: may return SG_INVALID_ID if view invalid or view not an image view +SOKOL_API_IMPL sg_image sg_query_view_image(sg_view view_id) { + SOKOL_ASSERT(_sg.valid); + sg_image img; _sg_clear(&img, sizeof(img)); + const _sg_view_t* view = _sg_lookup_view(view_id.id); + if (view) { + img.id = view->cmn.img.ref.sref.id; + } + return img; +} + +// NOTE: may return SG_INVALID_ID if view invalid or view not a buffer view +SOKOL_API_IMPL sg_buffer sg_query_view_buffer(sg_view view_id) { + SOKOL_ASSERT(_sg.valid); + sg_buffer buf; _sg_clear(&buf, sizeof(buf)); + const _sg_view_t* view = _sg_lookup_view(view_id.id); + if (view) { + buf.id = view->cmn.buf.ref.sref.id; + } + return buf; +} + +SOKOL_API_IMPL sg_sampler_desc sg_query_sampler_desc(sg_sampler smp_id) { + SOKOL_ASSERT(_sg.valid); + sg_sampler_desc desc; + _sg_clear(&desc, sizeof(desc)); + const _sg_sampler_t* smp = _sg_lookup_sampler(smp_id.id); + if (smp) { + desc.min_filter = smp->cmn.min_filter; + desc.mag_filter = smp->cmn.mag_filter; + desc.mipmap_filter = smp->cmn.mipmap_filter; + desc.wrap_u = smp->cmn.wrap_u; + desc.wrap_v = smp->cmn.wrap_v; + desc.wrap_w = smp->cmn.wrap_w; + desc.min_lod = smp->cmn.min_lod; + desc.max_lod = smp->cmn.max_lod; + desc.border_color = smp->cmn.border_color; + desc.compare = smp->cmn.compare; + desc.max_anisotropy = smp->cmn.max_anisotropy; + } + return desc; +} + +SOKOL_API_IMPL sg_shader_desc sg_query_shader_desc(sg_shader shd_id) { + SOKOL_ASSERT(_sg.valid); + sg_shader_desc desc; + _sg_clear(&desc, sizeof(desc)); + const _sg_shader_t* shd = _sg_lookup_shader(shd_id.id); + if (shd) { + for (size_t ub_idx = 0; ub_idx < SG_MAX_UNIFORMBLOCK_BINDSLOTS; ub_idx++) { + sg_shader_uniform_block* ub_desc = &desc.uniform_blocks[ub_idx]; + const _sg_shader_uniform_block_t* ub = &shd->cmn.uniform_blocks[ub_idx]; + ub_desc->stage = ub->stage; + ub_desc->size = ub->size; + } + for (size_t view_idx = 0; view_idx < SG_MAX_VIEW_BINDSLOTS; view_idx++) { + const _sg_shader_view_t* view = &shd->cmn.views[view_idx]; + if (view->view_type == SG_VIEWTYPE_TEXTURE) { + sg_shader_texture_view* tex_desc = &desc.views[view_idx].texture; + tex_desc->stage = view->stage; + tex_desc->image_type = view->image_type; + tex_desc->sample_type = view->sample_type; + tex_desc->multisampled = view->multisampled; + } else if (shd->cmn.views[view_idx].view_type == SG_VIEWTYPE_STORAGEBUFFER) { + sg_shader_storage_buffer_view* sbuf_desc = &desc.views[view_idx].storage_buffer; + sbuf_desc->stage = view->stage; + sbuf_desc->readonly = view->sbuf_readonly; + } else if (shd->cmn.views[view_idx].view_type == SG_VIEWTYPE_STORAGEIMAGE) { + sg_shader_storage_image_view* simg_desc = &desc.views[view_idx].storage_image; + simg_desc->stage = view->stage; + simg_desc->access_format = view->access_format; + simg_desc->image_type = view->image_type; + simg_desc->writeonly = view->simg_writeonly; + } + } + for (size_t smp_idx = 0; smp_idx < SG_MAX_SAMPLER_BINDSLOTS; smp_idx++) { + sg_shader_sampler* smp_desc = &desc.samplers[smp_idx]; + const _sg_shader_sampler_t* smp = &shd->cmn.samplers[smp_idx]; + smp_desc->stage = smp->stage; + smp_desc->sampler_type = smp->sampler_type; + } + for (size_t tex_smp_idx = 0; tex_smp_idx < SG_MAX_TEXTURE_SAMPLER_PAIRS; tex_smp_idx++) { + sg_shader_texture_sampler_pair* tex_smp_desc = &desc.texture_sampler_pairs[tex_smp_idx]; + const _sg_shader_texture_sampler_t* tex_smp = &shd->cmn.texture_samplers[tex_smp_idx]; + tex_smp_desc->stage = tex_smp->stage; + tex_smp_desc->view_slot = tex_smp->view_slot; + tex_smp_desc->sampler_slot = tex_smp->sampler_slot; + } + } + return desc; +} + +SOKOL_API_IMPL sg_pipeline_desc sg_query_pipeline_desc(sg_pipeline pip_id) { + SOKOL_ASSERT(_sg.valid); + sg_pipeline_desc desc; + _sg_clear(&desc, sizeof(desc)); + const _sg_pipeline_t* pip = _sg_lookup_pipeline(pip_id.id); + if (pip) { + desc.compute = pip->cmn.is_compute; + desc.shader.id = pip->cmn.shader.sref.id; + desc.layout = pip->cmn.layout; + desc.depth = pip->cmn.depth; + desc.stencil = pip->cmn.stencil; + desc.color_count = pip->cmn.color_count; + for (int i = 0; i < pip->cmn.color_count; i++) { + desc.colors[i] = pip->cmn.colors[i]; + } + desc.primitive_type = pip->cmn.primitive_type; + desc.index_type = pip->cmn.index_type; + desc.cull_mode = pip->cmn.cull_mode; + desc.face_winding = pip->cmn.face_winding; + desc.sample_count = pip->cmn.sample_count; + desc.blend_color = pip->cmn.blend_color; + desc.alpha_to_coverage_enabled = pip->cmn.alpha_to_coverage_enabled; + } + return desc; +} + +SOKOL_API_IMPL sg_view_desc sg_query_view_desc(sg_view view_id) { + SOKOL_ASSERT(_sg.valid); + sg_view_desc desc; + _sg_clear(&desc, sizeof(desc)); + const _sg_view_t* view = _sg_lookup_view(view_id.id); + if (view) { + switch (view->cmn.type) { + case SG_VIEWTYPE_STORAGEBUFFER: + desc.storage_buffer.buffer.id = view->cmn.buf.ref.sref.id; + desc.storage_buffer.offset = view->cmn.buf.offset; + break; + case SG_VIEWTYPE_STORAGEIMAGE: + desc.storage_image.image.id = view->cmn.img.ref.sref.id; + desc.storage_image.mip_level = view->cmn.img.mip_level; + desc.storage_image.slice = view->cmn.img.slice; + break; + case SG_VIEWTYPE_TEXTURE: + desc.texture.image.id = view->cmn.img.ref.sref.id; + desc.texture.mip_levels.base = view->cmn.img.mip_level; + desc.texture.mip_levels.count = view->cmn.img.mip_level_count; + desc.texture.slices.base = view->cmn.img.slice; + desc.texture.slices.count = view->cmn.img.slice_count; + break; + case SG_VIEWTYPE_COLORATTACHMENT: + desc.color_attachment.image.id = view->cmn.img.ref.sref.id; + desc.color_attachment.mip_level = view->cmn.img.mip_level; + desc.color_attachment.slice = view->cmn.img.slice; + break; + case SG_VIEWTYPE_RESOLVEATTACHMENT: + desc.resolve_attachment.image.id = view->cmn.img.ref.sref.id; + desc.resolve_attachment.mip_level = view->cmn.img.mip_level; + desc.resolve_attachment.slice = view->cmn.img.slice; + break; + case SG_VIEWTYPE_DEPTHSTENCILATTACHMENT: + desc.depth_stencil_attachment.image.id = view->cmn.img.ref.sref.id; + desc.depth_stencil_attachment.mip_level = view->cmn.img.mip_level; + desc.depth_stencil_attachment.slice = view->cmn.img.slice; + break; + default: + SOKOL_UNREACHABLE; + } + } + return desc; +} + +SOKOL_API_IMPL sg_buffer_desc sg_query_buffer_defaults(const sg_buffer_desc* desc) { + SOKOL_ASSERT(_sg.valid && desc); + return _sg_buffer_desc_defaults(desc); +} + +SOKOL_API_IMPL sg_image_desc sg_query_image_defaults(const sg_image_desc* desc) { + SOKOL_ASSERT(_sg.valid && desc); + return _sg_image_desc_defaults(desc); +} + +SOKOL_API_IMPL sg_sampler_desc sg_query_sampler_defaults(const sg_sampler_desc* desc) { + SOKOL_ASSERT(_sg.valid && desc); + return _sg_sampler_desc_defaults(desc); +} + +SOKOL_API_IMPL sg_shader_desc sg_query_shader_defaults(const sg_shader_desc* desc) { + SOKOL_ASSERT(_sg.valid && desc); + return _sg_shader_desc_defaults(desc); +} + +SOKOL_API_IMPL sg_pipeline_desc sg_query_pipeline_defaults(const sg_pipeline_desc* desc) { + SOKOL_ASSERT(_sg.valid && desc); + return _sg_pipeline_desc_defaults(desc); +} + +SOKOL_API_IMPL sg_view_desc sg_query_view_defaults(const sg_view_desc* desc) { + SOKOL_ASSERT(_sg.valid && desc); + return _sg_view_desc_defaults(desc); +} + +SOKOL_API_IMPL const void* sg_d3d11_device(void) { + #if defined(SOKOL_D3D11) + return (const void*) _sg.d3d11.dev; + #else + return 0; + #endif +} + +SOKOL_API_IMPL const void* sg_d3d11_device_context(void) { + #if defined(SOKOL_D3D11) + return (const void*) _sg.d3d11.ctx; + #else + return 0; + #endif +} + +SOKOL_API_IMPL sg_d3d11_buffer_info sg_d3d11_query_buffer_info(sg_buffer buf_id) { + SOKOL_ASSERT(_sg.valid); + sg_d3d11_buffer_info res; + _sg_clear(&res, sizeof(res)); + #if defined(SOKOL_D3D11) + const _sg_buffer_t* buf = _sg_lookup_buffer(buf_id.id); + if (buf) { + res.buf = (const void*) buf->d3d11.buf; + } + #else + _SOKOL_UNUSED(buf_id); + #endif + return res; +} + +SOKOL_API_IMPL sg_d3d11_image_info sg_d3d11_query_image_info(sg_image img_id) { + SOKOL_ASSERT(_sg.valid); + sg_d3d11_image_info res; + _sg_clear(&res, sizeof(res)); + #if defined(SOKOL_D3D11) + const _sg_image_t* img = _sg_lookup_image(img_id.id); + if (img) { + res.tex2d = (const void*) img->d3d11.tex2d; + res.tex3d = (const void*) img->d3d11.tex3d; + res.res = (const void*) img->d3d11.res; + } + #else + _SOKOL_UNUSED(img_id); + #endif + return res; +} + +SOKOL_API_IMPL sg_d3d11_sampler_info sg_d3d11_query_sampler_info(sg_sampler smp_id) { + SOKOL_ASSERT(_sg.valid); + sg_d3d11_sampler_info res; + _sg_clear(&res, sizeof(res)); + #if defined(SOKOL_D3D11) + const _sg_sampler_t* smp = _sg_lookup_sampler(smp_id.id); + if (smp) { + res.smp = (const void*) smp->d3d11.smp; + } + #else + _SOKOL_UNUSED(smp_id); + #endif + return res; +} + +SOKOL_API_IMPL sg_d3d11_shader_info sg_d3d11_query_shader_info(sg_shader shd_id) { + SOKOL_ASSERT(_sg.valid); + sg_d3d11_shader_info res; + _sg_clear(&res, sizeof(res)); + #if defined(SOKOL_D3D11) + const _sg_shader_t* shd = _sg_lookup_shader(shd_id.id); + if (shd) { + for (size_t i = 0; i < SG_MAX_UNIFORMBLOCK_BINDSLOTS; i++) { + res.cbufs[i] = (const void*) shd->d3d11.all_cbufs[i]; + } + res.vs = (const void*) shd->d3d11.vs; + res.fs = (const void*) shd->d3d11.fs; + } + #else + _SOKOL_UNUSED(shd_id); + #endif + return res; +} + +SOKOL_API_IMPL sg_d3d11_pipeline_info sg_d3d11_query_pipeline_info(sg_pipeline pip_id) { + SOKOL_ASSERT(_sg.valid); + sg_d3d11_pipeline_info res; + _sg_clear(&res, sizeof(res)); + #if defined(SOKOL_D3D11) + const _sg_pipeline_t* pip = _sg_lookup_pipeline(pip_id.id); + if (pip) { + res.il = (const void*) pip->d3d11.il; + res.rs = (const void*) pip->d3d11.rs; + res.dss = (const void*) pip->d3d11.dss; + res.bs = (const void*) pip->d3d11.bs; + } + #else + _SOKOL_UNUSED(pip_id); + #endif + return res; +} + +SOKOL_API_IMPL sg_d3d11_view_info sg_d3d11_query_view_info(sg_view view_id) { + SOKOL_ASSERT(_sg.valid); + sg_d3d11_view_info res; + _sg_clear(&res, sizeof(res)); + #if defined(SOKOL_D3D11) + const _sg_view_t* view = _sg_lookup_view(view_id.id); + res.srv = (const void*) view->d3d11.srv; + res.uav = (const void*) view->d3d11.uav; + res.rtv = (const void*) view->d3d11.rtv; + res.dsv = (const void*) view->d3d11.dsv; + #else + _SOKOL_UNUSED(view_id); + #endif + return res; +} + +SOKOL_API_IMPL const void* sg_mtl_device(void) { + #if defined(SOKOL_METAL) + if (nil != _sg.mtl.device) { + return (__bridge const void*) _sg.mtl.device; + } else { + return 0; + } + #else + return 0; + #endif +} + +SOKOL_API_IMPL const void* sg_mtl_render_command_encoder(void) { + #if defined(SOKOL_METAL) + if (nil != _sg.mtl.render_cmd_encoder) { + return (__bridge const void*) _sg.mtl.render_cmd_encoder; + } else { + return 0; + } + #else + return 0; + #endif +} + +SOKOL_API_IMPL const void* sg_mtl_compute_command_encoder(void) { + #if defined(SOKOL_METAL) + if (nil != _sg.mtl.compute_cmd_encoder) { + return (__bridge const void*) _sg.mtl.compute_cmd_encoder; + } else { + return 0; + } + #else + return 0; + #endif +} + +SOKOL_API_IMPL sg_mtl_buffer_info sg_mtl_query_buffer_info(sg_buffer buf_id) { + SOKOL_ASSERT(_sg.valid); + sg_mtl_buffer_info res; + _sg_clear(&res, sizeof(res)); + #if defined(SOKOL_METAL) + const _sg_buffer_t* buf = _sg_lookup_buffer(buf_id.id); + if (buf) { + for (int i = 0; i < SG_NUM_INFLIGHT_FRAMES; i++) { + if (buf->mtl.buf[i] != 0) { + res.buf[i] = (__bridge void*) _sg_mtl_id(buf->mtl.buf[i]); + } + } + res.active_slot = buf->cmn.active_slot; + } + #else + _SOKOL_UNUSED(buf_id); + #endif + return res; +} + +SOKOL_API_IMPL sg_mtl_image_info sg_mtl_query_image_info(sg_image img_id) { + SOKOL_ASSERT(_sg.valid); + sg_mtl_image_info res; + _sg_clear(&res, sizeof(res)); + #if defined(SOKOL_METAL) + const _sg_image_t* img = _sg_lookup_image(img_id.id); + if (img) { + for (int i = 0; i < SG_NUM_INFLIGHT_FRAMES; i++) { + if (img->mtl.tex[i] != 0) { + res.tex[i] = (__bridge void*) _sg_mtl_id(img->mtl.tex[i]); + } + } + res.active_slot = img->cmn.active_slot; + } + #else + _SOKOL_UNUSED(img_id); + #endif + return res; +} + +SOKOL_API_IMPL sg_mtl_sampler_info sg_mtl_query_sampler_info(sg_sampler smp_id) { + SOKOL_ASSERT(_sg.valid); + sg_mtl_sampler_info res; + _sg_clear(&res, sizeof(res)); + #if defined(SOKOL_METAL) + const _sg_sampler_t* smp = _sg_lookup_sampler(smp_id.id); + if (smp) { + if (smp->mtl.sampler_state != 0) { + res.smp = (__bridge void*) _sg_mtl_id(smp->mtl.sampler_state); + } + } + #else + _SOKOL_UNUSED(smp_id); + #endif + return res; +} + +SOKOL_API_IMPL sg_mtl_shader_info sg_mtl_query_shader_info(sg_shader shd_id) { + SOKOL_ASSERT(_sg.valid); + sg_mtl_shader_info res; + _sg_clear(&res, sizeof(res)); + #if defined(SOKOL_METAL) + const _sg_shader_t* shd = _sg_lookup_shader(shd_id.id); + if (shd) { + const int vertex_lib = shd->mtl.vertex_func.mtl_lib; + const int vertex_func = shd->mtl.vertex_func.mtl_func; + const int fragment_lib = shd->mtl.fragment_func.mtl_lib; + const int fragment_func = shd->mtl.fragment_func.mtl_func; + if (vertex_lib != 0) { + res.vertex_lib = (__bridge void*) _sg_mtl_id(vertex_lib); + } + if (fragment_lib != 0) { + res.fragment_lib = (__bridge void*) _sg_mtl_id(fragment_lib); + } + if (vertex_func != 0) { + res.vertex_func = (__bridge void*) _sg_mtl_id(vertex_func); + } + if (fragment_func != 0) { + res.fragment_func = (__bridge void*) _sg_mtl_id(fragment_func); + } + } + #else + _SOKOL_UNUSED(shd_id); + #endif + return res; +} + +SOKOL_API_IMPL sg_mtl_pipeline_info sg_mtl_query_pipeline_info(sg_pipeline pip_id) { + SOKOL_ASSERT(_sg.valid); + sg_mtl_pipeline_info res; + _sg_clear(&res, sizeof(res)); + #if defined(SOKOL_METAL) + const _sg_pipeline_t* pip = _sg_lookup_pipeline(pip_id.id); + if (pip) { + if (pip->mtl.rps != 0) { + res.rps = (__bridge void*) _sg_mtl_id(pip->mtl.rps); + } + if (pip->mtl.dss != 0) { + res.dss = (__bridge void*) _sg_mtl_id(pip->mtl.dss); + } + } + #else + _SOKOL_UNUSED(pip_id); + #endif + return res; +} + +SOKOL_API_IMPL const void* sg_wgpu_device(void) { + #if defined(SOKOL_WGPU) + return (const void*) _sg.wgpu.dev; + #else + return 0; + #endif +} + +SOKOL_API_IMPL const void* sg_wgpu_queue(void) { + #if defined(SOKOL_WGPU) + return (const void*) _sg.wgpu.queue; + #else + return 0; + #endif +} + +SOKOL_API_IMPL const void* sg_wgpu_command_encoder(void) { + #if defined(SOKOL_WGPU) + return (const void*) _sg.wgpu.cmd_enc; + #else + return 0; + #endif +} + +SOKOL_API_IMPL const void* sg_wgpu_render_pass_encoder(void) { + #if defined(SOKOL_WGPU) + return (const void*) _sg.wgpu.rpass_enc; + #else + return 0; + #endif +} + +SOKOL_API_IMPL const void* sg_wgpu_compute_pass_encoder(void) { + #if defined(SOKOL_WGPU) + return (const void*) _sg.wgpu.cpass_enc; + #else + return 0; + #endif +} + +SOKOL_API_IMPL sg_wgpu_buffer_info sg_wgpu_query_buffer_info(sg_buffer buf_id) { + SOKOL_ASSERT(_sg.valid); + sg_wgpu_buffer_info res; + _sg_clear(&res, sizeof(res)); + #if defined(SOKOL_WGPU) + const _sg_buffer_t* buf = _sg_lookup_buffer(buf_id.id); + if (buf) { + res.buf = (const void*) buf->wgpu.buf; + } + #else + _SOKOL_UNUSED(buf_id); + #endif + return res; +} + +SOKOL_API_IMPL sg_wgpu_image_info sg_wgpu_query_image_info(sg_image img_id) { + SOKOL_ASSERT(_sg.valid); + sg_wgpu_image_info res; + _sg_clear(&res, sizeof(res)); + #if defined(SOKOL_WGPU) + const _sg_image_t* img = _sg_lookup_image(img_id.id); + if (img) { + res.tex = (const void*) img->wgpu.tex; + } + #else + _SOKOL_UNUSED(img_id); + #endif + return res; +} + +SOKOL_API_IMPL sg_wgpu_sampler_info sg_wgpu_query_sampler_info(sg_sampler smp_id) { + SOKOL_ASSERT(_sg.valid); + sg_wgpu_sampler_info res; + _sg_clear(&res, sizeof(res)); + #if defined(SOKOL_WGPU) + const _sg_sampler_t* smp = _sg_lookup_sampler(smp_id.id); + if (smp) { + res.smp = (const void*) smp->wgpu.smp; + } + #else + _SOKOL_UNUSED(smp_id); + #endif + return res; +} + +SOKOL_API_IMPL sg_wgpu_shader_info sg_wgpu_query_shader_info(sg_shader shd_id) { + SOKOL_ASSERT(_sg.valid); + sg_wgpu_shader_info res; + _sg_clear(&res, sizeof(res)); + #if defined(SOKOL_WGPU) + const _sg_shader_t* shd = _sg_lookup_shader(shd_id.id); + if (shd) { + res.vs_mod = (const void*) shd->wgpu.vertex_func.module; + res.fs_mod = (const void*) shd->wgpu.fragment_func.module; + res.bgl = (const void*) shd->wgpu.bgl_view_smp; + } + #else + _SOKOL_UNUSED(shd_id); + #endif + return res; +} + +SOKOL_API_IMPL sg_wgpu_pipeline_info sg_wgpu_query_pipeline_info(sg_pipeline pip_id) { + SOKOL_ASSERT(_sg.valid); + sg_wgpu_pipeline_info res; + _sg_clear(&res, sizeof(res)); + #if defined(SOKOL_WGPU) + const _sg_pipeline_t* pip = _sg_lookup_pipeline(pip_id.id); + if (pip) { + res.render_pipeline = (const void*) pip->wgpu.rpip; + res.compute_pipeline = (const void*) pip->wgpu.cpip; + } + #else + _SOKOL_UNUSED(pip_id); + #endif + return res; +} + +SOKOL_API_IMPL sg_wgpu_view_info sg_wgpu_query_view_info(sg_view view_id) { + SOKOL_ASSERT(_sg.valid); + sg_wgpu_view_info res; + _sg_clear(&res, sizeof(res)); + #if defined(SOKOL_WGPU) + const _sg_view_t* view = _sg_lookup_view(view_id.id); + if (view) { + res.view = (const void*) view->wgpu.view; + } + #else + _SOKOL_UNUSED(view_id); + #endif + return res; +} + +SOKOL_API_IMPL sg_gl_buffer_info sg_gl_query_buffer_info(sg_buffer buf_id) { + SOKOL_ASSERT(_sg.valid); + sg_gl_buffer_info res; + _sg_clear(&res, sizeof(res)); + #if defined(_SOKOL_ANY_GL) + const _sg_buffer_t* buf = _sg_lookup_buffer(buf_id.id); + if (buf) { + for (int i = 0; i < SG_NUM_INFLIGHT_FRAMES; i++) { + res.buf[i] = buf->gl.buf[i]; + } + res.active_slot = buf->cmn.active_slot; + } + #else + _SOKOL_UNUSED(buf_id); + #endif + return res; +} + +SOKOL_API_IMPL sg_gl_image_info sg_gl_query_image_info(sg_image img_id) { + SOKOL_ASSERT(_sg.valid); + sg_gl_image_info res; + _sg_clear(&res, sizeof(res)); + #if defined(_SOKOL_ANY_GL) + const _sg_image_t* img = _sg_lookup_image(img_id.id); + if (img) { + for (int i = 0; i < SG_NUM_INFLIGHT_FRAMES; i++) { + res.tex[i] = img->gl.tex[i]; + } + res.tex_target = img->gl.target; + res.active_slot = img->cmn.active_slot; + } + #else + _SOKOL_UNUSED(img_id); + #endif + return res; +} + +SOKOL_API_IMPL sg_gl_sampler_info sg_gl_query_sampler_info(sg_sampler smp_id) { + SOKOL_ASSERT(_sg.valid); + sg_gl_sampler_info res; + _sg_clear(&res, sizeof(res)); + #if defined(_SOKOL_ANY_GL) + const _sg_sampler_t* smp = _sg_lookup_sampler(smp_id.id); + if (smp) { + res.smp = smp->gl.smp; + } + #else + _SOKOL_UNUSED(smp_id); + #endif + return res; +} + +SOKOL_API_IMPL sg_gl_shader_info sg_gl_query_shader_info(sg_shader shd_id) { + SOKOL_ASSERT(_sg.valid); + sg_gl_shader_info res; + _sg_clear(&res, sizeof(res)); + #if defined(_SOKOL_ANY_GL) + const _sg_shader_t* shd = _sg_lookup_shader(shd_id.id); + if (shd) { + res.prog = shd->gl.prog; + } + #else + _SOKOL_UNUSED(shd_id); + #endif + return res; +} + +SOKOL_API_IMPL sg_gl_view_info sg_gl_query_view_info(sg_view view_id) { + SOKOL_ASSERT(_sg.valid); + sg_gl_view_info res; + _sg_clear(&res, sizeof(res)); + #if defined(_SOKOL_ANY_GL) + const _sg_view_t* view = _sg_lookup_view(view_id.id); + if (view) { + for (size_t i = 0; i < SG_NUM_INFLIGHT_FRAMES; i++) { + res.tex_view[i] = view->gl.tex_view[i]; + } + res.msaa_render_buffer = view->gl.msaa_render_buffer; + res.msaa_resolve_frame_buffer = view->gl.msaa_resolve_frame_buffer; + } + #else + _SOKOL_UNUSED(view_id); + #endif + return res; +} + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#endif // SOKOL_GFX_IMPL diff --git a/third-party/sokol/sokol_gl.h b/third-party/sokol/sokol_gl.h new file mode 100644 index 0000000..afee0e2 --- /dev/null +++ b/third-party/sokol/sokol_gl.h @@ -0,0 +1,4821 @@ +#if defined(SOKOL_IMPL) && !defined(SOKOL_GL_IMPL) +#define SOKOL_GL_IMPL +#endif +#ifndef SOKOL_GL_INCLUDED +/* + sokol_gl.h -- OpenGL 1.x style rendering on top of sokol_gfx.h + + Project URL: https://github.com/floooh/sokol + + Do this: + #define SOKOL_IMPL or + #define SOKOL_GL_IMPL + before you include this file in *one* C or C++ file to create the + implementation. + + The following defines are used by the implementation to select the + platform-specific embedded shader code (these are the same defines as + used by sokol_gfx.h and sokol_app.h): + + SOKOL_GLCORE + SOKOL_GLES3 + SOKOL_D3D11 + SOKOL_METAL + SOKOL_WGPU + + ...optionally provide the following macros to override defaults: + + SOKOL_ASSERT(c) - your own assert macro (default: assert(c)) + SOKOL_GL_API_DECL - public function declaration prefix (default: extern) + SOKOL_API_DECL - same as SOKOL_GL_API_DECL + SOKOL_API_IMPL - public function implementation prefix (default: -) + SOKOL_UNREACHABLE() - a guard macro for unreachable code (default: assert(false)) + + If sokol_gl.h is compiled as a DLL, define the following before + including the declaration or implementation: + + SOKOL_DLL + + On Windows, SOKOL_DLL will define SOKOL_GL_API_DECL as __declspec(dllexport) + or __declspec(dllimport) as needed. + + Include the following headers before including sokol_gl.h: + + sokol_gfx.h + + Matrix functions have been taken from MESA and Regal. + + FEATURE OVERVIEW: + ================= + sokol_gl.h implements a subset of the OpenGLES 1.x feature set useful for + when you just want to quickly render a bunch of triangles or + lines without having to mess with buffers and shaders. + + The current feature set is mostly useful for debug visualizations + and simple UI-style 2D rendering: + + What's implemented: + - vertex components: + - position (x, y, z) + - 2D texture coords (u, v) + - color (r, g, b, a) + - primitive types: + - triangle list and strip + - line list and strip + - quad list (TODO: quad strips) + - point list + - one texture layer (no multi-texturing) + - viewport and scissor-rect with selectable origin (top-left or bottom-left) + - all GL 1.x matrix stack functions, and additionally equivalent + functions for gluPerspective and gluLookat + + Notable GLES 1.x features that are *NOT* implemented: + - vertex lighting (this is the most likely GL feature that might be added later) + - vertex arrays (although providing whole chunks of vertex data at once + might be a useful feature for a later version) + - texture coordinate generation + - line width + - all pixel store functions + - no ALPHA_TEST + - no clear functions (clearing is handled by the sokol-gfx render pass) + - fog + + Notable differences to GL: + - No "enum soup" for render states etc, instead there's a + 'pipeline stack', this is similar to GL's matrix stack, + but for pipeline-state-objects. The pipeline object at + the top of the pipeline stack defines the active set of render states + - All angles are in radians, not degrees (note the sgl_rad() and + sgl_deg() conversion functions) + - No enable/disable state for scissor test, this is always enabled + + STEP BY STEP: + ============= + --- To initialize sokol-gl, call: + + sgl_setup(const sgl_desc_t* desc) + + NOTE that sgl_setup() must be called *after* initializing sokol-gfx + (via sg_setup). This is because sgl_setup() needs to create + sokol-gfx resource objects. + + If you're intending to render to the default pass, and also don't + want to tweak memory usage, and don't want any logging output you can + just keep sgl_desc_t zero-initialized: + + sgl_setup(&(sgl_desc_t*){ 0 }); + + In this case, sokol-gl will create internal sg_pipeline objects that + are compatible with the sokol-app default framebuffer. + + I would recommend to at least install a logging callback so that + you'll see any warnings and errors. The easiest way is through + sokol_log.h: + + #include "sokol_log.h" + + sgl_setup(&(sgl_desc_t){ + .logger.func = slog_func. + }); + + If you want to render into a framebuffer with different pixel-format + and MSAA attributes you need to provide the matching attributes in the + sgl_setup() call: + + sgl_setup(&(sgl_desc_t*){ + .color_format = SG_PIXELFORMAT_..., + .depth_format = SG_PIXELFORMAT_..., + .sample_count = ..., + }); + + To reduce memory usage, or if you need to create more then the default number of + contexts, pipelines, vertices or draw commands, set the following sgl_desc_t + members: + + .context_pool_size (default: 4) + .pipeline_pool_size (default: 64) + .max_vertices (default: 64k) + .max_commands (default: 16k) + + Finally you can change the face winding for front-facing triangles + and quads: + + .face_winding - default is SG_FACEWINDING_CCW + + The default winding for front faces is counter-clock-wise. This is + the same as OpenGL's default, but different from sokol-gfx. + + --- Optionally create additional context objects if you want to render into + multiple sokol-gfx render passes (or generally if you want to + use multiple independent sokol-gl "state buckets") + + sgl_context ctx = sgl_make_context(const sgl_context_desc_t* desc) + + For details on rendering with sokol-gl contexts, search below for + WORKING WITH CONTEXTS. + + --- Optionally create pipeline-state-objects if you need render state + that differs from sokol-gl's default state: + + sgl_pipeline pip = sgl_make_pipeline(const sg_pipeline_desc* desc) + + ...this creates a pipeline object that's compatible with the currently + active context, alternatively call: + + sgl_pipeline_pip = sgl_context_make_pipeline(sgl_context ctx, const sg_pipeline_desc* desc) + + ...to create a pipeline object that's compatible with an explicitly + provided context. + + The similarity with sokol_gfx.h's sg_pipeline type and sg_make_pipeline() + function is intended. sgl_make_pipeline() also takes a standard + sokol-gfx sg_pipeline_desc object to describe the render state, but + without: + - shader + - vertex layout + - color- and depth-pixel-formats + - primitive type (lines, triangles, ...) + - MSAA sample count + Those will be filled in by sgl_make_pipeline(). Note that each + call to sgl_make_pipeline() needs to create several sokol-gfx + pipeline objects (one for each primitive type). + + 'depth.write_enabled' will be forced to 'false' if the context this + pipeline object is intended for has its depth pixel format set to + SG_PIXELFORMAT_NONE (which means the framebuffer this context is used + with doesn't have a depth-stencil surface). + + --- if you need to destroy sgl_pipeline objects before sgl_shutdown(): + + sgl_destroy_pipeline(sgl_pipeline pip) + + --- After sgl_setup() you can call any of the sokol-gl functions anywhere + in a frame, *except* sgl_draw(). The 'vanilla' functions + will only change internal sokol-gl state, and not call any sokol-gfx + functions. + + --- Unlike OpenGL, sokol-gl has a function to reset internal state to + a known default. This is useful at the start of a sequence of + rendering operations: + + void sgl_defaults(void) + + This will set the following default state: + + - current texture coordinate to u=0.0f, v=0.0f + - current color to white (rgba all 1.0f) + - current point size to 1.0f + - unbind the current texture and texturing will be disabled + - *all* matrices will be set to identity (also the projection matrix) + - the default render state will be set by loading the 'default pipeline' + into the top of the pipeline stack + + The current matrix- and pipeline-stack-depths will not be changed by + sgl_defaults(). + + --- change the currently active renderstate through the + pipeline-stack functions, this works similar to the + traditional GL matrix stack: + + ...load the default pipeline state on the top of the pipeline stack: + + sgl_load_default_pipeline() + + ...load a specific pipeline on the top of the pipeline stack: + + sgl_load_pipeline(sgl_pipeline pip) + + ...push and pop the pipeline stack: + sgl_push_pipeline() + sgl_pop_pipeline() + + --- control texturing with: + + sgl_enable_texture() + sgl_disable_texture() + sgl_texture(sg_view tex_view, sg_sampler smp) + + NOTE: the tex_view and smp handles can be invalid (SG_INVALID_ID), in this + case, sokol-gl will fall back to the internal default (white) texture + and sampler. + + --- set the current viewport and scissor rect with: + + sgl_viewport(int x, int y, int w, int h, bool origin_top_left) + sgl_scissor_rect(int x, int y, int w, int h, bool origin_top_left) + + ...or call these alternatives which take float arguments (this might allow + to avoid casting between float and integer in more strongly typed languages + when floating point pixel coordinates are used): + + sgl_viewportf(float x, float y, float w, float h, bool origin_top_left) + sgl_scissor_rectf(float x, float y, float w, float h, bool origin_top_left) + + ...these calls add a new command to the internal command queue, so + that the viewport or scissor rect are set at the right time relative + to other sokol-gl calls. + + --- adjust the transform matrices, matrix manipulation works just like + the OpenGL matrix stack: + + ...set the current matrix mode: + + sgl_matrix_mode_modelview() + sgl_matrix_mode_projection() + sgl_matrix_mode_texture() + + ...load the identity matrix into the current matrix: + + sgl_load_identity() + + ...translate, rotate and scale the current matrix: + + sgl_translate(float x, float y, float z) + sgl_rotate(float angle_rad, float x, float y, float z) + sgl_scale(float x, float y, float z) + + NOTE that all angles in sokol-gl are in radians, not in degree. + Convert between radians and degree with the helper functions: + + float sgl_rad(float deg) - degrees to radians + float sgl_deg(float rad) - radians to degrees + + ...directly load the current matrix from a float[16] array: + + sgl_load_matrix(const float m[16]) + sgl_load_transpose_matrix(const float m[16]) + + ...directly multiply the current matrix from a float[16] array: + + sgl_mult_matrix(const float m[16]) + sgl_mult_transpose_matrix(const float m[16]) + + The memory layout of those float[16] arrays is the same as in OpenGL. + + ...more matrix functions: + + sgl_frustum(float left, float right, float bottom, float top, float near, float far) + sgl_ortho(float left, float right, float bottom, float top, float near, float far) + sgl_perspective(float fov_y, float aspect, float near, float far) + sgl_lookat(float eye_x, float eye_y, float eye_z, float center_x, float center_y, float center_z, float up_x, float up_y, float up_z) + + These functions work the same as glFrustum(), glOrtho(), gluPerspective() + and gluLookAt(). + + ...and finally to push / pop the current matrix stack: + + sgl_push_matrix(void) + sgl_pop_matrix(void) + + Again, these work the same as glPushMatrix() and glPopMatrix(). + + --- perform primitive rendering: + + ...set the current texture coordinate and color 'registers' with or + point size with: + + sgl_t2f(float u, float v) - set current texture coordinate + sgl_c*(...) - set current color + sgl_point_size(float size) - set current point size + + There are several functions for setting the color (as float values, + unsigned byte values, packed as unsigned 32-bit integer, with + and without alpha). + + NOTE that these are the only functions that can be called both inside + sgl_begin_*() / sgl_end() and outside. + + Also NOTE that point size is currently hardwired to 1.0f if the D3D11 + backend is used. + + ...start a primitive vertex sequence with: + + sgl_begin_points() + sgl_begin_lines() + sgl_begin_line_strip() + sgl_begin_triangles() + sgl_begin_triangle_strip() + sgl_begin_quads() + + ...after sgl_begin_*() specify vertices: + + sgl_v*(...) + sgl_v*_t*(...) + sgl_v*_c*(...) + sgl_v*_t*_c*(...) + + These functions write a new vertex to sokol-gl's internal vertex buffer, + optionally with texture-coords and color. If the texture coordinate + and/or color is missing, it will be taken from the current texture-coord + and color 'register'. + + ...finally, after specifying vertices, call: + + sgl_end() + + This will record a new draw command in sokol-gl's internal command + list, or it will extend the previous draw command if no relevant + state has changed since the last sgl_begin/end pair. + + --- inside a sokol-gfx rendering pass, call the sgl_draw() function + to render the currently active context: + + sgl_draw() + + ...or alternatively call: + + sgl_context_draw(ctx) + + ...to render an explicitly provided context. + + This will render everything that has been recorded in the context since + the last call to sgl_draw() through sokol-gfx, and will 'rewind' the internal + vertex-, uniform- and command-buffers. + + --- each sokol-gl context tracks internal error states which can + be obtains via: + + sgl_error_t sgl_error() + + ...alternatively with an explicit context argument: + + sgl_error_t sgl_context_error(ctx); + + ...this returns a struct with the following booleans: + + .any - true if any of the below errors is true + .vertices_full - internal vertex buffer is full (checked in sgl_end()) + .uniforms_full - the internal uniforms buffer is full (checked in sgl_end()) + .commands_full - the internal command buffer is full (checked in sgl_end()) + .stack_overflow - matrix- or pipeline-stack overflow + .stack_underflow - matrix- or pipeline-stack underflow + .no_context - the active context no longer exists + + ...depending on the above error state, sgl_draw() may skip rendering + completely, or only draw partial geometry + + --- you can get the number of recorded vertices and draw commands in the current + frame and active sokol-gl context via: + + int sgl_num_vertices() + int sgl_num_commands() + + ...this allows you to check whether the vertex or command pools are running + full before the overflow actually happens (in this case you could also + check the error booleans in the result of sgl_error()). + + RENDER LAYERS + ============= + Render layers allow to split sokol-gl rendering into separate draw-command + groups which can then be rendered separately in a sokol-gfx draw pass. This + allows to mix/interleave sokol-gl rendering with other render operations. + + Layered rendering is controlled through two functions: + + sgl_layer(int layer_id) + sgl_draw_layer(int layer_id) + + (and the context-variant sgl_draw_layer(): sgl_context_draw_layer() + + The sgl_layer() function sets the 'current layer', any sokol-gl calls + which internally record draw commands will also store the current layer + in the draw command, and later in a sokol-gfx render pass, a call + to sgl_draw_layer() will only render the draw commands that have + a matching layer. + + The default layer is '0', this is active after sokol-gl setup, and + is also restored at the start of a new frame (but *not* by calling + sgl_defaults()). + + NOTE that calling sgl_draw() is equivalent with sgl_draw_layer(0) + (in general you should either use either use sgl_draw() or + sgl_draw_layer() in an application, but not both). + + WORKING WITH CONTEXTS: + ====================== + If you want to render to more than one sokol-gfx render pass you need to + work with additional sokol-gl context objects (one context object for + each offscreen rendering pass, in addition to the implicitly created + 'default context'. + + All sokol-gl state is tracked per context, and there is always a "current + context" (with the notable exception that the currently set context is + destroyed, more on that later). + + Using multiple contexts can also be useful if you only render in + a single pass, but want to maintain multiple independent "state buckets". + + To create new context object, call: + + sgl_context ctx = sgl_make_context(&(sgl_context_desc){ + .max_vertices = ..., // default: 64k + .max_commands = ..., // default: 16k + .color_format = ..., + .depth_format = ..., + .sample_count = ..., + }); + + The color_format, depth_format and sample_count items must be compatible + with the render pass the sgl_draw() or sgL_context_draw() function + will be called in. + + Creating a context does *not* make the context current. To do this, call: + + sgl_set_context(ctx); + + The currently active context will implicitly be used by most sokol-gl functions + which don't take an explicit context handle as argument. + + To switch back to the default context, pass the global constant SGL_DEFAULT_CONTEXT: + + sgl_set_context(SGL_DEFAULT_CONTEXT); + + ...or alternatively use the function sgl_default_context() instead of the + global constant: + + sgl_set_context(sgl_default_context()); + + To get the currently active context, call: + + sgl_context cur_ctx = sgl_get_context(); + + The following functions exist in two variants, one which use the currently + active context (set with sgl_set_context()), and another version which + takes an explicit context handle instead: + + sgl_make_pipeline() vs sgl_context_make_pipeline() + sgl_error() vs sgl_context_error(); + sgl_draw() vs sgl_context_draw(); + + Except for using the currently active context versus a provided context + handle, the two variants are exactlyidentical, e.g. the following + code sequences do the same thing: + + sgl_set_context(ctx); + sgl_pipeline pip = sgl_make_pipeline(...); + sgl_error_t err = sgl_error(); + sgl_draw(); + + vs + + sgl_pipeline pip = sgl_context_make_pipeline(ctx, ...); + sgl_error_t err = sgl_context_error(ctx); + sgl_context_draw(ctx); + + Destroying the currently active context is a 'soft error'. All following + calls which require a currently active context will silently fail, + and sgl_error() will return SGL_ERROR_NO_CONTEXT. + + UNDER THE HOOD: + =============== + sokol_gl.h works by recording vertex data and rendering commands into + memory buffers, and then drawing the recorded commands via sokol_gfx.h + + The only functions which call into sokol_gfx.h are: + - sgl_setup() + - sgl_shutdown() + - sgl_draw() (and variants) + + sgl_setup() must be called after initializing sokol-gfx. + sgl_shutdown() must be called before shutting down sokol-gfx. + sgl_draw() must be called once per frame inside a sokol-gfx render pass. + + All other sokol-gl function can be called anywhere in a frame, since + they just record data into memory buffers owned by sokol-gl. + + What happens in: + + sgl_setup(): + Unique resources shared by all contexts are created: + - a shader object (using embedded shader source or byte code) + - an 8x8 white default texture + The default context is created, which involves: + - 3 memory buffers are created, one for vertex data, + one for uniform data, and one for commands + - a dynamic vertex buffer is created + - the default sgl_pipeline object is created, which involves + creating 5 sg_pipeline objects + + One vertex is 24 bytes: + - float3 position + - float2 texture coords + - uint32_t color + + One uniform block is 128 bytes: + - mat4 model-view-projection matrix + - mat4 texture matrix + + One draw command is ca. 24 bytes for the actual + command code plus command arguments. + + Each sgl_end() consumes one command, and one uniform block + (only when the matrices have changed). + The required size for one sgl_begin/end pair is (at most): + + (152 + 24 * num_verts) bytes + + sgl_shutdown(): + - all sokol-gfx resources (buffer, shader, default-texture and + all pipeline objects) are destroyed + - the 3 memory buffers are freed + + sgl_draw() (and variants) + - copy all recorded vertex data into the dynamic sokol-gfx buffer + via a call to sg_update_buffer() + - for each recorded command: + - if the layer number stored in the command doesn't match + the layer that's to be rendered, skip to the next + command + - if it's a viewport command, call sg_apply_viewport() + - if it's a scissor-rect command, call sg_apply_scissor_rect() + - if it's a draw command: + - depending on what has changed since the last draw command, + call sg_apply_pipeline(), sg_apply_bindings() and + sg_apply_uniforms() + - finally call sg_draw() + + All other functions only modify the internally tracked state, add + data to the vertex, uniform and command buffers, or manipulate + the matrix stack. + + ON DRAW COMMAND MERGING + ======================= + Not every call to sgl_end() will automatically record a new draw command. + If possible, the previous draw command will simply be extended, + resulting in fewer actual draw calls later in sgl_draw(). + + A draw command will be merged with the previous command if "no relevant + state has changed" since the last sgl_end(), meaning: + + - no calls to sgl_viewport() and sgl_scissor_rect() + - the primitive type hasn't changed + - the primitive type isn't a 'strip type' (no line or triangle strip) + - the pipeline state object hasn't changed + - the current layer hasn't changed + - none of the matrices has changed + - none of the texture state has changed + + Merging a draw command simply means that the number of vertices + to render in the previous draw command will be incremented by the + number of vertices in the new draw command. + + MEMORY ALLOCATION OVERRIDE + ========================== + You can override the memory allocation functions at initialization time + like this: + + void* my_alloc(size_t size, void* user_data) { + return malloc(size); + } + + void my_free(void* ptr, void* user_data) { + free(ptr); + } + + ... + sgl_setup(&(sgl_desc_t){ + // ... + .allocator = { + .alloc_fn = my_alloc, + .free_fn = my_free, + .user_data = ...; + } + }); + ... + + If no overrides are provided, malloc and free will be used. + + + ERROR REPORTING AND LOGGING + =========================== + To get any logging information at all you need to provide a logging callback in the setup call, + the easiest way is to use sokol_log.h: + + #include "sokol_log.h" + + sgl_setup(&(sgl_desc_t){ + // ... + .logger.func = slog_func + }); + + To override logging with your own callback, first write a logging function like this: + + void my_log(const char* tag, // e.g. 'sgl' + uint32_t log_level, // 0=panic, 1=error, 2=warn, 3=info + uint32_t log_item_id, // SGL_LOGITEM_* + const char* message_or_null, // a message string, may be nullptr in release mode + uint32_t line_nr, // line number in sokol_gl.h + const char* filename_or_null, // source filename, may be nullptr in release mode + void* user_data) + { + ... + } + + ...and then setup sokol-gl like this: + + sgl_setup(&(sgl_desc_t){ + .logger = { + .func = my_log, + .user_data = my_user_data, + } + }); + + The provided logging function must be reentrant (e.g. be callable from + different threads). + + If you don't want to provide your own custom logger it is highly recommended to use + the standard logger in sokol_log.h instead, otherwise you won't see any warnings or + errors. + + + LICENSE + ======= + zlib/libpng license + + Copyright (c) 2018 Andre Weissflog + + This software is provided 'as-is', without any express or implied warranty. + In no event will the authors be held liable for any damages arising from the + use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software in a + product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not + be misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ +#define SOKOL_GL_INCLUDED (1) +#include +#include +#include // size_t, offsetof + +#if !defined(SOKOL_GFX_INCLUDED) +#error "Please include sokol_gfx.h before sokol_gl.h" +#endif + +#if defined(SOKOL_API_DECL) && !defined(SOKOL_GL_API_DECL) +#define SOKOL_GL_API_DECL SOKOL_API_DECL +#endif +#ifndef SOKOL_GL_API_DECL +#if defined(_WIN32) && defined(SOKOL_DLL) && defined(SOKOL_GL_IMPL) +#define SOKOL_GL_API_DECL __declspec(dllexport) +#elif defined(_WIN32) && defined(SOKOL_DLL) +#define SOKOL_GL_API_DECL __declspec(dllimport) +#else +#define SOKOL_GL_API_DECL extern +#endif +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/* + sgl_log_item_t + + Log items are defined via X-Macros, and expanded to an + enum 'sgl_log_item' - and in debug mode only - corresponding strings. + + Used as parameter in the logging callback. +*/ +#define _SGL_LOG_ITEMS \ + _SGL_LOGITEM_XMACRO(OK, "Ok") \ + _SGL_LOGITEM_XMACRO(MALLOC_FAILED, "memory allocation failed") \ + _SGL_LOGITEM_XMACRO(MAKE_PIPELINE_FAILED, "sg_make_pipeline() failed") \ + _SGL_LOGITEM_XMACRO(PIPELINE_POOL_EXHAUSTED, "pipeline pool exhausted (use sgl_desc_t.pipeline_pool_size to adjust)") \ + _SGL_LOGITEM_XMACRO(ADD_COMMIT_LISTENER_FAILED, "sg_add_commit_listener() failed") \ + _SGL_LOGITEM_XMACRO(CONTEXT_POOL_EXHAUSTED, "context pool exhausted (use sgl_desc_t.context_pool_size to adjust)") \ + _SGL_LOGITEM_XMACRO(CANNOT_DESTROY_DEFAULT_CONTEXT, "cannot destroy default context") \ + +#define _SGL_LOGITEM_XMACRO(item,msg) SGL_LOGITEM_##item, +typedef enum sgl_log_item_t { + _SGL_LOG_ITEMS +} sgl_log_item_t; +#undef _SGL_LOGITEM_XMACRO + +/* + sgl_logger_t + + Used in sgl_desc_t to provide a custom logging and error reporting + callback to sokol-gl. +*/ +typedef struct sgl_logger_t { + void (*func)( + const char* tag, // always "sgl" + uint32_t log_level, // 0=panic, 1=error, 2=warning, 3=info + uint32_t log_item_id, // SGL_LOGITEM_* + const char* message_or_null, // a message string, may be nullptr in release mode + uint32_t line_nr, // line number in sokol_gl.h + const char* filename_or_null, // source filename, may be nullptr in release mode + void* user_data); + void* user_data; +} sgl_logger_t; + +/* sokol_gl pipeline handle (created with sgl_make_pipeline()) */ +typedef struct sgl_pipeline { uint32_t id; } sgl_pipeline; + +/* a context handle (created with sgl_make_context()) */ +typedef struct sgl_context { uint32_t id; } sgl_context; + +/* + sgl_error_t + + Errors are reset each frame after calling sgl_draw(), + get the last error code with sgl_error() +*/ +typedef struct sgl_error_t { + bool any; + bool vertices_full; + bool uniforms_full; + bool commands_full; + bool stack_overflow; + bool stack_underflow; + bool no_context; +} sgl_error_t; + +/* + sgl_context_desc_t + + Describes the initialization parameters of a rendering context. + Creating additional contexts is useful if you want to render + in separate sokol-gfx passes. +*/ +typedef struct sgl_context_desc_t { + int max_vertices; // default: 64k + int max_commands; // default: 16k + sg_pixel_format color_format; + sg_pixel_format depth_format; + int sample_count; +} sgl_context_desc_t; + +/* + sgl_allocator_t + + Used in sgl_desc_t to provide custom memory-alloc and -free functions + to sokol_gl.h. If memory management should be overridden, both the + alloc and free function must be provided (e.g. it's not valid to + override one function but not the other). +*/ +typedef struct sgl_allocator_t { + void* (*alloc_fn)(size_t size, void* user_data); + void (*free_fn)(void* ptr, void* user_data); + void* user_data; +} sgl_allocator_t; + +typedef struct sgl_desc_t { + int max_vertices; // default: 64k + int max_commands; // default: 16k + int context_pool_size; // max number of contexts (including default context), default: 4 + int pipeline_pool_size; // size of internal pipeline pool, default: 64 + sg_pixel_format color_format; + sg_pixel_format depth_format; + int sample_count; + sg_face_winding face_winding; // default: SG_FACEWINDING_CCW + sgl_allocator_t allocator; // optional memory allocation overrides (default: malloc/free) + sgl_logger_t logger; // optional log function override (default: NO LOGGING) +} sgl_desc_t; + +/* the default context handle */ +static const sgl_context SGL_DEFAULT_CONTEXT = { 0x00010001 }; + +/* setup/shutdown/misc */ +SOKOL_GL_API_DECL void sgl_setup(const sgl_desc_t* desc); +SOKOL_GL_API_DECL void sgl_shutdown(void); +SOKOL_GL_API_DECL float sgl_rad(float deg); +SOKOL_GL_API_DECL float sgl_deg(float rad); +SOKOL_GL_API_DECL sgl_error_t sgl_error(void); +SOKOL_GL_API_DECL sgl_error_t sgl_context_error(sgl_context ctx); + +/* context functions */ +SOKOL_GL_API_DECL sgl_context sgl_make_context(const sgl_context_desc_t* desc); +SOKOL_GL_API_DECL void sgl_destroy_context(sgl_context ctx); +SOKOL_GL_API_DECL void sgl_set_context(sgl_context ctx); +SOKOL_GL_API_DECL sgl_context sgl_get_context(void); +SOKOL_GL_API_DECL sgl_context sgl_default_context(void); + +/* get information about recorded vertices and commands in current context */ +SOKOL_GL_API_DECL int sgl_num_vertices(void); +SOKOL_GL_API_DECL int sgl_num_commands(void); + +/* draw recorded commands (call inside a sokol-gfx render pass) */ +SOKOL_GL_API_DECL void sgl_draw(void); +SOKOL_GL_API_DECL void sgl_context_draw(sgl_context ctx); +SOKOL_GL_API_DECL void sgl_draw_layer(int layer_id); +SOKOL_GL_API_DECL void sgl_context_draw_layer(sgl_context ctx, int layer_id); + +/* create and destroy pipeline objects */ +SOKOL_GL_API_DECL sgl_pipeline sgl_make_pipeline(const sg_pipeline_desc* desc); +SOKOL_GL_API_DECL sgl_pipeline sgl_context_make_pipeline(sgl_context ctx, const sg_pipeline_desc* desc); +SOKOL_GL_API_DECL void sgl_destroy_pipeline(sgl_pipeline pip); + +/* render state functions */ +SOKOL_GL_API_DECL void sgl_defaults(void); +SOKOL_GL_API_DECL void sgl_viewport(int x, int y, int w, int h, bool origin_top_left); +SOKOL_GL_API_DECL void sgl_viewportf(float x, float y, float w, float h, bool origin_top_left); +SOKOL_GL_API_DECL void sgl_scissor_rect(int x, int y, int w, int h, bool origin_top_left); +SOKOL_GL_API_DECL void sgl_scissor_rectf(float x, float y, float w, float h, bool origin_top_left); +SOKOL_GL_API_DECL void sgl_enable_texture(void); +SOKOL_GL_API_DECL void sgl_disable_texture(void); +SOKOL_GL_API_DECL void sgl_texture(sg_view tex_view, sg_sampler smp); +SOKOL_GL_API_DECL void sgl_layer(int layer_id); + +/* pipeline stack functions */ +SOKOL_GL_API_DECL void sgl_load_default_pipeline(void); +SOKOL_GL_API_DECL void sgl_load_pipeline(sgl_pipeline pip); +SOKOL_GL_API_DECL void sgl_push_pipeline(void); +SOKOL_GL_API_DECL void sgl_pop_pipeline(void); + +/* matrix stack functions */ +SOKOL_GL_API_DECL void sgl_matrix_mode_modelview(void); +SOKOL_GL_API_DECL void sgl_matrix_mode_projection(void); +SOKOL_GL_API_DECL void sgl_matrix_mode_texture(void); +SOKOL_GL_API_DECL void sgl_load_identity(void); +SOKOL_GL_API_DECL void sgl_load_matrix(const float m[16]); +SOKOL_GL_API_DECL void sgl_load_transpose_matrix(const float m[16]); +SOKOL_GL_API_DECL void sgl_mult_matrix(const float m[16]); +SOKOL_GL_API_DECL void sgl_mult_transpose_matrix(const float m[16]); +SOKOL_GL_API_DECL void sgl_rotate(float angle_rad, float x, float y, float z); +SOKOL_GL_API_DECL void sgl_scale(float x, float y, float z); +SOKOL_GL_API_DECL void sgl_translate(float x, float y, float z); +SOKOL_GL_API_DECL void sgl_frustum(float l, float r, float b, float t, float n, float f); +SOKOL_GL_API_DECL void sgl_ortho(float l, float r, float b, float t, float n, float f); +SOKOL_GL_API_DECL void sgl_perspective(float fov_y, float aspect, float z_near, float z_far); +SOKOL_GL_API_DECL void sgl_lookat(float eye_x, float eye_y, float eye_z, float center_x, float center_y, float center_z, float up_x, float up_y, float up_z); +SOKOL_GL_API_DECL void sgl_push_matrix(void); +SOKOL_GL_API_DECL void sgl_pop_matrix(void); + +/* these functions only set the internal 'current texcoord / color / point size' (valid inside or outside begin/end) */ +SOKOL_GL_API_DECL void sgl_t2f(float u, float v); +SOKOL_GL_API_DECL void sgl_c3f(float r, float g, float b); +SOKOL_GL_API_DECL void sgl_c4f(float r, float g, float b, float a); +SOKOL_GL_API_DECL void sgl_c3b(uint8_t r, uint8_t g, uint8_t b); +SOKOL_GL_API_DECL void sgl_c4b(uint8_t r, uint8_t g, uint8_t b, uint8_t a); +SOKOL_GL_API_DECL void sgl_c1i(uint32_t rgba); +SOKOL_GL_API_DECL void sgl_point_size(float s); + +/* define primitives, each begin/end is one draw command */ +SOKOL_GL_API_DECL void sgl_begin_points(void); +SOKOL_GL_API_DECL void sgl_begin_lines(void); +SOKOL_GL_API_DECL void sgl_begin_line_strip(void); +SOKOL_GL_API_DECL void sgl_begin_triangles(void); +SOKOL_GL_API_DECL void sgl_begin_triangle_strip(void); +SOKOL_GL_API_DECL void sgl_begin_quads(void); +SOKOL_GL_API_DECL void sgl_v2f(float x, float y); +SOKOL_GL_API_DECL void sgl_v3f(float x, float y, float z); +SOKOL_GL_API_DECL void sgl_v2f_t2f(float x, float y, float u, float v); +SOKOL_GL_API_DECL void sgl_v3f_t2f(float x, float y, float z, float u, float v); +SOKOL_GL_API_DECL void sgl_v2f_c3f(float x, float y, float r, float g, float b); +SOKOL_GL_API_DECL void sgl_v2f_c3b(float x, float y, uint8_t r, uint8_t g, uint8_t b); +SOKOL_GL_API_DECL void sgl_v2f_c4f(float x, float y, float r, float g, float b, float a); +SOKOL_GL_API_DECL void sgl_v2f_c4b(float x, float y, uint8_t r, uint8_t g, uint8_t b, uint8_t a); +SOKOL_GL_API_DECL void sgl_v2f_c1i(float x, float y, uint32_t rgba); +SOKOL_GL_API_DECL void sgl_v3f_c3f(float x, float y, float z, float r, float g, float b); +SOKOL_GL_API_DECL void sgl_v3f_c3b(float x, float y, float z, uint8_t r, uint8_t g, uint8_t b); +SOKOL_GL_API_DECL void sgl_v3f_c4f(float x, float y, float z, float r, float g, float b, float a); +SOKOL_GL_API_DECL void sgl_v3f_c4b(float x, float y, float z, uint8_t r, uint8_t g, uint8_t b, uint8_t a); +SOKOL_GL_API_DECL void sgl_v3f_c1i(float x, float y, float z, uint32_t rgba); +SOKOL_GL_API_DECL void sgl_v2f_t2f_c3f(float x, float y, float u, float v, float r, float g, float b); +SOKOL_GL_API_DECL void sgl_v2f_t2f_c3b(float x, float y, float u, float v, uint8_t r, uint8_t g, uint8_t b); +SOKOL_GL_API_DECL void sgl_v2f_t2f_c4f(float x, float y, float u, float v, float r, float g, float b, float a); +SOKOL_GL_API_DECL void sgl_v2f_t2f_c4b(float x, float y, float u, float v, uint8_t r, uint8_t g, uint8_t b, uint8_t a); +SOKOL_GL_API_DECL void sgl_v2f_t2f_c1i(float x, float y, float u, float v, uint32_t rgba); +SOKOL_GL_API_DECL void sgl_v3f_t2f_c3f(float x, float y, float z, float u, float v, float r, float g, float b); +SOKOL_GL_API_DECL void sgl_v3f_t2f_c3b(float x, float y, float z, float u, float v, uint8_t r, uint8_t g, uint8_t b); +SOKOL_GL_API_DECL void sgl_v3f_t2f_c4f(float x, float y, float z, float u, float v, float r, float g, float b, float a); +SOKOL_GL_API_DECL void sgl_v3f_t2f_c4b(float x, float y, float z, float u, float v, uint8_t r, uint8_t g, uint8_t b, uint8_t a); +SOKOL_GL_API_DECL void sgl_v3f_t2f_c1i(float x, float y, float z, float u, float v, uint32_t rgba); +SOKOL_GL_API_DECL void sgl_end(void); + +#ifdef __cplusplus +} /* extern "C" */ + +/* reference-based equivalents for C++ */ +inline void sgl_setup(const sgl_desc_t& desc) { return sgl_setup(&desc); } +inline sgl_context sgl_make_context(const sgl_context_desc_t& desc) { return sgl_make_context(&desc); } +inline sgl_pipeline sgl_make_pipeline(const sg_pipeline_desc& desc) { return sgl_make_pipeline(&desc); } +inline sgl_pipeline sgl_context_make_pipeline(sgl_context ctx, const sg_pipeline_desc& desc) { return sgl_context_make_pipeline(ctx, &desc); } +#endif +#endif /* SOKOL_GL_INCLUDED */ + +// ██ ███ ███ ██████ ██ ███████ ███ ███ ███████ ███ ██ ████████ █████ ████████ ██ ██████ ███ ██ +// ██ ████ ████ ██ ██ ██ ██ ████ ████ ██ ████ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██ +// ██ ██ ████ ██ ██████ ██ █████ ██ ████ ██ █████ ██ ██ ██ ██ ███████ ██ ██ ██ ██ ██ ██ ██ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██ ██ ██ ██ ███████ ███████ ██ ██ ███████ ██ ████ ██ ██ ██ ██ ██ ██████ ██ ████ +// +// >>implementation +#ifdef SOKOL_GL_IMPL +#define SOKOL_GL_IMPL_INCLUDED (1) + +#if defined(SOKOL_MALLOC) || defined(SOKOL_CALLOC) || defined(SOKOL_FREE) +#error "SOKOL_MALLOC/CALLOC/FREE macros are no longer supported, please use sgl_desc_t.allocator to override memory allocation functions" +#endif + +#include // malloc/free +#include // memset +#include // M_PI, sqrtf, sinf, cosf + +#ifndef M_PI +#define M_PI 3.14159265358979323846264338327 +#endif + +#ifndef SOKOL_API_IMPL + #define SOKOL_API_IMPL +#endif +#ifndef SOKOL_DEBUG + #ifndef NDEBUG + #define SOKOL_DEBUG + #endif +#endif +#ifndef SOKOL_ASSERT + #include + #define SOKOL_ASSERT(c) assert(c) +#endif + +#define _sgl_def(val, def) (((val) == 0) ? (def) : (val)) +#define _SGL_INIT_COOKIE (0xABCDABCD) + +/* + Embedded source code compiled with: + + sokol-shdc -i sgl.glsl -o sgl.h -l glsl410:glsl300es:hlsl4:metal_macos:metal_ios:metal_sim:wgsl -b + + (not that for Metal and D3D11 byte code, sokol-shdc must be run + on macOS and Windows) + + @vs vs + layout(binding=0) uniform vs_params { + mat4 mvp; + mat4 tm; + }; + in vec4 position; + in vec2 texcoord0; + in vec4 color0; + in float psize; + out vec4 uv; + out vec4 color; + void main() { + gl_Position = mvp * position; + #ifndef SOKOL_WGSL + gl_PointSize = psize; + #endif + uv = tm * vec4(texcoord0, 0.0, 1.0); + color = color0; + } + @end + + @fs fs + layout(binding=0) uniform texture2D tex; + layout(binding=0) uniform sampler smp; + in vec4 uv; + in vec4 color; + out vec4 frag_color; + void main() { + frag_color = texture(sampler2D(tex, smp), uv.xy) * color; + } + @end + + @program sgl vs fs +*/ + +#if defined(SOKOL_GLCORE) +/* + #version 410 + + uniform vec4 vs_params[8]; + layout(location = 0) in vec4 position; + layout(location = 3) in float psize; + layout(location = 0) out vec4 uv; + layout(location = 1) in vec2 texcoord0; + layout(location = 1) out vec4 color; + layout(location = 2) in vec4 color0; + + void main() + { + gl_Position = mat4(vs_params[0], vs_params[1], vs_params[2], vs_params[3]) * position; + gl_PointSize = psize; + uv = mat4(vs_params[4], vs_params[5], vs_params[6], vs_params[7]) * vec4(texcoord0, 0.0, 1.0); + color = color0; + } +*/ +static const uint8_t _sgl_vs_source_glsl410[520] = { + 0x23,0x76,0x65,0x72,0x73,0x69,0x6f,0x6e,0x20,0x34,0x31,0x30,0x0a,0x0a,0x75,0x6e, + 0x69,0x66,0x6f,0x72,0x6d,0x20,0x76,0x65,0x63,0x34,0x20,0x76,0x73,0x5f,0x70,0x61, + 0x72,0x61,0x6d,0x73,0x5b,0x38,0x5d,0x3b,0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74,0x28, + 0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x30,0x29,0x20,0x69,0x6e, + 0x20,0x76,0x65,0x63,0x34,0x20,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x3b,0x0a, + 0x6c,0x61,0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x20, + 0x3d,0x20,0x33,0x29,0x20,0x69,0x6e,0x20,0x66,0x6c,0x6f,0x61,0x74,0x20,0x70,0x73, + 0x69,0x7a,0x65,0x3b,0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,0x61, + 0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x30,0x29,0x20,0x6f,0x75,0x74,0x20,0x76,0x65, + 0x63,0x34,0x20,0x75,0x76,0x3b,0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f, + 0x63,0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x31,0x29,0x20,0x69,0x6e,0x20,0x76, + 0x65,0x63,0x32,0x20,0x74,0x65,0x78,0x63,0x6f,0x6f,0x72,0x64,0x30,0x3b,0x0a,0x6c, + 0x61,0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d, + 0x20,0x31,0x29,0x20,0x6f,0x75,0x74,0x20,0x76,0x65,0x63,0x34,0x20,0x63,0x6f,0x6c, + 0x6f,0x72,0x3b,0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,0x61,0x74, + 0x69,0x6f,0x6e,0x20,0x3d,0x20,0x32,0x29,0x20,0x69,0x6e,0x20,0x76,0x65,0x63,0x34, + 0x20,0x63,0x6f,0x6c,0x6f,0x72,0x30,0x3b,0x0a,0x0a,0x76,0x6f,0x69,0x64,0x20,0x6d, + 0x61,0x69,0x6e,0x28,0x29,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x67,0x6c,0x5f,0x50, + 0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x6d,0x61,0x74,0x34,0x28,0x76, + 0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x30,0x5d,0x2c,0x20,0x76,0x73,0x5f, + 0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x31,0x5d,0x2c,0x20,0x76,0x73,0x5f,0x70,0x61, + 0x72,0x61,0x6d,0x73,0x5b,0x32,0x5d,0x2c,0x20,0x76,0x73,0x5f,0x70,0x61,0x72,0x61, + 0x6d,0x73,0x5b,0x33,0x5d,0x29,0x20,0x2a,0x20,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f, + 0x6e,0x3b,0x0a,0x20,0x20,0x20,0x20,0x67,0x6c,0x5f,0x50,0x6f,0x69,0x6e,0x74,0x53, + 0x69,0x7a,0x65,0x20,0x3d,0x20,0x70,0x73,0x69,0x7a,0x65,0x3b,0x0a,0x20,0x20,0x20, + 0x20,0x75,0x76,0x20,0x3d,0x20,0x6d,0x61,0x74,0x34,0x28,0x76,0x73,0x5f,0x70,0x61, + 0x72,0x61,0x6d,0x73,0x5b,0x34,0x5d,0x2c,0x20,0x76,0x73,0x5f,0x70,0x61,0x72,0x61, + 0x6d,0x73,0x5b,0x35,0x5d,0x2c,0x20,0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73, + 0x5b,0x36,0x5d,0x2c,0x20,0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x37, + 0x5d,0x29,0x20,0x2a,0x20,0x76,0x65,0x63,0x34,0x28,0x74,0x65,0x78,0x63,0x6f,0x6f, + 0x72,0x64,0x30,0x2c,0x20,0x30,0x2e,0x30,0x2c,0x20,0x31,0x2e,0x30,0x29,0x3b,0x0a, + 0x20,0x20,0x20,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20,0x63,0x6f,0x6c,0x6f, + 0x72,0x30,0x3b,0x0a,0x7d,0x0a,0x0a,0x00, +}; +/* + #version 410 + + uniform sampler2D tex_smp; + + layout(location = 0) out vec4 frag_color; + layout(location = 0) in vec4 uv; + layout(location = 1) in vec4 color; + + void main() + { + frag_color = texture(tex_smp, uv.xy) * color; + } +*/ +static const uint8_t _sgl_fs_source_glsl410[222] = { + 0x23,0x76,0x65,0x72,0x73,0x69,0x6f,0x6e,0x20,0x34,0x31,0x30,0x0a,0x0a,0x75,0x6e, + 0x69,0x66,0x6f,0x72,0x6d,0x20,0x73,0x61,0x6d,0x70,0x6c,0x65,0x72,0x32,0x44,0x20, + 0x74,0x65,0x78,0x5f,0x73,0x6d,0x70,0x3b,0x0a,0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74, + 0x28,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x30,0x29,0x20,0x6f, + 0x75,0x74,0x20,0x76,0x65,0x63,0x34,0x20,0x66,0x72,0x61,0x67,0x5f,0x63,0x6f,0x6c, + 0x6f,0x72,0x3b,0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,0x61,0x74, + 0x69,0x6f,0x6e,0x20,0x3d,0x20,0x30,0x29,0x20,0x69,0x6e,0x20,0x76,0x65,0x63,0x34, + 0x20,0x75,0x76,0x3b,0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,0x61, + 0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x31,0x29,0x20,0x69,0x6e,0x20,0x76,0x65,0x63, + 0x34,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x0a,0x76,0x6f,0x69,0x64,0x20,0x6d, + 0x61,0x69,0x6e,0x28,0x29,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66,0x72,0x61,0x67, + 0x5f,0x63,0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20,0x74,0x65,0x78,0x74,0x75,0x72,0x65, + 0x28,0x74,0x65,0x78,0x5f,0x73,0x6d,0x70,0x2c,0x20,0x75,0x76,0x2e,0x78,0x79,0x29, + 0x20,0x2a,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x7d,0x0a,0x0a,0x00, +}; +#elif defined(SOKOL_GLES3) +/* + #version 300 es + + uniform vec4 vs_params[8]; + layout(location = 0) in vec4 position; + layout(location = 3) in float psize; + out vec4 uv; + layout(location = 1) in vec2 texcoord0; + out vec4 color; + layout(location = 2) in vec4 color0; + + void main() + { + gl_Position = mat4(vs_params[0], vs_params[1], vs_params[2], vs_params[3]) * position; + gl_PointSize = psize; + uv = mat4(vs_params[4], vs_params[5], vs_params[6], vs_params[7]) * vec4(texcoord0, 0.0, 1.0); + color = color0; + } +*/ +static const uint8_t _sgl_vs_source_glsl300es[481] = { + 0x23,0x76,0x65,0x72,0x73,0x69,0x6f,0x6e,0x20,0x33,0x30,0x30,0x20,0x65,0x73,0x0a, + 0x0a,0x75,0x6e,0x69,0x66,0x6f,0x72,0x6d,0x20,0x76,0x65,0x63,0x34,0x20,0x76,0x73, + 0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x38,0x5d,0x3b,0x0a,0x6c,0x61,0x79,0x6f, + 0x75,0x74,0x28,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x30,0x29, + 0x20,0x69,0x6e,0x20,0x76,0x65,0x63,0x34,0x20,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f, + 0x6e,0x3b,0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,0x61,0x74,0x69, + 0x6f,0x6e,0x20,0x3d,0x20,0x33,0x29,0x20,0x69,0x6e,0x20,0x66,0x6c,0x6f,0x61,0x74, + 0x20,0x70,0x73,0x69,0x7a,0x65,0x3b,0x0a,0x6f,0x75,0x74,0x20,0x76,0x65,0x63,0x34, + 0x20,0x75,0x76,0x3b,0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,0x61, + 0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x31,0x29,0x20,0x69,0x6e,0x20,0x76,0x65,0x63, + 0x32,0x20,0x74,0x65,0x78,0x63,0x6f,0x6f,0x72,0x64,0x30,0x3b,0x0a,0x6f,0x75,0x74, + 0x20,0x76,0x65,0x63,0x34,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x6c,0x61,0x79, + 0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x32, + 0x29,0x20,0x69,0x6e,0x20,0x76,0x65,0x63,0x34,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x30, + 0x3b,0x0a,0x0a,0x76,0x6f,0x69,0x64,0x20,0x6d,0x61,0x69,0x6e,0x28,0x29,0x0a,0x7b, + 0x0a,0x20,0x20,0x20,0x20,0x67,0x6c,0x5f,0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e, + 0x20,0x3d,0x20,0x6d,0x61,0x74,0x34,0x28,0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d, + 0x73,0x5b,0x30,0x5d,0x2c,0x20,0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b, + 0x31,0x5d,0x2c,0x20,0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x32,0x5d, + 0x2c,0x20,0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x33,0x5d,0x29,0x20, + 0x2a,0x20,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x3b,0x0a,0x20,0x20,0x20,0x20, + 0x67,0x6c,0x5f,0x50,0x6f,0x69,0x6e,0x74,0x53,0x69,0x7a,0x65,0x20,0x3d,0x20,0x70, + 0x73,0x69,0x7a,0x65,0x3b,0x0a,0x20,0x20,0x20,0x20,0x75,0x76,0x20,0x3d,0x20,0x6d, + 0x61,0x74,0x34,0x28,0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x34,0x5d, + 0x2c,0x20,0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x35,0x5d,0x2c,0x20, + 0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x36,0x5d,0x2c,0x20,0x76,0x73, + 0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x37,0x5d,0x29,0x20,0x2a,0x20,0x76,0x65, + 0x63,0x34,0x28,0x74,0x65,0x78,0x63,0x6f,0x6f,0x72,0x64,0x30,0x2c,0x20,0x30,0x2e, + 0x30,0x2c,0x20,0x31,0x2e,0x30,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x63,0x6f,0x6c, + 0x6f,0x72,0x20,0x3d,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x30,0x3b,0x0a,0x7d,0x0a,0x0a, + 0x00, +}; +/* + #version 300 es + precision mediump float; + precision highp int; + + uniform highp sampler2D tex_smp; + + layout(location = 0) out highp vec4 frag_color; + in highp vec4 uv; + in highp vec4 color; + + void main() + { + frag_color = texture(tex_smp, uv.xy) * color; + } +*/ +static const uint8_t _sgl_fs_source_glsl300es[253] = { + 0x23,0x76,0x65,0x72,0x73,0x69,0x6f,0x6e,0x20,0x33,0x30,0x30,0x20,0x65,0x73,0x0a, + 0x70,0x72,0x65,0x63,0x69,0x73,0x69,0x6f,0x6e,0x20,0x6d,0x65,0x64,0x69,0x75,0x6d, + 0x70,0x20,0x66,0x6c,0x6f,0x61,0x74,0x3b,0x0a,0x70,0x72,0x65,0x63,0x69,0x73,0x69, + 0x6f,0x6e,0x20,0x68,0x69,0x67,0x68,0x70,0x20,0x69,0x6e,0x74,0x3b,0x0a,0x0a,0x75, + 0x6e,0x69,0x66,0x6f,0x72,0x6d,0x20,0x68,0x69,0x67,0x68,0x70,0x20,0x73,0x61,0x6d, + 0x70,0x6c,0x65,0x72,0x32,0x44,0x20,0x74,0x65,0x78,0x5f,0x73,0x6d,0x70,0x3b,0x0a, + 0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e, + 0x20,0x3d,0x20,0x30,0x29,0x20,0x6f,0x75,0x74,0x20,0x68,0x69,0x67,0x68,0x70,0x20, + 0x76,0x65,0x63,0x34,0x20,0x66,0x72,0x61,0x67,0x5f,0x63,0x6f,0x6c,0x6f,0x72,0x3b, + 0x0a,0x69,0x6e,0x20,0x68,0x69,0x67,0x68,0x70,0x20,0x76,0x65,0x63,0x34,0x20,0x75, + 0x76,0x3b,0x0a,0x69,0x6e,0x20,0x68,0x69,0x67,0x68,0x70,0x20,0x76,0x65,0x63,0x34, + 0x20,0x63,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x0a,0x76,0x6f,0x69,0x64,0x20,0x6d,0x61, + 0x69,0x6e,0x28,0x29,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66,0x72,0x61,0x67,0x5f, + 0x63,0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x28, + 0x74,0x65,0x78,0x5f,0x73,0x6d,0x70,0x2c,0x20,0x75,0x76,0x2e,0x78,0x79,0x29,0x20, + 0x2a,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x7d,0x0a,0x0a,0x00, +}; +#elif defined(SOKOL_METAL) +/* + #include + #include + + using namespace metal; + + struct vs_params + { + float4x4 mvp; + float4x4 tm; + }; + + struct main0_out + { + float4 uv [[user(locn0)]]; + float4 color [[user(locn1)]]; + float4 gl_Position [[position]]; + float gl_PointSize [[point_size]]; + }; + + struct main0_in + { + float4 position [[attribute(0)]]; + float2 texcoord0 [[attribute(1)]]; + float4 color0 [[attribute(2)]]; + float psize [[attribute(3)]]; + }; + + vertex main0_out main0(main0_in in [[stage_in]], constant vs_params& _19 [[buffer(0)]]) + { + main0_out out = {}; + out.gl_Position = _19.mvp * in.position; + out.gl_PointSize = in.psize; + out.uv = _19.tm * float4(in.texcoord0, 0.0, 1.0); + out.color = in.color0; + return out; + } +*/ +static const uint8_t _sgl_vs_bytecode_metal_macos[3381] = { + 0x4d,0x54,0x4c,0x42,0x01,0x80,0x02,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x35,0x0d,0x00,0x00,0x00,0x00,0x00,0x00,0x58,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x6d,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc9,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x44,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0d,0x01,0x00,0x00,0x00,0x00,0x00,0x00, + 0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x15,0x01,0x00,0x00,0x00,0x00,0x00,0x00, + 0x20,0x0c,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x6d,0x00,0x00,0x00, + 0x4e,0x41,0x4d,0x45,0x06,0x00,0x6d,0x61,0x69,0x6e,0x30,0x00,0x54,0x59,0x50,0x45, + 0x01,0x00,0x00,0x48,0x41,0x53,0x48,0x20,0x00,0x59,0xf1,0xe4,0x22,0x0f,0x75,0x42, + 0x53,0x9c,0x2a,0x05,0xe4,0xbd,0x91,0x28,0x82,0x06,0x3f,0x0d,0xb9,0xef,0x34,0xbd, + 0xe8,0xfa,0x46,0x5c,0x66,0x1f,0xc6,0xde,0x3a,0x4f,0x46,0x46,0x54,0x18,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x56,0x45,0x52,0x53,0x08,0x00,0x01,0x00,0x08, + 0x00,0x01,0x00,0x01,0x00,0x45,0x4e,0x44,0x54,0x40,0x00,0x00,0x00,0x56,0x41,0x54, + 0x54,0x2a,0x00,0x04,0x00,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x00,0x00,0x80, + 0x74,0x65,0x78,0x63,0x6f,0x6f,0x72,0x64,0x30,0x00,0x01,0x80,0x63,0x6f,0x6c,0x6f, + 0x72,0x30,0x00,0x02,0x80,0x70,0x73,0x69,0x7a,0x65,0x00,0x03,0x80,0x56,0x41,0x54, + 0x59,0x06,0x00,0x04,0x00,0x06,0x04,0x06,0x03,0x45,0x4e,0x44,0x54,0x04,0x00,0x00, + 0x00,0x45,0x4e,0x44,0x54,0xde,0xc0,0x17,0x0b,0x00,0x00,0x00,0x00,0x14,0x00,0x00, + 0x00,0x04,0x0c,0x00,0x00,0xff,0xff,0xff,0xff,0x42,0x43,0xc0,0xde,0x21,0x0c,0x00, + 0x00,0xfe,0x02,0x00,0x00,0x0b,0x82,0x20,0x00,0x02,0x00,0x00,0x00,0x12,0x00,0x00, + 0x00,0x07,0x81,0x23,0x91,0x41,0xc8,0x04,0x49,0x06,0x10,0x32,0x39,0x92,0x01,0x84, + 0x0c,0x25,0x05,0x08,0x19,0x1e,0x04,0x8b,0x62,0x80,0x14,0x45,0x02,0x42,0x92,0x0b, + 0x42,0xa4,0x10,0x32,0x14,0x38,0x08,0x18,0x49,0x0a,0x32,0x44,0x24,0x48,0x0a,0x90, + 0x21,0x23,0xc4,0x52,0x80,0x0c,0x19,0x21,0x72,0x24,0x07,0xc8,0x48,0x11,0x62,0xa8, + 0xa0,0xa8,0x40,0xc6,0xf0,0x01,0x00,0x00,0x00,0x51,0x18,0x00,0x00,0x68,0x00,0x00, + 0x00,0x1b,0x7e,0x24,0xf8,0xff,0xff,0xff,0xff,0x01,0x90,0x00,0x8a,0x08,0x07,0x78, + 0x80,0x07,0x79,0x78,0x07,0x7c,0x68,0x03,0x73,0xa8,0x07,0x77,0x18,0x87,0x36,0x30, + 0x07,0x78,0x68,0x83,0x76,0x08,0x07,0x7a,0x40,0x07,0x80,0x1e,0xe4,0xa1,0x1e,0xca, + 0x01,0x20,0xda,0x21,0x1d,0xdc,0xa1,0x0d,0xd8,0xa1,0x1c,0xce,0x21,0x1c,0xd8,0xa1, + 0x0d,0xec,0xa1,0x1c,0xc6,0x81,0x1e,0xde,0x41,0x1e,0xda,0xe0,0x1e,0xd2,0x81,0x1c, + 0xe8,0x01,0x1d,0x80,0x38,0x90,0x03,0x3c,0x00,0x06,0x77,0x78,0x87,0x36,0x10,0x87, + 0x7a,0x48,0x07,0x76,0xa0,0x87,0x74,0x70,0x87,0x79,0x00,0x08,0x77,0x78,0x87,0x36, + 0x30,0x07,0x79,0x08,0x87,0x76,0x28,0x87,0x36,0x80,0x87,0x77,0x48,0x07,0x77,0xa0, + 0x87,0x72,0x90,0x87,0x36,0x28,0x07,0x76,0x48,0x87,0x76,0x00,0xe8,0x41,0x1e,0xea, + 0xa1,0x1c,0x00,0xc2,0x1d,0xde,0xa1,0x0d,0xcc,0x41,0x1e,0xc2,0xa1,0x1d,0xca,0xa1, + 0x0d,0xe0,0xe1,0x1d,0xd2,0xc1,0x1d,0xe8,0xa1,0x1c,0xe4,0xa1,0x0d,0xca,0x81,0x1d, + 0xd2,0xa1,0x1d,0xda,0xc0,0x1d,0xde,0xc1,0x1d,0xda,0x80,0x1d,0xca,0x21,0x1c,0xcc, + 0x01,0xa0,0x07,0x79,0xa8,0x87,0x72,0x00,0x08,0x77,0x78,0x87,0x36,0x48,0x07,0x77, + 0x30,0x87,0x79,0x68,0x03,0x73,0x80,0x87,0x36,0x68,0x87,0x70,0xa0,0x07,0x74,0x00, + 0xe8,0x41,0x1e,0xea,0xa1,0x1c,0x00,0xc2,0x1d,0xde,0xa1,0x0d,0xdc,0x21,0x1c,0xdc, + 0x61,0x1e,0xda,0xc0,0x1c,0xe0,0xa1,0x0d,0xda,0x21,0x1c,0xe8,0x01,0x1d,0x00,0x7a, + 0x90,0x87,0x7a,0x28,0x07,0x80,0x70,0x87,0x77,0x68,0x83,0x79,0x48,0x87,0x73,0x70, + 0x87,0x72,0x20,0x87,0x36,0xd0,0x87,0x72,0x90,0x87,0x77,0x98,0x87,0x36,0x30,0x07, + 0x78,0x68,0x83,0x76,0x08,0x07,0x7a,0x40,0x07,0x80,0x1e,0xe4,0xa1,0x1e,0xca,0x01, + 0x20,0xdc,0xe1,0x1d,0xda,0x80,0x1e,0xe4,0x21,0x1c,0xe0,0x01,0x1e,0xd2,0xc1,0x1d, + 0xce,0xa1,0x0d,0xda,0x21,0x1c,0xe8,0x01,0x1d,0x00,0x7a,0x90,0x87,0x7a,0x28,0x07, + 0x80,0x98,0x07,0x7a,0x08,0x87,0x71,0x58,0x87,0x36,0x80,0x07,0x79,0x78,0x07,0x7a, + 0x28,0x87,0x71,0xa0,0x87,0x77,0x90,0x87,0x36,0x10,0x87,0x7a,0x30,0x07,0x73,0x28, + 0x07,0x79,0x68,0x83,0x79,0x48,0x07,0x7d,0x28,0x07,0x00,0x0f,0x00,0xa2,0x1e,0xdc, + 0x61,0x1e,0xc2,0xc1,0x1c,0xca,0xa1,0x0d,0xcc,0x01,0x1e,0xda,0xa0,0x1d,0xc2,0x81, + 0x1e,0xd0,0x01,0xa0,0x07,0x79,0xa8,0x87,0x72,0x00,0x36,0x18,0x42,0x01,0x2c,0x40, + 0x05,0x49,0x18,0x00,0x00,0x01,0x00,0x00,0x00,0x13,0x84,0x40,0x00,0x89,0x20,0x00, + 0x00,0x20,0x00,0x00,0x00,0x32,0x22,0x48,0x09,0x20,0x64,0x85,0x04,0x93,0x22,0xa4, + 0x84,0x04,0x93,0x22,0xe3,0x84,0xa1,0x90,0x14,0x12,0x4c,0x8a,0x8c,0x0b,0x84,0xa4, + 0x4c,0x10,0x44,0x33,0x00,0xc3,0x08,0x04,0x60,0x89,0x10,0x02,0x18,0x46,0x10,0x80, + 0x24,0x08,0x33,0x51,0xf3,0x40,0x0f,0xf2,0x50,0x0f,0xe3,0x40,0x0f,0x6e,0xd0,0x0e, + 0xe5,0x40,0x0f,0xe1,0xc0,0x0e,0x7a,0xa0,0x07,0xed,0x10,0x0e,0xf4,0x20,0x0f,0xe9, + 0x80,0x0f,0x28,0x20,0x07,0x49,0x53,0x44,0x09,0x93,0x5f,0x49,0xff,0x03,0x44,0x00, + 0x23,0x21,0xa1,0x94,0x41,0x04,0x43,0x28,0x86,0x08,0x23,0x80,0x43,0x68,0x20,0x60, + 0x8e,0x00,0x0c,0x52,0x60,0xcd,0x11,0x80,0xc2,0x20,0x42,0x20,0x0c,0x23,0x10,0xcb, + 0x08,0x00,0x00,0x00,0x00,0x13,0xb2,0x70,0x48,0x07,0x79,0xb0,0x03,0x3a,0x68,0x83, + 0x70,0x80,0x07,0x78,0x60,0x87,0x72,0x68,0x83,0x76,0x08,0x87,0x71,0x78,0x87,0x79, + 0xc0,0x87,0x38,0x80,0x03,0x37,0x88,0x83,0x38,0x70,0x03,0x38,0xd8,0x70,0x1b,0xe5, + 0xd0,0x06,0xf0,0xa0,0x07,0x76,0x40,0x07,0x7a,0x60,0x07,0x74,0xa0,0x07,0x76,0x40, + 0x07,0x6d,0x90,0x0e,0x71,0xa0,0x07,0x78,0xa0,0x07,0x78,0xd0,0x06,0xe9,0x80,0x07, + 0x7a,0x80,0x07,0x7a,0x80,0x07,0x6d,0x90,0x0e,0x71,0x60,0x07,0x7a,0x10,0x07,0x76, + 0xa0,0x07,0x71,0x60,0x07,0x6d,0x90,0x0e,0x73,0x20,0x07,0x7a,0x30,0x07,0x72,0xa0, + 0x07,0x73,0x20,0x07,0x6d,0x90,0x0e,0x76,0x40,0x07,0x7a,0x60,0x07,0x74,0xa0,0x07, + 0x76,0x40,0x07,0x6d,0x60,0x0e,0x73,0x20,0x07,0x7a,0x30,0x07,0x72,0xa0,0x07,0x73, + 0x20,0x07,0x6d,0x60,0x0e,0x76,0x40,0x07,0x7a,0x60,0x07,0x74,0xa0,0x07,0x76,0x40, + 0x07,0x6d,0x60,0x0f,0x71,0x60,0x07,0x7a,0x10,0x07,0x76,0xa0,0x07,0x71,0x60,0x07, + 0x6d,0x60,0x0f,0x72,0x40,0x07,0x7a,0x30,0x07,0x72,0xa0,0x07,0x73,0x20,0x07,0x6d, + 0x60,0x0f,0x73,0x20,0x07,0x7a,0x30,0x07,0x72,0xa0,0x07,0x73,0x20,0x07,0x6d,0x60, + 0x0f,0x74,0x80,0x07,0x7a,0x60,0x07,0x74,0xa0,0x07,0x76,0x40,0x07,0x6d,0x60,0x0f, + 0x76,0x40,0x07,0x7a,0x60,0x07,0x74,0xa0,0x07,0x76,0x40,0x07,0x6d,0x60,0x0f,0x79, + 0x60,0x07,0x7a,0x10,0x07,0x72,0x80,0x07,0x7a,0x10,0x07,0x72,0x80,0x07,0x6d,0x60, + 0x0f,0x71,0x20,0x07,0x78,0xa0,0x07,0x71,0x20,0x07,0x78,0xa0,0x07,0x71,0x20,0x07, + 0x78,0xd0,0x06,0xf6,0x10,0x07,0x79,0x20,0x07,0x7a,0x20,0x07,0x75,0x60,0x07,0x7a, + 0x20,0x07,0x75,0x60,0x07,0x6d,0x60,0x0f,0x72,0x50,0x07,0x76,0xa0,0x07,0x72,0x50, + 0x07,0x76,0xa0,0x07,0x72,0x50,0x07,0x76,0xd0,0x06,0xf6,0x50,0x07,0x71,0x20,0x07, + 0x7a,0x50,0x07,0x71,0x20,0x07,0x7a,0x50,0x07,0x71,0x20,0x07,0x6d,0x60,0x0f,0x71, + 0x00,0x07,0x72,0x40,0x07,0x7a,0x10,0x07,0x70,0x20,0x07,0x74,0xa0,0x07,0x71,0x00, + 0x07,0x72,0x40,0x07,0x6d,0xe0,0x0e,0x78,0xa0,0x07,0x71,0x60,0x07,0x7a,0x30,0x07, + 0x72,0x30,0x84,0x49,0x00,0x00,0x08,0x00,0x00,0x00,0x00,0x00,0xc8,0x02,0x01,0x00, + 0x00,0x0b,0x00,0x00,0x00,0x32,0x1e,0x98,0x10,0x19,0x11,0x4c,0x90,0x8c,0x09,0x26, + 0x47,0xc6,0x04,0x43,0x5a,0x25,0x30,0x02,0x50,0x04,0x05,0x18,0x50,0x08,0x65,0x50, + 0x80,0x02,0x05,0x51,0x20,0xd4,0x46,0x00,0x88,0x8d,0x25,0x3c,0x00,0x00,0x00,0x00, + 0x00,0x79,0x18,0x00,0x00,0x01,0x01,0x00,0x00,0x1a,0x03,0x4c,0x10,0x97,0x29,0xa2, + 0x25,0x10,0xab,0x32,0xb9,0xb9,0xb4,0x37,0xb7,0x21,0xc6,0x32,0x28,0x00,0xb3,0x50, + 0xb9,0x1b,0x43,0x0b,0x93,0xfb,0x9a,0x4b,0xd3,0x2b,0x1b,0x62,0x2c,0x81,0x22,0x2c, + 0x05,0xe3,0x20,0x08,0x0e,0x8e,0xad,0x0c,0xa4,0xad,0x8c,0x2e,0x8c,0x0d,0xc4,0xae, + 0x4c,0x6e,0x2e,0xed,0xcd,0x0d,0x64,0x46,0x06,0x46,0x66,0xc6,0x65,0x66,0xa6,0x06, + 0x04,0xa5,0xad,0x8c,0x2e,0x8c,0xcd,0xac,0xac,0x65,0x46,0x06,0x46,0x66,0xc6,0x65, + 0x66,0xa6,0x26,0x65,0x88,0xa0,0x10,0x43,0x8c,0x25,0x58,0x90,0x45,0x60,0xd1,0x54, + 0x46,0x17,0xc6,0x36,0x04,0x51,0x8e,0x25,0x58,0x82,0x45,0xe0,0x16,0x96,0x26,0xe7, + 0x32,0xf6,0xd6,0x06,0x97,0xc6,0x56,0xe6,0x42,0x56,0xe6,0xf6,0x26,0xd7,0x36,0xf7, + 0x45,0x96,0x36,0x17,0x26,0xc6,0x56,0x36,0x44,0x50,0x12,0x72,0x61,0x69,0x72,0x2e, + 0x63,0x6f,0x6d,0x70,0x69,0x6c,0x65,0x2e,0x66,0x61,0x73,0x74,0x5f,0x6d,0x61,0x74, + 0x68,0x5f,0x65,0x6e,0x61,0x62,0x6c,0x65,0x43,0x04,0x65,0x61,0x19,0x84,0xa5,0xc9, + 0xb9,0x8c,0xbd,0xb5,0xc1,0xa5,0xb1,0x95,0xb9,0x98,0xc9,0x85,0xb5,0x95,0x89,0xd5, + 0x99,0x99,0x95,0xc9,0x7d,0x99,0x95,0xd1,0x8d,0xa1,0x7d,0x91,0xa5,0xcd,0x85,0x89, + 0xb1,0x95,0x0d,0x11,0x94,0x86,0x51,0x58,0x9a,0x9c,0x8b,0x5d,0x99,0x1c,0x5d,0x19, + 0xde,0xd7,0x5b,0x1d,0x1d,0x5c,0x1d,0x1d,0x97,0xba,0xb9,0x32,0x39,0x14,0xb6,0xb7, + 0x31,0x37,0x98,0x14,0x46,0x61,0x69,0x72,0x2e,0x61,0x72,0x67,0x5f,0x74,0x79,0x70, + 0x65,0x5f,0x6e,0x61,0x6d,0x65,0x34,0xcc,0xd8,0xde,0xc2,0xe8,0x68,0xc8,0x84,0xa5, + 0xc9,0xb9,0x84,0xc9,0x9d,0x7d,0xb9,0x85,0xb5,0x95,0x51,0xa8,0xb3,0x1b,0xc2,0x28, + 0x8f,0x02,0x29,0x91,0x22,0x29,0x93,0x42,0x71,0xa9,0x9b,0x2b,0x93,0x43,0x61,0x7b, + 0x1b,0x73,0x8b,0x49,0x61,0x31,0xf6,0xc6,0xf6,0x26,0x37,0x84,0x51,0x1e,0xc5,0x52, + 0x22,0x45,0x52,0x26,0xe5,0x22,0x13,0x96,0x26,0xe7,0x02,0xf7,0x36,0x97,0x46,0x97, + 0xf6,0xe6,0xc6,0xe5,0x8c,0xed,0x0b,0xea,0x6d,0x2e,0x8d,0x2e,0xed,0xcd,0x6d,0x88, + 0xa2,0x64,0x4a,0xa4,0x48,0xca,0xa4,0x68,0x74,0xc2,0xd2,0xe4,0x5c,0xe0,0xde,0xd2, + 0xdc,0xe8,0xbe,0xe6,0xd2,0xf4,0xca,0x58,0x98,0xb1,0xbd,0x85,0xd1,0x91,0x39,0x63, + 0xfb,0x82,0x7a,0x4b,0x73,0xa3,0x9b,0x4a,0xd3,0x2b,0x1b,0xa2,0x28,0x9c,0x12,0x29, + 0x9d,0x32,0x29,0xde,0x10,0x44,0xa9,0x14,0x4c,0xd9,0x94,0x8f,0x50,0x58,0x9a,0x9c, + 0x8b,0x5d,0x99,0x1c,0x5d,0x19,0xde,0x57,0x9a,0x1b,0x5c,0x1d,0x1d,0xa5,0xb0,0x34, + 0x39,0x17,0xb6,0xb7,0xb1,0x30,0xba,0xb4,0x37,0xb7,0xaf,0x34,0x37,0xb2,0x32,0x3c, + 0x7a,0x67,0x65,0x6e,0x65,0x72,0x61,0x74,0x65,0x64,0x28,0x5f,0x5f,0x61,0x69,0x72, + 0x5f,0x70,0x6c,0x61,0x63,0x65,0x68,0x6f,0x6c,0x64,0x65,0x72,0x5f,0x5f,0x29,0x44, + 0xe0,0xde,0xe6,0xd2,0xe8,0xd2,0xde,0xdc,0x86,0x50,0x8b,0xa0,0x84,0x81,0x22,0x06, + 0x8b,0xb0,0x04,0xca,0x18,0x28,0x91,0x22,0x29,0x93,0x42,0x06,0x34,0xcc,0xd8,0xde, + 0xc2,0xe8,0x64,0x98,0xd0,0x95,0xe1,0x8d,0xbd,0xbd,0xc9,0x91,0xc1,0x0c,0xa1,0x96, + 0x40,0x09,0x03,0x45,0x0c,0x96,0x60,0x09,0x94,0x31,0x50,0x22,0xc5,0x0c,0x94,0x49, + 0x39,0x03,0x1a,0x63,0x6f,0x6c,0x6f,0x72,0x30,0x43,0xa8,0x65,0x50,0xc2,0x40,0x11, + 0x83,0x65,0x58,0x02,0x65,0x0c,0x94,0x48,0x91,0x94,0x49,0x49,0x03,0x16,0x70,0x73, + 0x69,0x7a,0x65,0x43,0xa8,0xc5,0x50,0xc2,0x40,0x11,0x83,0xc5,0x58,0x02,0x65,0x0c, + 0x94,0x48,0xe9,0x94,0x49,0x59,0x03,0x2a,0x61,0x69,0x72,0x2e,0x62,0x75,0x66,0x66, + 0x65,0x72,0x7c,0xc2,0xd2,0xe4,0x5c,0xc4,0xea,0xcc,0xcc,0xca,0xe4,0xbe,0xe6,0xd2, + 0xf4,0xca,0x88,0x84,0xa5,0xc9,0xb9,0xc8,0x95,0x85,0x91,0x91,0x0a,0x4b,0x93,0x73, + 0x99,0xa3,0x93,0xab,0x1b,0xa3,0xfb,0xa2,0xcb,0x83,0x2b,0xfb,0x4a,0x73,0x33,0x7b, + 0x23,0x62,0xc6,0xf6,0x16,0x46,0x47,0x83,0x47,0xc3,0xa1,0xcd,0x0e,0x8e,0x02,0x5d, + 0xdb,0x10,0x6a,0x11,0x16,0x62,0x11,0x94,0x38,0x50,0xe4,0x60,0x21,0x16,0x62,0x11, + 0x94,0x38,0x50,0xe6,0x80,0x51,0x58,0x9a,0x9c,0x4b,0x98,0xdc,0xd9,0x17,0x5d,0x1e, + 0x5c,0xd9,0xd7,0x5c,0x9a,0x5e,0x19,0xaf,0xb0,0x34,0x39,0x97,0x30,0xb9,0xb3,0x2f, + 0xba,0x3c,0xb8,0xb2,0xaf,0x30,0xb6,0xb4,0x33,0xb7,0xaf,0xb9,0x34,0xbd,0x32,0x26, + 0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x1c,0xbe,0x62,0x72,0x86,0x90,0xc1, + 0x52,0x28,0x6d,0xa0,0xb8,0xc1,0x72,0x28,0x62,0xb0,0x08,0x4b,0xa0,0xbc,0x81,0x02, + 0x07,0x0a,0x1d,0x28,0x75,0xb0,0x1c,0x8a,0x1d,0x2c,0x89,0x12,0x29,0x77,0xa0,0x4c, + 0x0a,0x1e,0x0c,0x51,0x94,0x32,0x50,0xd0,0x40,0x51,0x03,0x85,0x0d,0x94,0x3c,0x18, + 0x62,0x24,0x80,0x02,0x06,0x8a,0x1e,0xf0,0x79,0x6b,0x73,0x4b,0x83,0x7b,0xa3,0x2b, + 0x73,0xa3,0x03,0x19,0x43,0x0b,0x93,0xe3,0x33,0x95,0xd6,0x06,0xc7,0x56,0x06,0x32, + 0xb4,0xb2,0x02,0x42,0x25,0x14,0x14,0x34,0x44,0x50,0xfa,0x60,0x88,0xa1,0xf0,0x81, + 0xe2,0x07,0x8d,0x32,0xc4,0x50,0xfe,0x40,0xf9,0x83,0x46,0x19,0x11,0xb1,0x03,0x3b, + 0xd8,0x43,0x3b,0xb8,0x41,0x3b,0xbc,0x03,0x39,0xd4,0x03,0x3b,0x94,0x83,0x1b,0x98, + 0x03,0x3b,0x84,0xc3,0x39,0xcc,0xc3,0x14,0x21,0x18,0x46,0x28,0xec,0xc0,0x0e,0xf6, + 0xd0,0x0e,0x6e,0x90,0x0e,0xe4,0x50,0x0e,0xee,0x40,0x0f,0x53,0x82,0x62,0xc4,0x12, + 0x0e,0xe9,0x20,0x0f,0x6e,0x60,0x0f,0xe5,0x20,0x0f,0xf3,0x90,0x0e,0xef,0xe0,0x0e, + 0x53,0x02,0x63,0x04,0x15,0x0e,0xe9,0x20,0x0f,0x6e,0xc0,0x0e,0xe1,0xe0,0x0e,0xe7, + 0x50,0x0f,0xe1,0x70,0x0e,0xe5,0xf0,0x0b,0xf6,0x50,0x0e,0xf2,0x30,0x0f,0xe9,0xf0, + 0x0e,0xee,0x30,0x25,0x40,0x46,0x4c,0xe1,0x90,0x0e,0xf2,0xe0,0x06,0xe3,0xf0,0x0e, + 0xed,0x00,0x0f,0xe9,0xc0,0x0e,0xe5,0xf0,0x0b,0xef,0x00,0x0f,0xf4,0x90,0x0e,0xef, + 0xe0,0x0e,0xf3,0x30,0x65,0x50,0x18,0x67,0x84,0x12,0x0e,0xe9,0x20,0x0f,0x6e,0x60, + 0x0f,0xe5,0x20,0x0f,0xf4,0x50,0x0e,0xf8,0x30,0x25,0xd8,0x03,0x00,0x79,0x18,0x00, + 0x00,0xa5,0x00,0x00,0x00,0x33,0x08,0x80,0x1c,0xc4,0xe1,0x1c,0x66,0x14,0x01,0x3d, + 0x88,0x43,0x38,0x84,0xc3,0x8c,0x42,0x80,0x07,0x79,0x78,0x07,0x73,0x98,0x71,0x0c, + 0xe6,0x00,0x0f,0xed,0x10,0x0e,0xf4,0x80,0x0e,0x33,0x0c,0x42,0x1e,0xc2,0xc1,0x1d, + 0xce,0xa1,0x1c,0x66,0x30,0x05,0x3d,0x88,0x43,0x38,0x84,0x83,0x1b,0xcc,0x03,0x3d, + 0xc8,0x43,0x3d,0x8c,0x03,0x3d,0xcc,0x78,0x8c,0x74,0x70,0x07,0x7b,0x08,0x07,0x79, + 0x48,0x87,0x70,0x70,0x07,0x7a,0x70,0x03,0x76,0x78,0x87,0x70,0x20,0x87,0x19,0xcc, + 0x11,0x0e,0xec,0x90,0x0e,0xe1,0x30,0x0f,0x6e,0x30,0x0f,0xe3,0xf0,0x0e,0xf0,0x50, + 0x0e,0x33,0x10,0xc4,0x1d,0xde,0x21,0x1c,0xd8,0x21,0x1d,0xc2,0x61,0x1e,0x66,0x30, + 0x89,0x3b,0xbc,0x83,0x3b,0xd0,0x43,0x39,0xb4,0x03,0x3c,0xbc,0x83,0x3c,0x84,0x03, + 0x3b,0xcc,0xf0,0x14,0x76,0x60,0x07,0x7b,0x68,0x07,0x37,0x68,0x87,0x72,0x68,0x07, + 0x37,0x80,0x87,0x70,0x90,0x87,0x70,0x60,0x07,0x76,0x28,0x07,0x76,0xf8,0x05,0x76, + 0x78,0x87,0x77,0x80,0x87,0x5f,0x08,0x87,0x71,0x18,0x87,0x72,0x98,0x87,0x79,0x98, + 0x81,0x2c,0xee,0xf0,0x0e,0xee,0xe0,0x0e,0xf5,0xc0,0x0e,0xec,0x30,0x03,0x62,0xc8, + 0xa1,0x1c,0xe4,0xa1,0x1c,0xcc,0xa1,0x1c,0xe4,0xa1,0x1c,0xdc,0x61,0x1c,0xca,0x21, + 0x1c,0xc4,0x81,0x1d,0xca,0x61,0x06,0xd6,0x90,0x43,0x39,0xc8,0x43,0x39,0x98,0x43, + 0x39,0xc8,0x43,0x39,0xb8,0xc3,0x38,0x94,0x43,0x38,0x88,0x03,0x3b,0x94,0xc3,0x2f, + 0xbc,0x83,0x3c,0xfc,0x82,0x3b,0xd4,0x03,0x3b,0xb0,0xc3,0x0c,0xc7,0x69,0x87,0x70, + 0x58,0x87,0x72,0x70,0x83,0x74,0x68,0x07,0x78,0x60,0x87,0x74,0x18,0x87,0x74,0xa0, + 0x87,0x19,0xce,0x53,0x0f,0xee,0x00,0x0f,0xf2,0x50,0x0e,0xe4,0x90,0x0e,0xe3,0x40, + 0x0f,0xe1,0x20,0x0e,0xec,0x50,0x0e,0x33,0x20,0x28,0x1d,0xdc,0xc1,0x1e,0xc2,0x41, + 0x1e,0xd2,0x21,0x1c,0xdc,0x81,0x1e,0xdc,0xe0,0x1c,0xe4,0xe1,0x1d,0xea,0x01,0x1e, + 0x66,0x18,0x51,0x38,0xb0,0x43,0x3a,0x9c,0x83,0x3b,0xcc,0x50,0x24,0x76,0x60,0x07, + 0x7b,0x68,0x07,0x37,0x60,0x87,0x77,0x78,0x07,0x78,0x98,0x51,0x4c,0xf4,0x90,0x0f, + 0xf0,0x50,0x0e,0x33,0x1e,0x6a,0x1e,0xca,0x61,0x1c,0xe8,0x21,0x1d,0xde,0xc1,0x1d, + 0x7e,0x01,0x1e,0xe4,0xa1,0x1c,0xcc,0x21,0x1d,0xf0,0x61,0x06,0x54,0x85,0x83,0x38, + 0xcc,0xc3,0x3b,0xb0,0x43,0x3d,0xd0,0x43,0x39,0xfc,0xc2,0x3c,0xe4,0x43,0x3b,0x88, + 0xc3,0x3b,0xb0,0xc3,0x8c,0xc5,0x0a,0x87,0x79,0x98,0x87,0x77,0x18,0x87,0x74,0x08, + 0x07,0x7a,0x28,0x07,0x72,0x98,0x81,0x5c,0xe3,0x10,0x0e,0xec,0xc0,0x0e,0xe5,0x50, + 0x0e,0xf3,0x30,0x23,0xc1,0xd2,0x41,0x1e,0xe4,0xe1,0x17,0xd8,0xe1,0x1d,0xde,0x01, + 0x1e,0x66,0x48,0x19,0x3b,0xb0,0x83,0x3d,0xb4,0x83,0x1b,0x84,0xc3,0x38,0x8c,0x43, + 0x39,0xcc,0xc3,0x3c,0xb8,0xc1,0x39,0xc8,0xc3,0x3b,0xd4,0x03,0x3c,0xcc,0x48,0xb4, + 0x71,0x08,0x07,0x76,0x60,0x07,0x71,0x08,0x87,0x71,0x58,0x87,0x19,0xdb,0xc6,0x0e, + 0xec,0x60,0x0f,0xed,0xe0,0x06,0xf0,0x20,0x0f,0xe5,0x30,0x0f,0xe5,0x20,0x0f,0xf6, + 0x50,0x0e,0x6e,0x10,0x0e,0xe3,0x30,0x0e,0xe5,0x30,0x0f,0xf3,0xe0,0x06,0xe9,0xe0, + 0x0e,0xe4,0x50,0x0e,0xf8,0x30,0x23,0xe2,0xec,0x61,0x1c,0xc2,0x81,0x1d,0xd8,0xe1, + 0x17,0xec,0x21,0x1d,0xe6,0x21,0x1d,0xc4,0x21,0x1d,0xd8,0x21,0x1d,0xe8,0x21,0x1f, + 0x66,0x20,0x9d,0x3b,0xbc,0x43,0x3d,0xb8,0x03,0x39,0x94,0x83,0x39,0xcc,0x58,0xbc, + 0x70,0x70,0x07,0x77,0x78,0x07,0x7a,0x08,0x07,0x7a,0x48,0x87,0x77,0x70,0x87,0x19, + 0xce,0x87,0x0e,0xe5,0x10,0x0e,0xf0,0x10,0x0e,0xec,0xc0,0x0e,0xef,0x30,0x0e,0xf3, + 0x90,0x0e,0xf4,0x50,0x0e,0x33,0x28,0x30,0x08,0x87,0x74,0x90,0x07,0x37,0x30,0x87, + 0x7a,0x70,0x87,0x71,0xa0,0x87,0x74,0x78,0x07,0x77,0xf8,0x85,0x73,0x90,0x87,0x77, + 0xa8,0x07,0x78,0x98,0x07,0x00,0x00,0x00,0x00,0x71,0x20,0x00,0x00,0x02,0x00,0x00, + 0x00,0x06,0x50,0x30,0x00,0xd2,0xd0,0x00,0x00,0x61,0x20,0x00,0x00,0x3e,0x00,0x00, + 0x00,0x13,0x04,0x41,0x2c,0x10,0x00,0x00,0x00,0x09,0x00,0x00,0x00,0xf4,0xc6,0x22, + 0x86,0x61,0x18,0xc6,0x22,0x04,0x41,0x10,0xc6,0x22,0x82,0x20,0x08,0x46,0x00,0xa8, + 0x95,0x40,0x19,0x14,0x01,0x8d,0x19,0x00,0x12,0x33,0x00,0x14,0x66,0x00,0x66,0x00, + 0x00,0xe3,0x15,0x8c,0xa4,0x69,0x12,0x05,0x65,0x90,0x41,0x22,0x14,0x13,0x02,0xf9, + 0x8c,0x57,0x40,0x96,0xe7,0x2d,0x14,0x94,0x41,0x06,0xeb,0x78,0x4c,0x08,0xe4,0x63, + 0x41,0x01,0x9f,0xf1,0x8a,0x6a,0x1b,0x83,0x31,0x70,0x28,0x28,0x83,0x0c,0x1b,0x53, + 0x99,0x10,0xc8,0xc7,0x8a,0x00,0x3e,0xe3,0x15,0x1a,0x18,0xa0,0x01,0x1a,0x50,0x14, + 0x94,0x41,0x06,0x30,0x88,0x36,0x13,0x02,0xf9,0x58,0x11,0xc0,0x67,0xbc,0xe2,0x2b, + 0x03,0x37,0x68,0x83,0x32,0xa0,0xa0,0x0c,0x32,0x90,0x41,0xd6,0x99,0x10,0xc8,0x67, + 0xbc,0x62,0x0c,0xd2,0x40,0x0e,0xe2,0xc0,0xa3,0xa0,0x0c,0x32,0xa0,0x41,0x27,0x06, + 0x26,0x04,0xf2,0xb1,0xa0,0x80,0xcf,0x78,0x05,0x1a,0xb8,0xc1,0x1d,0xd8,0x81,0x18, + 0x50,0x50,0x6c,0x08,0xe0,0x33,0xdb,0x20,0x06,0x01,0x30,0xdb,0x10,0xb8,0x41,0x30, + 0xdb,0x10,0x3c,0xc2,0x6c,0x43,0xf0,0x06,0x43,0x06,0x01,0x31,0x00,0x09,0x00,0x00, + 0x00,0x5b,0x86,0x20,0x00,0x85,0x2d,0x43,0x11,0x80,0xc2,0x96,0x41,0x09,0x40,0x61, + 0xcb,0xf0,0x04,0xa0,0xb0,0x65,0xa0,0x02,0x50,0xd8,0x32,0x60,0x01,0x28,0x6c,0x19, + 0xba,0x00,0x14,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00, +}; +/* + #include + #include + + using namespace metal; + + struct main0_out + { + float4 frag_color [[color(0)]]; + }; + + struct main0_in + { + float4 uv [[user(locn0)]]; + float4 color [[user(locn1)]]; + }; + + fragment main0_out main0(main0_in in [[stage_in]], texture2d tex [[texture(0)]], sampler smp [[sampler(0)]]) + { + main0_out out = {}; + out.frag_color = tex.sample(smp, in.uv.xy) * in.color; + return out; + } +*/ +static const uint8_t _sgl_fs_bytecode_metal_macos[3033] = { + 0x4d,0x54,0x4c,0x42,0x01,0x80,0x02,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xd9,0x0b,0x00,0x00,0x00,0x00,0x00,0x00,0x58,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x6d,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc9,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xd1,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xd9,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x0b,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x6d,0x00,0x00,0x00, + 0x4e,0x41,0x4d,0x45,0x06,0x00,0x6d,0x61,0x69,0x6e,0x30,0x00,0x54,0x59,0x50,0x45, + 0x01,0x00,0x01,0x48,0x41,0x53,0x48,0x20,0x00,0x43,0x4f,0xc0,0x79,0x15,0x0f,0x7e, + 0x56,0x86,0x83,0xab,0x09,0x97,0xeb,0xad,0x1f,0xad,0xc5,0x99,0xa0,0x69,0x5d,0x31, + 0x4f,0x5e,0x5b,0x06,0x1b,0x6c,0x91,0x10,0xa7,0x4f,0x46,0x46,0x54,0x18,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x56,0x45,0x52,0x53,0x08,0x00,0x01,0x00,0x08, + 0x00,0x01,0x00,0x01,0x00,0x45,0x4e,0x44,0x54,0x04,0x00,0x00,0x00,0x45,0x4e,0x44, + 0x54,0x04,0x00,0x00,0x00,0x45,0x4e,0x44,0x54,0xde,0xc0,0x17,0x0b,0x00,0x00,0x00, + 0x00,0x14,0x00,0x00,0x00,0xe4,0x0a,0x00,0x00,0xff,0xff,0xff,0xff,0x42,0x43,0xc0, + 0xde,0x21,0x0c,0x00,0x00,0xb6,0x02,0x00,0x00,0x0b,0x82,0x20,0x00,0x02,0x00,0x00, + 0x00,0x12,0x00,0x00,0x00,0x07,0x81,0x23,0x91,0x41,0xc8,0x04,0x49,0x06,0x10,0x32, + 0x39,0x92,0x01,0x84,0x0c,0x25,0x05,0x08,0x19,0x1e,0x04,0x8b,0x62,0x80,0x14,0x45, + 0x02,0x42,0x92,0x0b,0x42,0xa4,0x10,0x32,0x14,0x38,0x08,0x18,0x49,0x0a,0x32,0x44, + 0x24,0x48,0x0a,0x90,0x21,0x23,0xc4,0x52,0x80,0x0c,0x19,0x21,0x72,0x24,0x07,0xc8, + 0x48,0x11,0x62,0xa8,0xa0,0xa8,0x40,0xc6,0xf0,0x01,0x00,0x00,0x00,0x51,0x18,0x00, + 0x00,0x74,0x00,0x00,0x00,0x1b,0xc2,0x24,0xf8,0xff,0xff,0xff,0xff,0x01,0x60,0x00, + 0x09,0xa8,0x88,0x70,0x80,0x07,0x78,0x90,0x87,0x77,0xc0,0x87,0x36,0x30,0x87,0x7a, + 0x70,0x87,0x71,0x68,0x03,0x73,0x80,0x87,0x36,0x68,0x87,0x70,0xa0,0x07,0x74,0x00, + 0xe8,0x41,0x1e,0xea,0xa1,0x1c,0x00,0xa2,0x1d,0xd2,0xc1,0x1d,0xda,0x80,0x1d,0xca, + 0xe1,0x1c,0xc2,0x81,0x1d,0xda,0xc0,0x1e,0xca,0x61,0x1c,0xe8,0xe1,0x1d,0xe4,0xa1, + 0x0d,0xee,0x21,0x1d,0xc8,0x81,0x1e,0xd0,0x01,0x88,0x03,0x39,0xc0,0x03,0x60,0x70, + 0x87,0x77,0x68,0x03,0x71,0xa8,0x87,0x74,0x60,0x07,0x7a,0x48,0x07,0x77,0x98,0x07, + 0x80,0x70,0x87,0x77,0x68,0x03,0x73,0x90,0x87,0x70,0x68,0x87,0x72,0x68,0x03,0x78, + 0x78,0x87,0x74,0x70,0x07,0x7a,0x28,0x07,0x79,0x68,0x83,0x72,0x60,0x87,0x74,0x68, + 0x07,0x80,0x1e,0xe4,0xa1,0x1e,0xca,0x01,0x20,0xdc,0xe1,0x1d,0xda,0xc0,0x1c,0xe4, + 0x21,0x1c,0xda,0xa1,0x1c,0xda,0x00,0x1e,0xde,0x21,0x1d,0xdc,0x81,0x1e,0xca,0x41, + 0x1e,0xda,0xa0,0x1c,0xd8,0x21,0x1d,0xda,0xa1,0x0d,0xdc,0xe1,0x1d,0xdc,0xa1,0x0d, + 0xd8,0xa1,0x1c,0xc2,0xc1,0x1c,0x00,0x7a,0x90,0x87,0x7a,0x28,0x07,0x80,0x70,0x87, + 0x77,0x68,0x83,0x74,0x70,0x07,0x73,0x98,0x87,0x36,0x30,0x07,0x78,0x68,0x83,0x76, + 0x08,0x07,0x7a,0x40,0x07,0x80,0x1e,0xe4,0xa1,0x1e,0xca,0x01,0x20,0xdc,0xe1,0x1d, + 0xda,0xc0,0x1d,0xc2,0xc1,0x1d,0xe6,0xa1,0x0d,0xcc,0x01,0x1e,0xda,0xa0,0x1d,0xc2, + 0x81,0x1e,0xd0,0x01,0xa0,0x07,0x79,0xa8,0x87,0x72,0x00,0x08,0x77,0x78,0x87,0x36, + 0x98,0x87,0x74,0x38,0x07,0x77,0x28,0x07,0x72,0x68,0x03,0x7d,0x28,0x07,0x79,0x78, + 0x87,0x79,0x68,0x03,0x73,0x80,0x87,0x36,0x68,0x87,0x70,0xa0,0x07,0x74,0x00,0xe8, + 0x41,0x1e,0xea,0xa1,0x1c,0x00,0xc2,0x1d,0xde,0xa1,0x0d,0xe8,0x41,0x1e,0xc2,0x01, + 0x1e,0xe0,0x21,0x1d,0xdc,0xe1,0x1c,0xda,0xa0,0x1d,0xc2,0x81,0x1e,0xd0,0x01,0xa0, + 0x07,0x79,0xa8,0x87,0x72,0x00,0x88,0x79,0xa0,0x87,0x70,0x18,0x87,0x75,0x68,0x03, + 0x78,0x90,0x87,0x77,0xa0,0x87,0x72,0x18,0x07,0x7a,0x78,0x07,0x79,0x68,0x03,0x71, + 0xa8,0x07,0x73,0x30,0x87,0x72,0x90,0x87,0x36,0x98,0x87,0x74,0xd0,0x87,0x72,0x00, + 0xf0,0x00,0x20,0xea,0xc1,0x1d,0xe6,0x21,0x1c,0xcc,0xa1,0x1c,0xda,0xc0,0x1c,0xe0, + 0xa1,0x0d,0xda,0x21,0x1c,0xe8,0x01,0x1d,0x00,0x7a,0x90,0x87,0x7a,0x28,0x07,0x60, + 0x83,0x21,0x0c,0xc0,0x02,0x54,0x1b,0x8c,0x81,0x00,0x16,0xa0,0xda,0x80,0x10,0xff, + 0xff,0xff,0xff,0x3f,0x00,0x0c,0x20,0x01,0xd5,0x06,0xa3,0x08,0x80,0x05,0xa8,0x36, + 0x18,0x86,0x00,0x2c,0x40,0xb5,0x01,0x39,0xfe,0xff,0xff,0xff,0x7f,0x00,0x18,0x40, + 0x02,0x2a,0x00,0x00,0x00,0x49,0x18,0x00,0x00,0x04,0x00,0x00,0x00,0x13,0x86,0x40, + 0x18,0x26,0x0c,0x44,0x61,0x4c,0x18,0x8e,0xc2,0x00,0x00,0x00,0x00,0x89,0x20,0x00, + 0x00,0x1d,0x00,0x00,0x00,0x32,0x22,0x48,0x09,0x20,0x64,0x85,0x04,0x93,0x22,0xa4, + 0x84,0x04,0x93,0x22,0xe3,0x84,0xa1,0x90,0x14,0x12,0x4c,0x8a,0x8c,0x0b,0x84,0xa4, + 0x4c,0x10,0x48,0x33,0x00,0xc3,0x08,0x04,0x60,0x83,0x70,0x94,0x34,0x45,0x94,0x30, + 0xf9,0xff,0x44,0x5c,0x13,0x15,0x11,0xbf,0x3d,0xfc,0xd3,0x18,0x01,0x30,0x88,0x30, + 0x04,0x17,0x49,0x53,0x44,0x09,0x93,0xff,0x4b,0x00,0xf3,0x2c,0x44,0xf4,0x4f,0x63, + 0x04,0xc0,0x20,0x42,0x21,0x94,0x42,0x84,0x40,0x0c,0x9d,0x61,0x04,0x01,0x98,0x23, + 0x08,0xe6,0x08,0xc0,0x60,0x18,0x41,0x58,0x0a,0x12,0x88,0x49,0x8a,0x29,0x40,0x6d, + 0x20,0x20,0x05,0xd6,0x08,0x00,0x00,0x00,0x00,0x13,0xb2,0x70,0x48,0x07,0x79,0xb0, + 0x03,0x3a,0x68,0x83,0x70,0x80,0x07,0x78,0x60,0x87,0x72,0x68,0x83,0x76,0x08,0x87, + 0x71,0x78,0x87,0x79,0xc0,0x87,0x38,0x80,0x03,0x37,0x88,0x83,0x38,0x70,0x03,0x38, + 0xd8,0x70,0x1b,0xe5,0xd0,0x06,0xf0,0xa0,0x07,0x76,0x40,0x07,0x7a,0x60,0x07,0x74, + 0xa0,0x07,0x76,0x40,0x07,0x6d,0x90,0x0e,0x71,0xa0,0x07,0x78,0xa0,0x07,0x78,0xd0, + 0x06,0xe9,0x80,0x07,0x7a,0x80,0x07,0x7a,0x80,0x07,0x6d,0x90,0x0e,0x71,0x60,0x07, + 0x7a,0x10,0x07,0x76,0xa0,0x07,0x71,0x60,0x07,0x6d,0x90,0x0e,0x73,0x20,0x07,0x7a, + 0x30,0x07,0x72,0xa0,0x07,0x73,0x20,0x07,0x6d,0x90,0x0e,0x76,0x40,0x07,0x7a,0x60, + 0x07,0x74,0xa0,0x07,0x76,0x40,0x07,0x6d,0x60,0x0e,0x73,0x20,0x07,0x7a,0x30,0x07, + 0x72,0xa0,0x07,0x73,0x20,0x07,0x6d,0x60,0x0e,0x76,0x40,0x07,0x7a,0x60,0x07,0x74, + 0xa0,0x07,0x76,0x40,0x07,0x6d,0x60,0x0f,0x71,0x60,0x07,0x7a,0x10,0x07,0x76,0xa0, + 0x07,0x71,0x60,0x07,0x6d,0x60,0x0f,0x72,0x40,0x07,0x7a,0x30,0x07,0x72,0xa0,0x07, + 0x73,0x20,0x07,0x6d,0x60,0x0f,0x73,0x20,0x07,0x7a,0x30,0x07,0x72,0xa0,0x07,0x73, + 0x20,0x07,0x6d,0x60,0x0f,0x74,0x80,0x07,0x7a,0x60,0x07,0x74,0xa0,0x07,0x76,0x40, + 0x07,0x6d,0x60,0x0f,0x76,0x40,0x07,0x7a,0x60,0x07,0x74,0xa0,0x07,0x76,0x40,0x07, + 0x6d,0x60,0x0f,0x79,0x60,0x07,0x7a,0x10,0x07,0x72,0x80,0x07,0x7a,0x10,0x07,0x72, + 0x80,0x07,0x6d,0x60,0x0f,0x71,0x20,0x07,0x78,0xa0,0x07,0x71,0x20,0x07,0x78,0xa0, + 0x07,0x71,0x20,0x07,0x78,0xd0,0x06,0xf6,0x10,0x07,0x79,0x20,0x07,0x7a,0x20,0x07, + 0x75,0x60,0x07,0x7a,0x20,0x07,0x75,0x60,0x07,0x6d,0x60,0x0f,0x72,0x50,0x07,0x76, + 0xa0,0x07,0x72,0x50,0x07,0x76,0xa0,0x07,0x72,0x50,0x07,0x76,0xd0,0x06,0xf6,0x50, + 0x07,0x71,0x20,0x07,0x7a,0x50,0x07,0x71,0x20,0x07,0x7a,0x50,0x07,0x71,0x20,0x07, + 0x6d,0x60,0x0f,0x71,0x00,0x07,0x72,0x40,0x07,0x7a,0x10,0x07,0x70,0x20,0x07,0x74, + 0xa0,0x07,0x71,0x00,0x07,0x72,0x40,0x07,0x6d,0xe0,0x0e,0x78,0xa0,0x07,0x71,0x60, + 0x07,0x7a,0x30,0x07,0x72,0x30,0x84,0x41,0x00,0x00,0x08,0x00,0x00,0x00,0x00,0x00, + 0x18,0xc2,0x38,0x40,0x00,0x08,0x00,0x00,0x00,0x00,0x00,0x64,0x81,0x00,0x00,0x00, + 0x00,0x08,0x00,0x00,0x00,0x32,0x1e,0x98,0x10,0x19,0x11,0x4c,0x90,0x8c,0x09,0x26, + 0x47,0xc6,0x04,0x43,0x5a,0x25,0x30,0x02,0x50,0x04,0x85,0x50,0x10,0x65,0x40,0x70, + 0x2c,0xe1,0x01,0x00,0x00,0x79,0x18,0x00,0x00,0xd1,0x00,0x00,0x00,0x1a,0x03,0x4c, + 0x10,0x97,0x29,0xa2,0x25,0x10,0xab,0x32,0xb9,0xb9,0xb4,0x37,0xb7,0x21,0xc6,0x42, + 0x3c,0x00,0x84,0x50,0xb9,0x1b,0x43,0x0b,0x93,0xfb,0x9a,0x4b,0xd3,0x2b,0x1b,0x62, + 0x2c,0xc2,0x23,0x2c,0x05,0xe3,0x20,0x08,0x0e,0x8e,0xad,0x0c,0xa4,0xad,0x8c,0x2e, + 0x8c,0x0d,0xc4,0xae,0x4c,0x6e,0x2e,0xed,0xcd,0x0d,0x64,0x46,0x06,0x46,0x66,0xc6, + 0x65,0x66,0xa6,0x06,0x04,0xa5,0xad,0x8c,0x2e,0x8c,0xcd,0xac,0xac,0x65,0x46,0x06, + 0x46,0x66,0xc6,0x65,0x66,0xa6,0x26,0x65,0x88,0xf0,0x10,0x43,0x8c,0x45,0x58,0x8c, + 0x65,0x60,0xd1,0x54,0x46,0x17,0xc6,0x36,0x04,0x79,0x8e,0x45,0x58,0x84,0x65,0xe0, + 0x16,0x96,0x26,0xe7,0x32,0xf6,0xd6,0x06,0x97,0xc6,0x56,0xe6,0x42,0x56,0xe6,0xf6, + 0x26,0xd7,0x36,0xf7,0x45,0x96,0x36,0x17,0x26,0xc6,0x56,0x36,0x44,0x78,0x12,0x72, + 0x61,0x69,0x72,0x2e,0x63,0x6f,0x6d,0x70,0x69,0x6c,0x65,0x2e,0x66,0x61,0x73,0x74, + 0x5f,0x6d,0x61,0x74,0x68,0x5f,0x65,0x6e,0x61,0x62,0x6c,0x65,0x43,0x84,0x67,0x61, + 0x19,0x84,0xa5,0xc9,0xb9,0x8c,0xbd,0xb5,0xc1,0xa5,0xb1,0x95,0xb9,0x98,0xc9,0x85, + 0xb5,0x95,0x89,0xd5,0x99,0x99,0x95,0xc9,0x7d,0x99,0x95,0xd1,0x8d,0xa1,0x7d,0x91, + 0xa5,0xcd,0x85,0x89,0xb1,0x95,0x0d,0x11,0x9e,0x86,0x51,0x58,0x9a,0x9c,0x8b,0x5c, + 0x99,0x1b,0x59,0x99,0xdc,0x17,0x5d,0x98,0xdc,0x59,0x19,0x1d,0xa3,0xb0,0x34,0x39, + 0x97,0x30,0xb9,0xb3,0x2f,0xba,0x3c,0xb8,0xb2,0x2f,0xb7,0xb0,0xb6,0x32,0x1a,0x66, + 0x6c,0x6f,0x61,0x74,0x34,0x64,0xc2,0xd2,0xe4,0x5c,0xc2,0xe4,0xce,0xbe,0xdc,0xc2, + 0xda,0xca,0xa8,0x98,0xc9,0x85,0x9d,0x7d,0x8d,0xbd,0xb1,0xbd,0xc9,0x0d,0x61,0x9e, + 0x67,0x19,0x1e,0xe8,0x89,0x1e,0xe9,0x99,0x86,0x08,0x0f,0x45,0x29,0x2c,0x4d,0xce, + 0xc5,0x4c,0x2e,0xec,0xac,0xad,0xcc,0x8d,0xee,0x2b,0xcd,0x0d,0xae,0x8e,0x8e,0x4b, + 0xdd,0x5c,0x99,0x1c,0x0a,0xdb,0xdb,0x98,0x1b,0x4c,0x0a,0x95,0xb0,0x34,0x39,0x97, + 0xb1,0x32,0x37,0xba,0x32,0x39,0x3e,0x61,0x69,0x72,0x2e,0x70,0x65,0x72,0x73,0x70, + 0x65,0x63,0x74,0x69,0x76,0x65,0x14,0xea,0xec,0x86,0x48,0xcb,0xf0,0x58,0xcf,0xf5, + 0x60,0x4f,0xf6,0x40,0x4f,0xf4,0x48,0x8f,0xc6,0xa5,0x6e,0xae,0x4c,0x0e,0x85,0xed, + 0x6d,0xcc,0x2d,0x26,0x85,0xc5,0xd8,0x1b,0xdb,0x9b,0xdc,0x10,0x69,0x11,0x1e,0xeb, + 0xe1,0x1e,0xec,0xc9,0x1e,0xe8,0x89,0x1e,0xe9,0xe9,0xb8,0x84,0xa5,0xc9,0xb9,0xd0, + 0x95,0xe1,0xd1,0xd5,0xc9,0x95,0x51,0x0a,0x4b,0x93,0x73,0x61,0x7b,0x1b,0x0b,0xa3, + 0x4b,0x7b,0x73,0xfb,0x4a,0x73,0x23,0x2b,0xc3,0xa3,0x12,0x96,0x26,0xe7,0x32,0x17, + 0xd6,0x06,0xc7,0x56,0x46,0x8c,0xae,0x0c,0x8f,0xae,0x4e,0xae,0x4c,0x86,0x8c,0xc7, + 0x8c,0xed,0x2d,0x8c,0x8e,0x05,0x64,0x2e,0xac,0x0d,0x8e,0xad,0xcc,0x87,0x03,0x5d, + 0x19,0xde,0x10,0x6a,0x21,0x9e,0xef,0x01,0x83,0x65,0x58,0x84,0x27,0x0c,0x1e,0xe8, + 0x11,0x83,0x47,0x7a,0xc6,0x80,0x4b,0x58,0x9a,0x9c,0xcb,0x5c,0x58,0x1b,0x1c,0x5b, + 0x99,0x1c,0x8f,0xb9,0xb0,0x36,0x38,0xb6,0x32,0x39,0x0e,0x73,0x6d,0x70,0x43,0xa4, + 0xe5,0x78,0xca,0xe0,0x01,0x83,0x65,0x58,0x84,0x07,0x7a,0xcc,0xe0,0x91,0x9e,0x33, + 0x18,0x82,0x3c,0xdb,0xe3,0x3d,0x64,0xf0,0xa0,0xc1,0x10,0x03,0x01,0x9e,0xea,0x49, + 0x03,0x5e,0x61,0x69,0x72,0x2d,0x61,0x6c,0x69,0x61,0x73,0x2d,0x73,0x63,0x6f,0x70, + 0x65,0x73,0x28,0x6d,0x61,0x69,0x6e,0x30,0x29,0x43,0x88,0x87,0x0d,0x9e,0x35,0x20, + 0x16,0x96,0x26,0xd7,0x12,0xc6,0x96,0x16,0x36,0xd7,0x32,0x37,0xf6,0x06,0x57,0xd6, + 0x42,0x57,0x86,0x47,0x57,0x27,0x57,0x36,0x37,0xc4,0x78,0xdc,0xe0,0x61,0x83,0xa7, + 0x0d,0x88,0x85,0xa5,0xc9,0xb5,0x84,0xb1,0xa5,0x85,0xcd,0xb5,0xcc,0x8d,0xbd,0xc1, + 0x95,0xb5,0xcc,0x85,0xb5,0xc1,0xb1,0x95,0xc9,0xcd,0x0d,0x31,0x1e,0x38,0x78,0xd8, + 0xe0,0x79,0x83,0x21,0xc4,0xe3,0x06,0x0f,0x1c,0x8c,0x88,0xd8,0x81,0x1d,0xec,0xa1, + 0x1d,0xdc,0xa0,0x1d,0xde,0x81,0x1c,0xea,0x81,0x1d,0xca,0xc1,0x0d,0xcc,0x81,0x1d, + 0xc2,0xe1,0x1c,0xe6,0x61,0x8a,0x10,0x0c,0x23,0x14,0x76,0x60,0x07,0x7b,0x68,0x07, + 0x37,0x48,0x07,0x72,0x28,0x07,0x77,0xa0,0x87,0x29,0x41,0x31,0x62,0x09,0x87,0x74, + 0x90,0x07,0x37,0xb0,0x87,0x72,0x90,0x87,0x79,0x48,0x87,0x77,0x70,0x87,0x29,0x81, + 0x31,0x82,0x0a,0x87,0x74,0x90,0x07,0x37,0x60,0x87,0x70,0x70,0x87,0x73,0xa8,0x87, + 0x70,0x38,0x87,0x72,0xf8,0x05,0x7b,0x28,0x07,0x79,0x98,0x87,0x74,0x78,0x07,0x77, + 0x98,0x12,0x20,0x23,0xa6,0x70,0x48,0x07,0x79,0x70,0x83,0x71,0x78,0x87,0x76,0x80, + 0x87,0x74,0x60,0x87,0x72,0xf8,0x85,0x77,0x80,0x07,0x7a,0x48,0x87,0x77,0x70,0x87, + 0x79,0x98,0x32,0x28,0x8c,0x33,0x82,0x09,0x87,0x74,0x90,0x07,0x37,0x30,0x07,0x79, + 0x08,0x87,0x73,0x68,0x87,0x72,0x70,0x07,0x7a,0x98,0x12,0xa8,0x01,0x00,0x00,0x00, + 0x00,0x79,0x18,0x00,0x00,0xa5,0x00,0x00,0x00,0x33,0x08,0x80,0x1c,0xc4,0xe1,0x1c, + 0x66,0x14,0x01,0x3d,0x88,0x43,0x38,0x84,0xc3,0x8c,0x42,0x80,0x07,0x79,0x78,0x07, + 0x73,0x98,0x71,0x0c,0xe6,0x00,0x0f,0xed,0x10,0x0e,0xf4,0x80,0x0e,0x33,0x0c,0x42, + 0x1e,0xc2,0xc1,0x1d,0xce,0xa1,0x1c,0x66,0x30,0x05,0x3d,0x88,0x43,0x38,0x84,0x83, + 0x1b,0xcc,0x03,0x3d,0xc8,0x43,0x3d,0x8c,0x03,0x3d,0xcc,0x78,0x8c,0x74,0x70,0x07, + 0x7b,0x08,0x07,0x79,0x48,0x87,0x70,0x70,0x07,0x7a,0x70,0x03,0x76,0x78,0x87,0x70, + 0x20,0x87,0x19,0xcc,0x11,0x0e,0xec,0x90,0x0e,0xe1,0x30,0x0f,0x6e,0x30,0x0f,0xe3, + 0xf0,0x0e,0xf0,0x50,0x0e,0x33,0x10,0xc4,0x1d,0xde,0x21,0x1c,0xd8,0x21,0x1d,0xc2, + 0x61,0x1e,0x66,0x30,0x89,0x3b,0xbc,0x83,0x3b,0xd0,0x43,0x39,0xb4,0x03,0x3c,0xbc, + 0x83,0x3c,0x84,0x03,0x3b,0xcc,0xf0,0x14,0x76,0x60,0x07,0x7b,0x68,0x07,0x37,0x68, + 0x87,0x72,0x68,0x07,0x37,0x80,0x87,0x70,0x90,0x87,0x70,0x60,0x07,0x76,0x28,0x07, + 0x76,0xf8,0x05,0x76,0x78,0x87,0x77,0x80,0x87,0x5f,0x08,0x87,0x71,0x18,0x87,0x72, + 0x98,0x87,0x79,0x98,0x81,0x2c,0xee,0xf0,0x0e,0xee,0xe0,0x0e,0xf5,0xc0,0x0e,0xec, + 0x30,0x03,0x62,0xc8,0xa1,0x1c,0xe4,0xa1,0x1c,0xcc,0xa1,0x1c,0xe4,0xa1,0x1c,0xdc, + 0x61,0x1c,0xca,0x21,0x1c,0xc4,0x81,0x1d,0xca,0x61,0x06,0xd6,0x90,0x43,0x39,0xc8, + 0x43,0x39,0x98,0x43,0x39,0xc8,0x43,0x39,0xb8,0xc3,0x38,0x94,0x43,0x38,0x88,0x03, + 0x3b,0x94,0xc3,0x2f,0xbc,0x83,0x3c,0xfc,0x82,0x3b,0xd4,0x03,0x3b,0xb0,0xc3,0x0c, + 0xc7,0x69,0x87,0x70,0x58,0x87,0x72,0x70,0x83,0x74,0x68,0x07,0x78,0x60,0x87,0x74, + 0x18,0x87,0x74,0xa0,0x87,0x19,0xce,0x53,0x0f,0xee,0x00,0x0f,0xf2,0x50,0x0e,0xe4, + 0x90,0x0e,0xe3,0x40,0x0f,0xe1,0x20,0x0e,0xec,0x50,0x0e,0x33,0x20,0x28,0x1d,0xdc, + 0xc1,0x1e,0xc2,0x41,0x1e,0xd2,0x21,0x1c,0xdc,0x81,0x1e,0xdc,0xe0,0x1c,0xe4,0xe1, + 0x1d,0xea,0x01,0x1e,0x66,0x18,0x51,0x38,0xb0,0x43,0x3a,0x9c,0x83,0x3b,0xcc,0x50, + 0x24,0x76,0x60,0x07,0x7b,0x68,0x07,0x37,0x60,0x87,0x77,0x78,0x07,0x78,0x98,0x51, + 0x4c,0xf4,0x90,0x0f,0xf0,0x50,0x0e,0x33,0x1e,0x6a,0x1e,0xca,0x61,0x1c,0xe8,0x21, + 0x1d,0xde,0xc1,0x1d,0x7e,0x01,0x1e,0xe4,0xa1,0x1c,0xcc,0x21,0x1d,0xf0,0x61,0x06, + 0x54,0x85,0x83,0x38,0xcc,0xc3,0x3b,0xb0,0x43,0x3d,0xd0,0x43,0x39,0xfc,0xc2,0x3c, + 0xe4,0x43,0x3b,0x88,0xc3,0x3b,0xb0,0xc3,0x8c,0xc5,0x0a,0x87,0x79,0x98,0x87,0x77, + 0x18,0x87,0x74,0x08,0x07,0x7a,0x28,0x07,0x72,0x98,0x81,0x5c,0xe3,0x10,0x0e,0xec, + 0xc0,0x0e,0xe5,0x50,0x0e,0xf3,0x30,0x23,0xc1,0xd2,0x41,0x1e,0xe4,0xe1,0x17,0xd8, + 0xe1,0x1d,0xde,0x01,0x1e,0x66,0x48,0x19,0x3b,0xb0,0x83,0x3d,0xb4,0x83,0x1b,0x84, + 0xc3,0x38,0x8c,0x43,0x39,0xcc,0xc3,0x3c,0xb8,0xc1,0x39,0xc8,0xc3,0x3b,0xd4,0x03, + 0x3c,0xcc,0x48,0xb4,0x71,0x08,0x07,0x76,0x60,0x07,0x71,0x08,0x87,0x71,0x58,0x87, + 0x19,0xdb,0xc6,0x0e,0xec,0x60,0x0f,0xed,0xe0,0x06,0xf0,0x20,0x0f,0xe5,0x30,0x0f, + 0xe5,0x20,0x0f,0xf6,0x50,0x0e,0x6e,0x10,0x0e,0xe3,0x30,0x0e,0xe5,0x30,0x0f,0xf3, + 0xe0,0x06,0xe9,0xe0,0x0e,0xe4,0x50,0x0e,0xf8,0x30,0x23,0xe2,0xec,0x61,0x1c,0xc2, + 0x81,0x1d,0xd8,0xe1,0x17,0xec,0x21,0x1d,0xe6,0x21,0x1d,0xc4,0x21,0x1d,0xd8,0x21, + 0x1d,0xe8,0x21,0x1f,0x66,0x20,0x9d,0x3b,0xbc,0x43,0x3d,0xb8,0x03,0x39,0x94,0x83, + 0x39,0xcc,0x58,0xbc,0x70,0x70,0x07,0x77,0x78,0x07,0x7a,0x08,0x07,0x7a,0x48,0x87, + 0x77,0x70,0x87,0x19,0xce,0x87,0x0e,0xe5,0x10,0x0e,0xf0,0x10,0x0e,0xec,0xc0,0x0e, + 0xef,0x30,0x0e,0xf3,0x90,0x0e,0xf4,0x50,0x0e,0x33,0x28,0x30,0x08,0x87,0x74,0x90, + 0x07,0x37,0x30,0x87,0x7a,0x70,0x87,0x71,0xa0,0x87,0x74,0x78,0x07,0x77,0xf8,0x85, + 0x73,0x90,0x87,0x77,0xa8,0x07,0x78,0x98,0x07,0x00,0x00,0x00,0x00,0x71,0x20,0x00, + 0x00,0x08,0x00,0x00,0x00,0x16,0xb0,0x01,0x48,0xe4,0x4b,0x00,0xf3,0x2c,0xc4,0x3f, + 0x11,0xd7,0x44,0x45,0xc4,0x6f,0x0f,0x7e,0x85,0x17,0xb7,0x6d,0x00,0x05,0x03,0x20, + 0x0d,0x0d,0x00,0x00,0x00,0x61,0x20,0x00,0x00,0x14,0x00,0x00,0x00,0x13,0x04,0x41, + 0x2c,0x10,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0xc4,0x46,0x00,0xc6,0x12,0x80,0x80, + 0xd4,0x08,0x40,0x0d,0x90,0x98,0x01,0xa0,0x30,0x03,0x40,0x60,0x04,0x00,0x00,0x00, + 0x00,0x83,0x0c,0x8b,0x60,0x8c,0x18,0x28,0x43,0x40,0x29,0x49,0x50,0x20,0x86,0x60, + 0x01,0x23,0x9f,0xd9,0x06,0x23,0x00,0x32,0x08,0x88,0x01,0x00,0x00,0x02,0x00,0x00, + 0x00,0x5b,0x86,0xe0,0x88,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +}; +/* + #include + #include + + using namespace metal; + + struct vs_params + { + float4x4 mvp; + float4x4 tm; + }; + + struct main0_out + { + float4 uv [[user(locn0)]]; + float4 color [[user(locn1)]]; + float4 gl_Position [[position]]; + float gl_PointSize [[point_size]]; + }; + + struct main0_in + { + float4 position [[attribute(0)]]; + float2 texcoord0 [[attribute(1)]]; + float4 color0 [[attribute(2)]]; + float psize [[attribute(3)]]; + }; + + vertex main0_out main0(main0_in in [[stage_in]], constant vs_params& _19 [[buffer(0)]]) + { + main0_out out = {}; + out.gl_Position = _19.mvp * in.position; + out.gl_PointSize = in.psize; + out.uv = _19.tm * float4(in.texcoord0, 0.0, 1.0); + out.color = in.color0; + return out; + } +*/ +static const uint8_t _sgl_vs_bytecode_metal_ios[3493] = { + 0x4d,0x54,0x4c,0x42,0x01,0x00,0x02,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xa5,0x0d,0x00,0x00,0x00,0x00,0x00,0x00,0x58,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x6d,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc9,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x44,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0d,0x01,0x00,0x00,0x00,0x00,0x00,0x00, + 0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x15,0x01,0x00,0x00,0x00,0x00,0x00,0x00, + 0x90,0x0c,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x6d,0x00,0x00,0x00, + 0x4e,0x41,0x4d,0x45,0x06,0x00,0x6d,0x61,0x69,0x6e,0x30,0x00,0x54,0x59,0x50,0x45, + 0x01,0x00,0x00,0x48,0x41,0x53,0x48,0x20,0x00,0x81,0x80,0x75,0xbc,0xa9,0xd0,0xb9, + 0x4f,0xee,0x63,0x10,0x8f,0xfe,0x66,0xa5,0x3b,0x40,0xb7,0x43,0x44,0x12,0xba,0x69, + 0x7d,0xf3,0x83,0x9a,0xda,0x47,0x00,0x29,0xec,0x4f,0x46,0x46,0x54,0x18,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x56,0x45,0x52,0x53,0x08,0x00,0x01,0x00,0x08, + 0x00,0x01,0x00,0x01,0x00,0x45,0x4e,0x44,0x54,0x40,0x00,0x00,0x00,0x56,0x41,0x54, + 0x54,0x2a,0x00,0x04,0x00,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x00,0x00,0x80, + 0x74,0x65,0x78,0x63,0x6f,0x6f,0x72,0x64,0x30,0x00,0x01,0x80,0x63,0x6f,0x6c,0x6f, + 0x72,0x30,0x00,0x02,0x80,0x70,0x73,0x69,0x7a,0x65,0x00,0x03,0x80,0x56,0x41,0x54, + 0x59,0x06,0x00,0x04,0x00,0x06,0x04,0x06,0x03,0x45,0x4e,0x44,0x54,0x04,0x00,0x00, + 0x00,0x45,0x4e,0x44,0x54,0xde,0xc0,0x17,0x0b,0x00,0x00,0x00,0x00,0x14,0x00,0x00, + 0x00,0x70,0x0c,0x00,0x00,0xff,0xff,0xff,0xff,0x42,0x43,0xc0,0xde,0x21,0x0c,0x00, + 0x00,0x19,0x03,0x00,0x00,0x0b,0x82,0x20,0x00,0x02,0x00,0x00,0x00,0x12,0x00,0x00, + 0x00,0x07,0x81,0x23,0x91,0x41,0xc8,0x04,0x49,0x06,0x10,0x32,0x39,0x92,0x01,0x84, + 0x0c,0x25,0x05,0x08,0x19,0x1e,0x04,0x8b,0x62,0x80,0x14,0x45,0x02,0x42,0x92,0x0b, + 0x42,0xa4,0x10,0x32,0x14,0x38,0x08,0x18,0x49,0x0a,0x32,0x44,0x24,0x48,0x0a,0x90, + 0x21,0x23,0xc4,0x52,0x80,0x0c,0x19,0x21,0x72,0x24,0x07,0xc8,0x48,0x11,0x62,0xa8, + 0xa0,0xa8,0x40,0xc6,0xf0,0x01,0x00,0x00,0x00,0x51,0x18,0x00,0x00,0x70,0x00,0x00, + 0x00,0x1b,0x7e,0x24,0xf8,0xff,0xff,0xff,0xff,0x01,0x90,0x00,0x8a,0x08,0x07,0x78, + 0x80,0x07,0x79,0x78,0x07,0x7c,0x68,0x03,0x73,0xa8,0x07,0x77,0x18,0x87,0x36,0x30, + 0x07,0x78,0x68,0x83,0x76,0x08,0x07,0x7a,0x40,0x07,0x80,0x1e,0xe4,0xa1,0x1e,0xca, + 0x01,0x20,0xda,0x21,0x1d,0xdc,0xa1,0x0d,0xd8,0xa1,0x1c,0xce,0x21,0x1c,0xd8,0xa1, + 0x0d,0xec,0xa1,0x1c,0xc6,0x81,0x1e,0xde,0x41,0x1e,0xda,0xe0,0x1e,0xd2,0x81,0x1c, + 0xe8,0x01,0x1d,0x80,0x38,0x90,0x03,0x3c,0x00,0x06,0x77,0x78,0x87,0x36,0x10,0x87, + 0x7a,0x48,0x07,0x76,0xa0,0x87,0x74,0x70,0x87,0x79,0x00,0x08,0x77,0x78,0x87,0x36, + 0x30,0x07,0x79,0x08,0x87,0x76,0x28,0x87,0x36,0x80,0x87,0x77,0x48,0x07,0x77,0xa0, + 0x87,0x72,0x90,0x87,0x36,0x28,0x07,0x76,0x48,0x87,0x76,0x00,0xe8,0x41,0x1e,0xea, + 0xa1,0x1c,0x00,0xc2,0x1d,0xde,0xa1,0x0d,0xcc,0x41,0x1e,0xc2,0xa1,0x1d,0xca,0xa1, + 0x0d,0xe0,0xe1,0x1d,0xd2,0xc1,0x1d,0xe8,0xa1,0x1c,0xe4,0xa1,0x0d,0xca,0x81,0x1d, + 0xd2,0xa1,0x1d,0xda,0xc0,0x1d,0xde,0xc1,0x1d,0xda,0x80,0x1d,0xca,0x21,0x1c,0xcc, + 0x01,0xa0,0x07,0x79,0xa8,0x87,0x72,0x00,0x08,0x77,0x78,0x87,0x36,0x48,0x07,0x77, + 0x30,0x87,0x79,0x68,0x03,0x73,0x80,0x87,0x36,0x68,0x87,0x70,0xa0,0x07,0x74,0x00, + 0xe8,0x41,0x1e,0xea,0xa1,0x1c,0x00,0xc2,0x1d,0xde,0xa1,0x0d,0xdc,0x21,0x1c,0xdc, + 0x61,0x1e,0xda,0xc0,0x1c,0xe0,0xa1,0x0d,0xda,0x21,0x1c,0xe8,0x01,0x1d,0x00,0x7a, + 0x90,0x87,0x7a,0x28,0x07,0x80,0x70,0x87,0x77,0x68,0x83,0x79,0x48,0x87,0x73,0x70, + 0x87,0x72,0x20,0x87,0x36,0xd0,0x87,0x72,0x90,0x87,0x77,0x98,0x87,0x36,0x30,0x07, + 0x78,0x68,0x83,0x76,0x08,0x07,0x7a,0x40,0x07,0x80,0x1e,0xe4,0xa1,0x1e,0xca,0x01, + 0x20,0xdc,0xe1,0x1d,0xda,0x80,0x1e,0xe4,0x21,0x1c,0xe0,0x01,0x1e,0xd2,0xc1,0x1d, + 0xce,0xa1,0x0d,0xda,0x21,0x1c,0xe8,0x01,0x1d,0x00,0x7a,0x90,0x87,0x7a,0x28,0x07, + 0x80,0x98,0x07,0x7a,0x08,0x87,0x71,0x58,0x87,0x36,0x80,0x07,0x79,0x78,0x07,0x7a, + 0x28,0x87,0x71,0xa0,0x87,0x77,0x90,0x87,0x36,0x10,0x87,0x7a,0x30,0x07,0x73,0x28, + 0x07,0x79,0x68,0x83,0x79,0x48,0x07,0x7d,0x28,0x07,0x00,0x0f,0x00,0xa2,0x1e,0xdc, + 0x61,0x1e,0xc2,0xc1,0x1c,0xca,0xa1,0x0d,0xcc,0x01,0x1e,0xda,0xa0,0x1d,0xc2,0x81, + 0x1e,0xd0,0x01,0xa0,0x07,0x79,0xa8,0x87,0x72,0x00,0x36,0x6c,0x42,0x01,0x2c,0x40, + 0x35,0x84,0x43,0x3a,0xc8,0x43,0x1b,0x88,0x43,0x3d,0x98,0x83,0x39,0x94,0x83,0x3c, + 0xb4,0x81,0x3b,0xbc,0x43,0x1b,0x84,0x03,0x3b,0xa4,0x43,0x38,0xcc,0x03,0x00,0x00, + 0x00,0x49,0x18,0x00,0x00,0x01,0x00,0x00,0x00,0x13,0x84,0x40,0x00,0x89,0x20,0x00, + 0x00,0x20,0x00,0x00,0x00,0x32,0x22,0x48,0x09,0x20,0x64,0x85,0x04,0x93,0x22,0xa4, + 0x84,0x04,0x93,0x22,0xe3,0x84,0xa1,0x90,0x14,0x12,0x4c,0x8a,0x8c,0x0b,0x84,0xa4, + 0x4c,0x10,0x44,0x33,0x00,0xc3,0x08,0x04,0x60,0x89,0x10,0x02,0x18,0x46,0x10,0x80, + 0x24,0x08,0x33,0x51,0xf3,0x40,0x0f,0xf2,0x50,0x0f,0xe3,0x40,0x0f,0x6e,0xd0,0x0e, + 0xe5,0x40,0x0f,0xe1,0xc0,0x0e,0x7a,0xa0,0x07,0xed,0x10,0x0e,0xf4,0x20,0x0f,0xe9, + 0x80,0x0f,0x28,0x20,0x07,0x49,0x53,0x44,0x09,0x93,0x5f,0x49,0xff,0x03,0x44,0x00, + 0x23,0x21,0xa1,0x94,0x41,0x04,0x43,0x28,0x86,0x08,0x23,0x80,0x43,0x68,0x20,0x60, + 0x8e,0x00,0x0c,0x52,0x60,0xcd,0x11,0x80,0xc2,0x20,0x42,0x20,0x0c,0x23,0x10,0xcb, + 0x08,0x00,0x00,0x00,0x00,0x13,0xa8,0x70,0x48,0x07,0x79,0xb0,0x03,0x3a,0x68,0x83, + 0x70,0x80,0x07,0x78,0x60,0x87,0x72,0x68,0x83,0x74,0x78,0x87,0x79,0xc8,0x03,0x37, + 0x80,0x03,0x37,0x80,0x83,0x0d,0xb7,0x51,0x0e,0x6d,0x00,0x0f,0x7a,0x60,0x07,0x74, + 0xa0,0x07,0x76,0x40,0x07,0x7a,0x60,0x07,0x74,0xd0,0x06,0xe9,0x10,0x07,0x7a,0x80, + 0x07,0x7a,0x80,0x07,0x6d,0x90,0x0e,0x78,0xa0,0x07,0x78,0xa0,0x07,0x78,0xd0,0x06, + 0xe9,0x10,0x07,0x76,0xa0,0x07,0x71,0x60,0x07,0x7a,0x10,0x07,0x76,0xd0,0x06,0xe9, + 0x30,0x07,0x72,0xa0,0x07,0x73,0x20,0x07,0x7a,0x30,0x07,0x72,0xd0,0x06,0xe9,0x60, + 0x07,0x74,0xa0,0x07,0x76,0x40,0x07,0x7a,0x60,0x07,0x74,0xd0,0x06,0xe6,0x30,0x07, + 0x72,0xa0,0x07,0x73,0x20,0x07,0x7a,0x30,0x07,0x72,0xd0,0x06,0xe6,0x60,0x07,0x74, + 0xa0,0x07,0x76,0x40,0x07,0x7a,0x60,0x07,0x74,0xd0,0x06,0xf6,0x10,0x07,0x76,0xa0, + 0x07,0x71,0x60,0x07,0x7a,0x10,0x07,0x76,0xd0,0x06,0xf6,0x20,0x07,0x74,0xa0,0x07, + 0x73,0x20,0x07,0x7a,0x30,0x07,0x72,0xd0,0x06,0xf6,0x30,0x07,0x72,0xa0,0x07,0x73, + 0x20,0x07,0x7a,0x30,0x07,0x72,0xd0,0x06,0xf6,0x40,0x07,0x78,0xa0,0x07,0x76,0x40, + 0x07,0x7a,0x60,0x07,0x74,0xd0,0x06,0xf6,0x60,0x07,0x74,0xa0,0x07,0x76,0x40,0x07, + 0x7a,0x60,0x07,0x74,0xd0,0x06,0xf6,0x90,0x07,0x76,0xa0,0x07,0x71,0x20,0x07,0x78, + 0xa0,0x07,0x71,0x20,0x07,0x78,0xd0,0x06,0xf6,0x10,0x07,0x72,0x80,0x07,0x7a,0x10, + 0x07,0x72,0x80,0x07,0x7a,0x10,0x07,0x72,0x80,0x07,0x6d,0x60,0x0f,0x71,0x90,0x07, + 0x72,0xa0,0x07,0x72,0x50,0x07,0x76,0xa0,0x07,0x72,0x50,0x07,0x76,0xd0,0x06,0xf6, + 0x20,0x07,0x75,0x60,0x07,0x7a,0x20,0x07,0x75,0x60,0x07,0x7a,0x20,0x07,0x75,0x60, + 0x07,0x6d,0x60,0x0f,0x75,0x10,0x07,0x72,0xa0,0x07,0x75,0x10,0x07,0x72,0xa0,0x07, + 0x75,0x10,0x07,0x72,0xd0,0x06,0xf6,0x10,0x07,0x70,0x20,0x07,0x74,0xa0,0x07,0x71, + 0x00,0x07,0x72,0x40,0x07,0x7a,0x10,0x07,0x70,0x20,0x07,0x74,0xd0,0x06,0xee,0x80, + 0x07,0x7a,0x10,0x07,0x76,0xa0,0x07,0x73,0x20,0x07,0x43,0x98,0x04,0x00,0x80,0x00, + 0x00,0x00,0x00,0x00,0x80,0x2c,0x10,0x00,0x00,0x0b,0x00,0x00,0x00,0x32,0x1e,0x98, + 0x10,0x19,0x11,0x4c,0x90,0x8c,0x09,0x26,0x47,0xc6,0x04,0x43,0x5a,0x25,0x30,0x02, + 0x50,0x04,0x05,0x18,0x50,0x08,0x65,0x50,0x80,0x02,0x05,0x51,0x20,0xd4,0x46,0x00, + 0x88,0x8d,0x25,0x48,0x00,0x00,0x00,0x00,0x00,0x79,0x18,0x00,0x00,0x12,0x01,0x00, + 0x00,0x1a,0x03,0x4c,0x10,0x97,0x29,0xa2,0x25,0x10,0xab,0x32,0xb9,0xb9,0xb4,0x37, + 0xb7,0x21,0xc6,0x32,0x28,0x00,0xb3,0x50,0xb9,0x1b,0x43,0x0b,0x93,0xfb,0x9a,0x4b, + 0xd3,0x2b,0x1b,0x62,0x2c,0x81,0x22,0x2c,0x05,0xe3,0x20,0x08,0x0e,0x8e,0xad,0x0c, + 0xa4,0xad,0x8c,0x2e,0x8c,0x0d,0xc4,0xae,0x4c,0x6e,0x2e,0xed,0xcd,0x0d,0x64,0x46, + 0x06,0x46,0x66,0xc6,0x65,0x66,0xa6,0x06,0x04,0xa5,0xad,0x8c,0x2e,0x8c,0xcd,0xac, + 0xac,0x65,0x46,0x06,0x46,0x66,0xc6,0x65,0x66,0xa6,0x26,0x65,0x88,0xa0,0x10,0x43, + 0x8c,0x25,0x58,0x90,0x45,0x60,0xd1,0x54,0x46,0x17,0xc6,0x36,0x04,0x51,0x8e,0x25, + 0x58,0x82,0x45,0xe0,0x16,0x96,0x26,0xe7,0x32,0xf6,0xd6,0x06,0x97,0xc6,0x56,0xe6, + 0x42,0x56,0xe6,0xf6,0x26,0xd7,0x36,0xf7,0x45,0x96,0x36,0x17,0x26,0xc6,0x56,0x36, + 0x44,0x50,0x12,0x72,0x61,0x69,0x72,0x2e,0x63,0x6f,0x6d,0x70,0x69,0x6c,0x65,0x2e, + 0x66,0x61,0x73,0x74,0x5f,0x6d,0x61,0x74,0x68,0x5f,0x65,0x6e,0x61,0x62,0x6c,0x65, + 0x43,0x04,0x65,0x21,0x19,0x84,0xa5,0xc9,0xb9,0x8c,0xbd,0xb5,0xc1,0xa5,0xb1,0x95, + 0xb9,0x98,0xc9,0x85,0xb5,0x95,0x89,0xd5,0x99,0x99,0x95,0xc9,0x7d,0x99,0x95,0xd1, + 0x8d,0xa1,0x7d,0x95,0xb9,0x85,0x89,0xb1,0x95,0x0d,0x11,0x94,0x86,0x51,0x58,0x9a, + 0x9c,0x8b,0x5d,0x99,0x1c,0x5d,0x19,0xde,0xd7,0x5b,0x1d,0x1d,0x5c,0x1d,0x1d,0x97, + 0xba,0xb9,0x32,0x39,0x14,0xb6,0xb7,0x31,0x37,0x98,0x14,0x46,0x61,0x69,0x72,0x2e, + 0x61,0x72,0x67,0x5f,0x74,0x79,0x70,0x65,0x5f,0x6e,0x61,0x6d,0x65,0x34,0xcc,0xd8, + 0xde,0xc2,0xe8,0x68,0xc8,0x84,0xa5,0xc9,0xb9,0x84,0xc9,0x9d,0x7d,0xb9,0x85,0xb5, + 0x95,0x51,0xa8,0xb3,0x1b,0xc2,0x28,0x8f,0x02,0x29,0x91,0x22,0x29,0x93,0x42,0x71, + 0xa9,0x9b,0x2b,0x93,0x43,0x61,0x7b,0x1b,0x73,0x8b,0x49,0x61,0x31,0xf6,0xc6,0xf6, + 0x26,0x37,0x84,0x51,0x1e,0xc5,0x52,0x22,0x45,0x52,0x26,0xe5,0x22,0x13,0x96,0x26, + 0xe7,0x02,0xf7,0x36,0x97,0x46,0x97,0xf6,0xe6,0xc6,0xe5,0x8c,0xed,0x0b,0xea,0x6d, + 0x2e,0x8d,0x2e,0xed,0xcd,0x6d,0x88,0xa2,0x64,0x4a,0xa4,0x48,0xca,0xa4,0x68,0x74, + 0xc2,0xd2,0xe4,0x5c,0xe0,0xde,0xd2,0xdc,0xe8,0xbe,0xe6,0xd2,0xf4,0xca,0x58,0x98, + 0xb1,0xbd,0x85,0xd1,0x91,0x39,0x63,0xfb,0x82,0x7a,0x4b,0x73,0xa3,0x9b,0x4a,0xd3, + 0x2b,0x1b,0xa2,0x28,0x9c,0x12,0x29,0x9d,0x32,0x29,0xde,0x10,0x44,0xa9,0x14,0x4c, + 0xd9,0x94,0x8f,0x50,0x58,0x9a,0x9c,0x8b,0x5d,0x99,0x1c,0x5d,0x19,0xde,0x57,0x9a, + 0x1b,0x5c,0x1d,0x1d,0xa5,0xb0,0x34,0x39,0x17,0xb6,0xb7,0xb1,0x30,0xba,0xb4,0x37, + 0xb7,0xaf,0x34,0x37,0xb2,0x32,0x3c,0x7a,0x67,0x65,0x6e,0x65,0x72,0x61,0x74,0x65, + 0x64,0x28,0x5f,0x5f,0x61,0x69,0x72,0x5f,0x70,0x6c,0x61,0x63,0x65,0x68,0x6f,0x6c, + 0x64,0x65,0x72,0x5f,0x5f,0x29,0x44,0xe0,0xde,0xe6,0xd2,0xe8,0xd2,0xde,0xdc,0x86, + 0x50,0x8b,0xa0,0x84,0x81,0x22,0x06,0x8b,0xb0,0x04,0xca,0x18,0x28,0x91,0x22,0x29, + 0x93,0x42,0x06,0x34,0xcc,0xd8,0xde,0xc2,0xe8,0x64,0x98,0xd0,0x95,0xe1,0x8d,0xbd, + 0xbd,0xc9,0x91,0xc1,0x0c,0xa1,0x96,0x40,0x09,0x03,0x45,0x0c,0x96,0x60,0x09,0x94, + 0x31,0x50,0x22,0xc5,0x0c,0x94,0x49,0x39,0x03,0x1a,0x63,0x6f,0x6c,0x6f,0x72,0x30, + 0x43,0xa8,0x65,0x50,0xc2,0x40,0x11,0x83,0x65,0x58,0x02,0x65,0x0c,0x94,0x48,0x91, + 0x94,0x49,0x49,0x03,0x16,0x70,0x73,0x69,0x7a,0x65,0x43,0xa8,0xc5,0x50,0xc2,0x40, + 0x11,0x83,0xc5,0x58,0x02,0x65,0x0c,0x94,0x48,0xe9,0x94,0x49,0x59,0x03,0x2a,0x61, + 0x69,0x72,0x2e,0x62,0x75,0x66,0x66,0x65,0x72,0x7c,0xc2,0xd2,0xe4,0x5c,0xc4,0xea, + 0xcc,0xcc,0xca,0xe4,0xbe,0xe6,0xd2,0xf4,0xca,0x88,0x84,0xa5,0xc9,0xb9,0xc8,0x95, + 0x85,0x91,0x91,0x0a,0x4b,0x93,0x73,0x99,0xa3,0x93,0xab,0x1b,0xa3,0xfb,0xa2,0xcb, + 0x83,0x2b,0xfb,0x4a,0x73,0x33,0x7b,0x23,0x62,0xc6,0xf6,0x16,0x46,0x47,0x83,0x47, + 0xc3,0xa1,0xcd,0x0e,0x8e,0x02,0x5d,0xdb,0x10,0x6a,0x11,0x16,0x62,0x11,0x94,0x38, + 0x50,0xe4,0x60,0x21,0x16,0x62,0x11,0x94,0x38,0x50,0xe6,0x80,0x51,0x58,0x9a,0x9c, + 0x4b,0x98,0xdc,0xd9,0x17,0x5d,0x1e,0x5c,0xd9,0xd7,0x5c,0x9a,0x5e,0x19,0xaf,0xb0, + 0x34,0x39,0x97,0x30,0xb9,0xb3,0x2f,0xba,0x3c,0xb8,0xb2,0xaf,0x30,0xb6,0xb4,0x33, + 0xb7,0xaf,0xb9,0x34,0xbd,0x32,0x26,0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73, + 0x1c,0xbe,0x62,0x72,0x86,0x90,0xc1,0x52,0x28,0x6d,0xa0,0xb8,0xc1,0x72,0x28,0x62, + 0xb0,0x08,0x4b,0xa0,0xbc,0x81,0x02,0x07,0x0a,0x1d,0x28,0x75,0xb0,0x1c,0x8a,0x1d, + 0x2c,0x89,0x12,0x29,0x77,0xa0,0x4c,0x0a,0x1e,0x0c,0x51,0x94,0x32,0x50,0xd0,0x40, + 0x51,0x03,0x85,0x0d,0x94,0x3c,0x18,0x62,0x24,0x80,0x02,0x06,0x8a,0x1e,0xf0,0x79, + 0x6b,0x73,0x4b,0x83,0x7b,0xa3,0x2b,0x73,0xa3,0x03,0x19,0x43,0x0b,0x93,0xe3,0x33, + 0x95,0xd6,0x06,0xc7,0x56,0x06,0x32,0xb4,0xb2,0x02,0x42,0x25,0x14,0x14,0x34,0x44, + 0x50,0xfa,0x60,0x88,0xa1,0xf0,0x81,0xe2,0x07,0x8d,0x32,0xc4,0x50,0xfe,0x40,0xf9, + 0x83,0x46,0xe1,0x15,0x96,0x26,0xd7,0x12,0xc6,0x96,0x16,0x36,0xd7,0x32,0x37,0xf6, + 0x06,0x57,0x36,0x87,0xd2,0x16,0x96,0xe6,0x06,0x93,0x32,0x84,0x50,0x44,0x41,0x09, + 0x05,0x5a,0x61,0x69,0x72,0x2d,0x61,0x6c,0x69,0x61,0x73,0x2d,0x73,0x63,0x6f,0x70, + 0x65,0x2d,0x61,0x72,0x67,0x28,0x34,0x29,0x43,0x0c,0x85,0x14,0x14,0x51,0x50,0x46, + 0x61,0x88,0xa0,0x90,0xc2,0x88,0x88,0x1d,0xd8,0xc1,0x1e,0xda,0xc1,0x0d,0xda,0xe1, + 0x1d,0xc8,0xa1,0x1e,0xd8,0xa1,0x1c,0xdc,0xc0,0x1c,0xd8,0x21,0x1c,0xce,0x61,0x1e, + 0xa6,0x08,0xc1,0x30,0x42,0x61,0x07,0x76,0xb0,0x87,0x76,0x70,0x83,0x74,0x20,0x87, + 0x72,0x70,0x07,0x7a,0x98,0x12,0x14,0x23,0x96,0x70,0x48,0x07,0x79,0x70,0x03,0x7b, + 0x28,0x07,0x79,0x98,0x87,0x74,0x78,0x07,0x77,0x98,0x12,0x18,0x23,0xa8,0x70,0x48, + 0x07,0x79,0x70,0x03,0x76,0x08,0x07,0x77,0x38,0x87,0x7a,0x08,0x87,0x73,0x28,0x87, + 0x5f,0xb0,0x87,0x72,0x90,0x87,0x79,0x48,0x87,0x77,0x70,0x87,0x29,0x01,0x32,0x62, + 0x0a,0x87,0x74,0x90,0x07,0x37,0x18,0x87,0x77,0x68,0x07,0x78,0x48,0x07,0x76,0x28, + 0x87,0x5f,0x78,0x07,0x78,0xa0,0x87,0x74,0x78,0x07,0x77,0x98,0x87,0x29,0x83,0xc2, + 0x38,0x23,0x94,0x70,0x48,0x07,0x79,0x70,0x03,0x7b,0x28,0x07,0x79,0xa0,0x87,0x72, + 0xc0,0x87,0x29,0xc1,0x1e,0x00,0x00,0x00,0x00,0x79,0x18,0x00,0x00,0xa5,0x00,0x00, + 0x00,0x33,0x08,0x80,0x1c,0xc4,0xe1,0x1c,0x66,0x14,0x01,0x3d,0x88,0x43,0x38,0x84, + 0xc3,0x8c,0x42,0x80,0x07,0x79,0x78,0x07,0x73,0x98,0x71,0x0c,0xe6,0x00,0x0f,0xed, + 0x10,0x0e,0xf4,0x80,0x0e,0x33,0x0c,0x42,0x1e,0xc2,0xc1,0x1d,0xce,0xa1,0x1c,0x66, + 0x30,0x05,0x3d,0x88,0x43,0x38,0x84,0x83,0x1b,0xcc,0x03,0x3d,0xc8,0x43,0x3d,0x8c, + 0x03,0x3d,0xcc,0x78,0x8c,0x74,0x70,0x07,0x7b,0x08,0x07,0x79,0x48,0x87,0x70,0x70, + 0x07,0x7a,0x70,0x03,0x76,0x78,0x87,0x70,0x20,0x87,0x19,0xcc,0x11,0x0e,0xec,0x90, + 0x0e,0xe1,0x30,0x0f,0x6e,0x30,0x0f,0xe3,0xf0,0x0e,0xf0,0x50,0x0e,0x33,0x10,0xc4, + 0x1d,0xde,0x21,0x1c,0xd8,0x21,0x1d,0xc2,0x61,0x1e,0x66,0x30,0x89,0x3b,0xbc,0x83, + 0x3b,0xd0,0x43,0x39,0xb4,0x03,0x3c,0xbc,0x83,0x3c,0x84,0x03,0x3b,0xcc,0xf0,0x14, + 0x76,0x60,0x07,0x7b,0x68,0x07,0x37,0x68,0x87,0x72,0x68,0x07,0x37,0x80,0x87,0x70, + 0x90,0x87,0x70,0x60,0x07,0x76,0x28,0x07,0x76,0xf8,0x05,0x76,0x78,0x87,0x77,0x80, + 0x87,0x5f,0x08,0x87,0x71,0x18,0x87,0x72,0x98,0x87,0x79,0x98,0x81,0x2c,0xee,0xf0, + 0x0e,0xee,0xe0,0x0e,0xf5,0xc0,0x0e,0xec,0x30,0x03,0x62,0xc8,0xa1,0x1c,0xe4,0xa1, + 0x1c,0xcc,0xa1,0x1c,0xe4,0xa1,0x1c,0xdc,0x61,0x1c,0xca,0x21,0x1c,0xc4,0x81,0x1d, + 0xca,0x61,0x06,0xd6,0x90,0x43,0x39,0xc8,0x43,0x39,0x98,0x43,0x39,0xc8,0x43,0x39, + 0xb8,0xc3,0x38,0x94,0x43,0x38,0x88,0x03,0x3b,0x94,0xc3,0x2f,0xbc,0x83,0x3c,0xfc, + 0x82,0x3b,0xd4,0x03,0x3b,0xb0,0xc3,0x0c,0xc7,0x69,0x87,0x70,0x58,0x87,0x72,0x70, + 0x83,0x74,0x68,0x07,0x78,0x60,0x87,0x74,0x18,0x87,0x74,0xa0,0x87,0x19,0xce,0x53, + 0x0f,0xee,0x00,0x0f,0xf2,0x50,0x0e,0xe4,0x90,0x0e,0xe3,0x40,0x0f,0xe1,0x20,0x0e, + 0xec,0x50,0x0e,0x33,0x20,0x28,0x1d,0xdc,0xc1,0x1e,0xc2,0x41,0x1e,0xd2,0x21,0x1c, + 0xdc,0x81,0x1e,0xdc,0xe0,0x1c,0xe4,0xe1,0x1d,0xea,0x01,0x1e,0x66,0x18,0x51,0x38, + 0xb0,0x43,0x3a,0x9c,0x83,0x3b,0xcc,0x50,0x24,0x76,0x60,0x07,0x7b,0x68,0x07,0x37, + 0x60,0x87,0x77,0x78,0x07,0x78,0x98,0x51,0x4c,0xf4,0x90,0x0f,0xf0,0x50,0x0e,0x33, + 0x1e,0x6a,0x1e,0xca,0x61,0x1c,0xe8,0x21,0x1d,0xde,0xc1,0x1d,0x7e,0x01,0x1e,0xe4, + 0xa1,0x1c,0xcc,0x21,0x1d,0xf0,0x61,0x06,0x54,0x85,0x83,0x38,0xcc,0xc3,0x3b,0xb0, + 0x43,0x3d,0xd0,0x43,0x39,0xfc,0xc2,0x3c,0xe4,0x43,0x3b,0x88,0xc3,0x3b,0xb0,0xc3, + 0x8c,0xc5,0x0a,0x87,0x79,0x98,0x87,0x77,0x18,0x87,0x74,0x08,0x07,0x7a,0x28,0x07, + 0x72,0x98,0x81,0x5c,0xe3,0x10,0x0e,0xec,0xc0,0x0e,0xe5,0x50,0x0e,0xf3,0x30,0x23, + 0xc1,0xd2,0x41,0x1e,0xe4,0xe1,0x17,0xd8,0xe1,0x1d,0xde,0x01,0x1e,0x66,0x48,0x19, + 0x3b,0xb0,0x83,0x3d,0xb4,0x83,0x1b,0x84,0xc3,0x38,0x8c,0x43,0x39,0xcc,0xc3,0x3c, + 0xb8,0xc1,0x39,0xc8,0xc3,0x3b,0xd4,0x03,0x3c,0xcc,0x48,0xb4,0x71,0x08,0x07,0x76, + 0x60,0x07,0x71,0x08,0x87,0x71,0x58,0x87,0x19,0xdb,0xc6,0x0e,0xec,0x60,0x0f,0xed, + 0xe0,0x06,0xf0,0x20,0x0f,0xe5,0x30,0x0f,0xe5,0x20,0x0f,0xf6,0x50,0x0e,0x6e,0x10, + 0x0e,0xe3,0x30,0x0e,0xe5,0x30,0x0f,0xf3,0xe0,0x06,0xe9,0xe0,0x0e,0xe4,0x50,0x0e, + 0xf8,0x30,0x23,0xe2,0xec,0x61,0x1c,0xc2,0x81,0x1d,0xd8,0xe1,0x17,0xec,0x21,0x1d, + 0xe6,0x21,0x1d,0xc4,0x21,0x1d,0xd8,0x21,0x1d,0xe8,0x21,0x1f,0x66,0x20,0x9d,0x3b, + 0xbc,0x43,0x3d,0xb8,0x03,0x39,0x94,0x83,0x39,0xcc,0x58,0xbc,0x70,0x70,0x07,0x77, + 0x78,0x07,0x7a,0x08,0x07,0x7a,0x48,0x87,0x77,0x70,0x87,0x19,0xce,0x87,0x0e,0xe5, + 0x10,0x0e,0xf0,0x10,0x0e,0xec,0xc0,0x0e,0xef,0x30,0x0e,0xf3,0x90,0x0e,0xf4,0x50, + 0x0e,0x33,0x28,0x30,0x08,0x87,0x74,0x90,0x07,0x37,0x30,0x87,0x7a,0x70,0x87,0x71, + 0xa0,0x87,0x74,0x78,0x07,0x77,0xf8,0x85,0x73,0x90,0x87,0x77,0xa8,0x07,0x78,0x98, + 0x07,0x00,0x00,0x00,0x00,0x71,0x20,0x00,0x00,0x02,0x00,0x00,0x00,0x06,0x50,0x30, + 0x00,0xd2,0xd0,0x00,0x00,0x61,0x20,0x00,0x00,0x42,0x00,0x00,0x00,0x13,0x04,0x41, + 0x2c,0x10,0x00,0x00,0x00,0x09,0x00,0x00,0x00,0xf4,0xc6,0x22,0x86,0x61,0x18,0xc6, + 0x22,0x04,0x41,0x10,0xc6,0x22,0x82,0x20,0x08,0x46,0x00,0xa8,0x95,0x40,0x19,0x14, + 0x01,0x8d,0x19,0x00,0x12,0x33,0x00,0x14,0x66,0x00,0x66,0x00,0x00,0xe3,0x15,0x8c, + 0xa4,0x69,0x12,0x05,0x65,0x90,0x41,0x22,0x14,0x13,0x02,0xf9,0x8c,0x57,0x40,0x96, + 0xe7,0x2d,0x14,0x94,0x41,0x06,0xeb,0x78,0x4c,0x08,0xe4,0x63,0x41,0x01,0x9f,0xf1, + 0x8a,0x6a,0x1b,0x83,0x31,0x70,0x28,0x28,0x83,0x0c,0x1b,0x53,0x99,0x10,0xc8,0xc7, + 0x8a,0x00,0x3e,0xe3,0x15,0x1a,0x18,0xa0,0x01,0x1a,0x50,0x14,0x94,0x41,0x06,0x30, + 0x88,0x36,0x13,0x02,0xf9,0x58,0x11,0xc0,0x67,0xbc,0xe2,0x2b,0x03,0x37,0x68,0x83, + 0x32,0xa0,0xa0,0x0c,0x32,0x90,0x41,0xd6,0x99,0x10,0xc8,0x67,0xbc,0x62,0x0c,0xd2, + 0x40,0x0e,0xe2,0xc0,0xa3,0xa0,0x0c,0x32,0xa0,0x41,0x27,0x06,0x26,0x04,0xf2,0xb1, + 0xa0,0x80,0xcf,0x78,0x05,0x1a,0xb8,0xc1,0x1d,0xd8,0x81,0x18,0x50,0x50,0x6c,0x08, + 0xe0,0x33,0xdb,0x20,0x06,0x01,0x30,0xdb,0x10,0xb8,0x41,0x30,0xdb,0x10,0x3c,0xc2, + 0x6c,0x43,0xf0,0x06,0x43,0x06,0x01,0x31,0x00,0x0d,0x00,0x00,0x00,0x5b,0x8a,0x20, + 0x00,0x85,0xa3,0x14,0xb6,0x14,0x45,0x00,0x0a,0x47,0x29,0x6c,0x29,0x94,0x00,0x14, + 0x8e,0x52,0xd8,0x52,0x3c,0x01,0x28,0x1c,0xa5,0xb0,0xa5,0xa0,0x02,0x50,0x38,0x4a, + 0x61,0x4b,0x81,0x05,0xa0,0x70,0x94,0xc2,0x96,0xa2,0x0b,0x40,0xe1,0x28,0x05,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00, +}; +/* + #include + #include + + using namespace metal; + + struct main0_out + { + float4 frag_color [[color(0)]]; + }; + + struct main0_in + { + float4 uv [[user(locn0)]]; + float4 color [[user(locn1)]]; + }; + + fragment main0_out main0(main0_in in [[stage_in]], texture2d tex [[texture(0)]], sampler smp [[sampler(0)]]) + { + main0_out out = {}; + out.frag_color = tex.sample(smp, in.uv.xy) * in.color; + return out; + } +*/ +static const uint8_t _sgl_fs_bytecode_metal_ios[3017] = { + 0x4d,0x54,0x4c,0x42,0x01,0x00,0x02,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xc9,0x0b,0x00,0x00,0x00,0x00,0x00,0x00,0x58,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x6d,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc9,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xd1,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xd9,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xf0,0x0a,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x6d,0x00,0x00,0x00, + 0x4e,0x41,0x4d,0x45,0x06,0x00,0x6d,0x61,0x69,0x6e,0x30,0x00,0x54,0x59,0x50,0x45, + 0x01,0x00,0x01,0x48,0x41,0x53,0x48,0x20,0x00,0x40,0xd6,0xeb,0xe8,0x54,0x51,0x57, + 0x09,0x04,0x8e,0xcb,0x4c,0x44,0x2c,0x92,0x69,0x71,0x54,0xbd,0xb6,0xe0,0x7e,0x6b, + 0x97,0x01,0x63,0xb9,0x24,0x88,0x76,0x15,0x8c,0x4f,0x46,0x46,0x54,0x18,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x56,0x45,0x52,0x53,0x08,0x00,0x01,0x00,0x08, + 0x00,0x01,0x00,0x01,0x00,0x45,0x4e,0x44,0x54,0x04,0x00,0x00,0x00,0x45,0x4e,0x44, + 0x54,0x04,0x00,0x00,0x00,0x45,0x4e,0x44,0x54,0xde,0xc0,0x17,0x0b,0x00,0x00,0x00, + 0x00,0x14,0x00,0x00,0x00,0xd8,0x0a,0x00,0x00,0xff,0xff,0xff,0xff,0x42,0x43,0xc0, + 0xde,0x21,0x0c,0x00,0x00,0xb3,0x02,0x00,0x00,0x0b,0x82,0x20,0x00,0x02,0x00,0x00, + 0x00,0x12,0x00,0x00,0x00,0x07,0x81,0x23,0x91,0x41,0xc8,0x04,0x49,0x06,0x10,0x32, + 0x39,0x92,0x01,0x84,0x0c,0x25,0x05,0x08,0x19,0x1e,0x04,0x8b,0x62,0x80,0x14,0x45, + 0x02,0x42,0x92,0x0b,0x42,0xa4,0x10,0x32,0x14,0x38,0x08,0x18,0x49,0x0a,0x32,0x44, + 0x24,0x48,0x0a,0x90,0x21,0x23,0xc4,0x52,0x80,0x0c,0x19,0x21,0x72,0x24,0x07,0xc8, + 0x48,0x11,0x62,0xa8,0xa0,0xa8,0x40,0xc6,0xf0,0x01,0x00,0x00,0x00,0x51,0x18,0x00, + 0x00,0x74,0x00,0x00,0x00,0x1b,0xc2,0x24,0xf8,0xff,0xff,0xff,0xff,0x01,0x60,0x00, + 0x09,0xa8,0x88,0x70,0x80,0x07,0x78,0x90,0x87,0x77,0xc0,0x87,0x36,0x30,0x87,0x7a, + 0x70,0x87,0x71,0x68,0x03,0x73,0x80,0x87,0x36,0x68,0x87,0x70,0xa0,0x07,0x74,0x00, + 0xe8,0x41,0x1e,0xea,0xa1,0x1c,0x00,0xa2,0x1d,0xd2,0xc1,0x1d,0xda,0x80,0x1d,0xca, + 0xe1,0x1c,0xc2,0x81,0x1d,0xda,0xc0,0x1e,0xca,0x61,0x1c,0xe8,0xe1,0x1d,0xe4,0xa1, + 0x0d,0xee,0x21,0x1d,0xc8,0x81,0x1e,0xd0,0x01,0x88,0x03,0x39,0xc0,0x03,0x60,0x70, + 0x87,0x77,0x68,0x03,0x71,0xa8,0x87,0x74,0x60,0x07,0x7a,0x48,0x07,0x77,0x98,0x07, + 0x80,0x70,0x87,0x77,0x68,0x03,0x73,0x90,0x87,0x70,0x68,0x87,0x72,0x68,0x03,0x78, + 0x78,0x87,0x74,0x70,0x07,0x7a,0x28,0x07,0x79,0x68,0x83,0x72,0x60,0x87,0x74,0x68, + 0x07,0x80,0x1e,0xe4,0xa1,0x1e,0xca,0x01,0x20,0xdc,0xe1,0x1d,0xda,0xc0,0x1c,0xe4, + 0x21,0x1c,0xda,0xa1,0x1c,0xda,0x00,0x1e,0xde,0x21,0x1d,0xdc,0x81,0x1e,0xca,0x41, + 0x1e,0xda,0xa0,0x1c,0xd8,0x21,0x1d,0xda,0xa1,0x0d,0xdc,0xe1,0x1d,0xdc,0xa1,0x0d, + 0xd8,0xa1,0x1c,0xc2,0xc1,0x1c,0x00,0x7a,0x90,0x87,0x7a,0x28,0x07,0x80,0x70,0x87, + 0x77,0x68,0x83,0x74,0x70,0x07,0x73,0x98,0x87,0x36,0x30,0x07,0x78,0x68,0x83,0x76, + 0x08,0x07,0x7a,0x40,0x07,0x80,0x1e,0xe4,0xa1,0x1e,0xca,0x01,0x20,0xdc,0xe1,0x1d, + 0xda,0xc0,0x1d,0xc2,0xc1,0x1d,0xe6,0xa1,0x0d,0xcc,0x01,0x1e,0xda,0xa0,0x1d,0xc2, + 0x81,0x1e,0xd0,0x01,0xa0,0x07,0x79,0xa8,0x87,0x72,0x00,0x08,0x77,0x78,0x87,0x36, + 0x98,0x87,0x74,0x38,0x07,0x77,0x28,0x07,0x72,0x68,0x03,0x7d,0x28,0x07,0x79,0x78, + 0x87,0x79,0x68,0x03,0x73,0x80,0x87,0x36,0x68,0x87,0x70,0xa0,0x07,0x74,0x00,0xe8, + 0x41,0x1e,0xea,0xa1,0x1c,0x00,0xc2,0x1d,0xde,0xa1,0x0d,0xe8,0x41,0x1e,0xc2,0x01, + 0x1e,0xe0,0x21,0x1d,0xdc,0xe1,0x1c,0xda,0xa0,0x1d,0xc2,0x81,0x1e,0xd0,0x01,0xa0, + 0x07,0x79,0xa8,0x87,0x72,0x00,0x88,0x79,0xa0,0x87,0x70,0x18,0x87,0x75,0x68,0x03, + 0x78,0x90,0x87,0x77,0xa0,0x87,0x72,0x18,0x07,0x7a,0x78,0x07,0x79,0x68,0x03,0x71, + 0xa8,0x07,0x73,0x30,0x87,0x72,0x90,0x87,0x36,0x98,0x87,0x74,0xd0,0x87,0x72,0x00, + 0xf0,0x00,0x20,0xea,0xc1,0x1d,0xe6,0x21,0x1c,0xcc,0xa1,0x1c,0xda,0xc0,0x1c,0xe0, + 0xa1,0x0d,0xda,0x21,0x1c,0xe8,0x01,0x1d,0x00,0x7a,0x90,0x87,0x7a,0x28,0x07,0x60, + 0x83,0x21,0x0c,0xc0,0x02,0x54,0x1b,0x8c,0x81,0x00,0x16,0xa0,0xda,0x80,0x10,0xff, + 0xff,0xff,0xff,0x3f,0x00,0x0c,0x20,0x01,0xd5,0x06,0xa3,0x08,0x80,0x05,0xa8,0x36, + 0x18,0x86,0x00,0x2c,0x40,0xb5,0x01,0x39,0xfe,0xff,0xff,0xff,0x7f,0x00,0x18,0x40, + 0x02,0x2a,0x00,0x00,0x00,0x49,0x18,0x00,0x00,0x04,0x00,0x00,0x00,0x13,0x86,0x40, + 0x18,0x26,0x0c,0x44,0x61,0x4c,0x18,0x8e,0xc2,0x00,0x00,0x00,0x00,0x89,0x20,0x00, + 0x00,0x1d,0x00,0x00,0x00,0x32,0x22,0x48,0x09,0x20,0x64,0x85,0x04,0x93,0x22,0xa4, + 0x84,0x04,0x93,0x22,0xe3,0x84,0xa1,0x90,0x14,0x12,0x4c,0x8a,0x8c,0x0b,0x84,0xa4, + 0x4c,0x10,0x48,0x33,0x00,0xc3,0x08,0x04,0x60,0x83,0x70,0x94,0x34,0x45,0x94,0x30, + 0xf9,0xff,0x44,0x5c,0x13,0x15,0x11,0xbf,0x3d,0xfc,0xd3,0x18,0x01,0x30,0x88,0x30, + 0x04,0x17,0x49,0x53,0x44,0x09,0x93,0xff,0x4b,0x00,0xf3,0x2c,0x44,0xf4,0x4f,0x63, + 0x04,0xc0,0x20,0x42,0x21,0x94,0x42,0x84,0x40,0x0c,0x9d,0x61,0x04,0x01,0x98,0x23, + 0x08,0xe6,0x08,0xc0,0x60,0x18,0x41,0x58,0x0a,0x12,0x88,0x49,0x8a,0x29,0x40,0x6d, + 0x20,0x20,0x05,0xd6,0x08,0x00,0x00,0x00,0x00,0x13,0xa8,0x70,0x48,0x07,0x79,0xb0, + 0x03,0x3a,0x68,0x83,0x70,0x80,0x07,0x78,0x60,0x87,0x72,0x68,0x83,0x74,0x78,0x87, + 0x79,0xc8,0x03,0x37,0x80,0x03,0x37,0x80,0x83,0x0d,0xb7,0x51,0x0e,0x6d,0x00,0x0f, + 0x7a,0x60,0x07,0x74,0xa0,0x07,0x76,0x40,0x07,0x7a,0x60,0x07,0x74,0xd0,0x06,0xe9, + 0x10,0x07,0x7a,0x80,0x07,0x7a,0x80,0x07,0x6d,0x90,0x0e,0x78,0xa0,0x07,0x78,0xa0, + 0x07,0x78,0xd0,0x06,0xe9,0x10,0x07,0x76,0xa0,0x07,0x71,0x60,0x07,0x7a,0x10,0x07, + 0x76,0xd0,0x06,0xe9,0x30,0x07,0x72,0xa0,0x07,0x73,0x20,0x07,0x7a,0x30,0x07,0x72, + 0xd0,0x06,0xe9,0x60,0x07,0x74,0xa0,0x07,0x76,0x40,0x07,0x7a,0x60,0x07,0x74,0xd0, + 0x06,0xe6,0x30,0x07,0x72,0xa0,0x07,0x73,0x20,0x07,0x7a,0x30,0x07,0x72,0xd0,0x06, + 0xe6,0x60,0x07,0x74,0xa0,0x07,0x76,0x40,0x07,0x7a,0x60,0x07,0x74,0xd0,0x06,0xf6, + 0x10,0x07,0x76,0xa0,0x07,0x71,0x60,0x07,0x7a,0x10,0x07,0x76,0xd0,0x06,0xf6,0x20, + 0x07,0x74,0xa0,0x07,0x73,0x20,0x07,0x7a,0x30,0x07,0x72,0xd0,0x06,0xf6,0x30,0x07, + 0x72,0xa0,0x07,0x73,0x20,0x07,0x7a,0x30,0x07,0x72,0xd0,0x06,0xf6,0x40,0x07,0x78, + 0xa0,0x07,0x76,0x40,0x07,0x7a,0x60,0x07,0x74,0xd0,0x06,0xf6,0x60,0x07,0x74,0xa0, + 0x07,0x76,0x40,0x07,0x7a,0x60,0x07,0x74,0xd0,0x06,0xf6,0x90,0x07,0x76,0xa0,0x07, + 0x71,0x20,0x07,0x78,0xa0,0x07,0x71,0x20,0x07,0x78,0xd0,0x06,0xf6,0x10,0x07,0x72, + 0x80,0x07,0x7a,0x10,0x07,0x72,0x80,0x07,0x7a,0x10,0x07,0x72,0x80,0x07,0x6d,0x60, + 0x0f,0x71,0x90,0x07,0x72,0xa0,0x07,0x72,0x50,0x07,0x76,0xa0,0x07,0x72,0x50,0x07, + 0x76,0xd0,0x06,0xf6,0x20,0x07,0x75,0x60,0x07,0x7a,0x20,0x07,0x75,0x60,0x07,0x7a, + 0x20,0x07,0x75,0x60,0x07,0x6d,0x60,0x0f,0x75,0x10,0x07,0x72,0xa0,0x07,0x75,0x10, + 0x07,0x72,0xa0,0x07,0x75,0x10,0x07,0x72,0xd0,0x06,0xf6,0x10,0x07,0x70,0x20,0x07, + 0x74,0xa0,0x07,0x71,0x00,0x07,0x72,0x40,0x07,0x7a,0x10,0x07,0x70,0x20,0x07,0x74, + 0xd0,0x06,0xee,0x80,0x07,0x7a,0x10,0x07,0x76,0xa0,0x07,0x73,0x20,0x07,0x43,0x18, + 0x04,0x00,0x80,0x00,0x00,0x00,0x00,0x00,0x80,0x21,0x8c,0x03,0x04,0x80,0x00,0x00, + 0x00,0x00,0x00,0x40,0x16,0x08,0x00,0x00,0x00,0x08,0x00,0x00,0x00,0x32,0x1e,0x98, + 0x10,0x19,0x11,0x4c,0x90,0x8c,0x09,0x26,0x47,0xc6,0x04,0x43,0x5a,0x25,0x30,0x02, + 0x50,0x04,0x85,0x50,0x10,0x65,0x40,0x70,0x2c,0x41,0x02,0x00,0x00,0x79,0x18,0x00, + 0x00,0xd0,0x00,0x00,0x00,0x1a,0x03,0x4c,0x10,0x97,0x29,0xa2,0x25,0x10,0xab,0x32, + 0xb9,0xb9,0xb4,0x37,0xb7,0x21,0xc6,0x42,0x3c,0x00,0x84,0x50,0xb9,0x1b,0x43,0x0b, + 0x93,0xfb,0x9a,0x4b,0xd3,0x2b,0x1b,0x62,0x2c,0xc2,0x23,0x2c,0x05,0xe3,0x20,0x08, + 0x0e,0x8e,0xad,0x0c,0xa4,0xad,0x8c,0x2e,0x8c,0x0d,0xc4,0xae,0x4c,0x6e,0x2e,0xed, + 0xcd,0x0d,0x64,0x46,0x06,0x46,0x66,0xc6,0x65,0x66,0xa6,0x06,0x04,0xa5,0xad,0x8c, + 0x2e,0x8c,0xcd,0xac,0xac,0x65,0x46,0x06,0x46,0x66,0xc6,0x65,0x66,0xa6,0x26,0x65, + 0x88,0xf0,0x10,0x43,0x8c,0x45,0x58,0x8c,0x65,0x60,0xd1,0x54,0x46,0x17,0xc6,0x36, + 0x04,0x79,0x8e,0x45,0x58,0x84,0x65,0xe0,0x16,0x96,0x26,0xe7,0x32,0xf6,0xd6,0x06, + 0x97,0xc6,0x56,0xe6,0x42,0x56,0xe6,0xf6,0x26,0xd7,0x36,0xf7,0x45,0x96,0x36,0x17, + 0x26,0xc6,0x56,0x36,0x44,0x78,0x12,0x72,0x61,0x69,0x72,0x2e,0x63,0x6f,0x6d,0x70, + 0x69,0x6c,0x65,0x2e,0x66,0x61,0x73,0x74,0x5f,0x6d,0x61,0x74,0x68,0x5f,0x65,0x6e, + 0x61,0x62,0x6c,0x65,0x43,0x84,0x67,0x21,0x19,0x84,0xa5,0xc9,0xb9,0x8c,0xbd,0xb5, + 0xc1,0xa5,0xb1,0x95,0xb9,0x98,0xc9,0x85,0xb5,0x95,0x89,0xd5,0x99,0x99,0x95,0xc9, + 0x7d,0x99,0x95,0xd1,0x8d,0xa1,0x7d,0x95,0xb9,0x85,0x89,0xb1,0x95,0x0d,0x11,0x9e, + 0x86,0x51,0x58,0x9a,0x9c,0x8b,0x5c,0x99,0x1b,0x59,0x99,0xdc,0x17,0x5d,0x98,0xdc, + 0x59,0x19,0x1d,0xa3,0xb0,0x34,0x39,0x97,0x30,0xb9,0xb3,0x2f,0xba,0x3c,0xb8,0xb2, + 0x2f,0xb7,0xb0,0xb6,0x32,0x1a,0x66,0x6c,0x6f,0x61,0x74,0x34,0x64,0xc2,0xd2,0xe4, + 0x5c,0xc2,0xe4,0xce,0xbe,0xdc,0xc2,0xda,0xca,0xa8,0x98,0xc9,0x85,0x9d,0x7d,0x8d, + 0xbd,0xb1,0xbd,0xc9,0x0d,0x61,0x9e,0x67,0x19,0x1e,0xe8,0x89,0x1e,0xe9,0x99,0x86, + 0x08,0x0f,0x45,0x29,0x2c,0x4d,0xce,0xc5,0x4c,0x2e,0xec,0xac,0xad,0xcc,0x8d,0xee, + 0x2b,0xcd,0x0d,0xae,0x8e,0x8e,0x4b,0xdd,0x5c,0x99,0x1c,0x0a,0xdb,0xdb,0x98,0x1b, + 0x4c,0x0a,0x95,0xb0,0x34,0x39,0x97,0xb1,0x32,0x37,0xba,0x32,0x39,0x3e,0x61,0x69, + 0x72,0x2e,0x70,0x65,0x72,0x73,0x70,0x65,0x63,0x74,0x69,0x76,0x65,0x14,0xea,0xec, + 0x86,0x48,0xcb,0xf0,0x58,0xcf,0xf5,0x60,0x4f,0xf6,0x40,0x4f,0xf4,0x48,0x8f,0xc6, + 0xa5,0x6e,0xae,0x4c,0x0e,0x85,0xed,0x6d,0xcc,0x2d,0x26,0x85,0xc5,0xd8,0x1b,0xdb, + 0x9b,0xdc,0x10,0x69,0x11,0x1e,0xeb,0xe1,0x1e,0xec,0xc9,0x1e,0xe8,0x89,0x1e,0xe9, + 0xe9,0xb8,0x84,0xa5,0xc9,0xb9,0xd0,0x95,0xe1,0xd1,0xd5,0xc9,0x95,0x51,0x0a,0x4b, + 0x93,0x73,0x61,0x7b,0x1b,0x0b,0xa3,0x4b,0x7b,0x73,0xfb,0x4a,0x73,0x23,0x2b,0xc3, + 0xa3,0x12,0x96,0x26,0xe7,0x32,0x17,0xd6,0x06,0xc7,0x56,0x46,0x8c,0xae,0x0c,0x8f, + 0xae,0x4e,0xae,0x4c,0x86,0x8c,0xc7,0x8c,0xed,0x2d,0x8c,0x8e,0x05,0x64,0x2e,0xac, + 0x0d,0x8e,0xad,0xcc,0x87,0x03,0x5d,0x19,0xde,0x10,0x6a,0x21,0x9e,0xef,0x01,0x83, + 0x65,0x58,0x84,0x27,0x0c,0x1e,0xe8,0x11,0x83,0x47,0x7a,0xc6,0x80,0x4b,0x58,0x9a, + 0x9c,0xcb,0x5c,0x58,0x1b,0x1c,0x5b,0x99,0x1c,0x8f,0xb9,0xb0,0x36,0x38,0xb6,0x32, + 0x39,0x0e,0x73,0x6d,0x70,0x43,0xa4,0xe5,0x78,0xca,0xe0,0x01,0x83,0x65,0x58,0x84, + 0x07,0x7a,0xcc,0xe0,0x91,0x9e,0x33,0x18,0x82,0x3c,0xdb,0xe3,0x3d,0x64,0xf0,0xa0, + 0xc1,0x10,0x03,0x01,0x9e,0xea,0x49,0x03,0x5e,0x61,0x69,0x72,0x2d,0x61,0x6c,0x69, + 0x61,0x73,0x2d,0x73,0x63,0x6f,0x70,0x65,0x73,0x28,0x6d,0x61,0x69,0x6e,0x30,0x29, + 0x43,0x88,0x87,0x0d,0x9e,0x35,0x20,0x16,0x96,0x26,0xd7,0x12,0xc6,0x96,0x16,0x36, + 0xd7,0x32,0x37,0xf6,0x06,0x57,0xd6,0x42,0x57,0x86,0x47,0x57,0x27,0x57,0x36,0x37, + 0xc4,0x78,0xdc,0xe0,0x61,0x83,0xa7,0x0d,0x88,0x85,0xa5,0xc9,0xb5,0x84,0xb1,0xa5, + 0x85,0xcd,0xb5,0xcc,0x8d,0xbd,0xc1,0x95,0xb5,0xcc,0x85,0xb5,0xc1,0xb1,0x95,0xc9, + 0xcd,0x0d,0x31,0x1e,0x38,0x78,0xd8,0xe0,0x79,0x83,0x21,0xc4,0xe3,0x06,0x0f,0x1c, + 0x8c,0x88,0xd8,0x81,0x1d,0xec,0xa1,0x1d,0xdc,0xa0,0x1d,0xde,0x81,0x1c,0xea,0x81, + 0x1d,0xca,0xc1,0x0d,0xcc,0x81,0x1d,0xc2,0xe1,0x1c,0xe6,0x61,0x8a,0x10,0x0c,0x23, + 0x14,0x76,0x60,0x07,0x7b,0x68,0x07,0x37,0x48,0x07,0x72,0x28,0x07,0x77,0xa0,0x87, + 0x29,0x41,0x31,0x62,0x09,0x87,0x74,0x90,0x07,0x37,0xb0,0x87,0x72,0x90,0x87,0x79, + 0x48,0x87,0x77,0x70,0x87,0x29,0x81,0x31,0x82,0x0a,0x87,0x74,0x90,0x07,0x37,0x60, + 0x87,0x70,0x70,0x87,0x73,0xa8,0x87,0x70,0x38,0x87,0x72,0xf8,0x05,0x7b,0x28,0x07, + 0x79,0x98,0x87,0x74,0x78,0x07,0x77,0x98,0x12,0x20,0x23,0xa6,0x70,0x48,0x07,0x79, + 0x70,0x83,0x71,0x78,0x87,0x76,0x80,0x87,0x74,0x60,0x87,0x72,0xf8,0x85,0x77,0x80, + 0x07,0x7a,0x48,0x87,0x77,0x70,0x87,0x79,0x98,0x32,0x28,0x8c,0x33,0x82,0x09,0x87, + 0x74,0x90,0x07,0x37,0x30,0x07,0x79,0x08,0x87,0x73,0x68,0x87,0x72,0x70,0x07,0x7a, + 0x98,0x12,0xa8,0x01,0x00,0x79,0x18,0x00,0x00,0xa5,0x00,0x00,0x00,0x33,0x08,0x80, + 0x1c,0xc4,0xe1,0x1c,0x66,0x14,0x01,0x3d,0x88,0x43,0x38,0x84,0xc3,0x8c,0x42,0x80, + 0x07,0x79,0x78,0x07,0x73,0x98,0x71,0x0c,0xe6,0x00,0x0f,0xed,0x10,0x0e,0xf4,0x80, + 0x0e,0x33,0x0c,0x42,0x1e,0xc2,0xc1,0x1d,0xce,0xa1,0x1c,0x66,0x30,0x05,0x3d,0x88, + 0x43,0x38,0x84,0x83,0x1b,0xcc,0x03,0x3d,0xc8,0x43,0x3d,0x8c,0x03,0x3d,0xcc,0x78, + 0x8c,0x74,0x70,0x07,0x7b,0x08,0x07,0x79,0x48,0x87,0x70,0x70,0x07,0x7a,0x70,0x03, + 0x76,0x78,0x87,0x70,0x20,0x87,0x19,0xcc,0x11,0x0e,0xec,0x90,0x0e,0xe1,0x30,0x0f, + 0x6e,0x30,0x0f,0xe3,0xf0,0x0e,0xf0,0x50,0x0e,0x33,0x10,0xc4,0x1d,0xde,0x21,0x1c, + 0xd8,0x21,0x1d,0xc2,0x61,0x1e,0x66,0x30,0x89,0x3b,0xbc,0x83,0x3b,0xd0,0x43,0x39, + 0xb4,0x03,0x3c,0xbc,0x83,0x3c,0x84,0x03,0x3b,0xcc,0xf0,0x14,0x76,0x60,0x07,0x7b, + 0x68,0x07,0x37,0x68,0x87,0x72,0x68,0x07,0x37,0x80,0x87,0x70,0x90,0x87,0x70,0x60, + 0x07,0x76,0x28,0x07,0x76,0xf8,0x05,0x76,0x78,0x87,0x77,0x80,0x87,0x5f,0x08,0x87, + 0x71,0x18,0x87,0x72,0x98,0x87,0x79,0x98,0x81,0x2c,0xee,0xf0,0x0e,0xee,0xe0,0x0e, + 0xf5,0xc0,0x0e,0xec,0x30,0x03,0x62,0xc8,0xa1,0x1c,0xe4,0xa1,0x1c,0xcc,0xa1,0x1c, + 0xe4,0xa1,0x1c,0xdc,0x61,0x1c,0xca,0x21,0x1c,0xc4,0x81,0x1d,0xca,0x61,0x06,0xd6, + 0x90,0x43,0x39,0xc8,0x43,0x39,0x98,0x43,0x39,0xc8,0x43,0x39,0xb8,0xc3,0x38,0x94, + 0x43,0x38,0x88,0x03,0x3b,0x94,0xc3,0x2f,0xbc,0x83,0x3c,0xfc,0x82,0x3b,0xd4,0x03, + 0x3b,0xb0,0xc3,0x0c,0xc7,0x69,0x87,0x70,0x58,0x87,0x72,0x70,0x83,0x74,0x68,0x07, + 0x78,0x60,0x87,0x74,0x18,0x87,0x74,0xa0,0x87,0x19,0xce,0x53,0x0f,0xee,0x00,0x0f, + 0xf2,0x50,0x0e,0xe4,0x90,0x0e,0xe3,0x40,0x0f,0xe1,0x20,0x0e,0xec,0x50,0x0e,0x33, + 0x20,0x28,0x1d,0xdc,0xc1,0x1e,0xc2,0x41,0x1e,0xd2,0x21,0x1c,0xdc,0x81,0x1e,0xdc, + 0xe0,0x1c,0xe4,0xe1,0x1d,0xea,0x01,0x1e,0x66,0x18,0x51,0x38,0xb0,0x43,0x3a,0x9c, + 0x83,0x3b,0xcc,0x50,0x24,0x76,0x60,0x07,0x7b,0x68,0x07,0x37,0x60,0x87,0x77,0x78, + 0x07,0x78,0x98,0x51,0x4c,0xf4,0x90,0x0f,0xf0,0x50,0x0e,0x33,0x1e,0x6a,0x1e,0xca, + 0x61,0x1c,0xe8,0x21,0x1d,0xde,0xc1,0x1d,0x7e,0x01,0x1e,0xe4,0xa1,0x1c,0xcc,0x21, + 0x1d,0xf0,0x61,0x06,0x54,0x85,0x83,0x38,0xcc,0xc3,0x3b,0xb0,0x43,0x3d,0xd0,0x43, + 0x39,0xfc,0xc2,0x3c,0xe4,0x43,0x3b,0x88,0xc3,0x3b,0xb0,0xc3,0x8c,0xc5,0x0a,0x87, + 0x79,0x98,0x87,0x77,0x18,0x87,0x74,0x08,0x07,0x7a,0x28,0x07,0x72,0x98,0x81,0x5c, + 0xe3,0x10,0x0e,0xec,0xc0,0x0e,0xe5,0x50,0x0e,0xf3,0x30,0x23,0xc1,0xd2,0x41,0x1e, + 0xe4,0xe1,0x17,0xd8,0xe1,0x1d,0xde,0x01,0x1e,0x66,0x48,0x19,0x3b,0xb0,0x83,0x3d, + 0xb4,0x83,0x1b,0x84,0xc3,0x38,0x8c,0x43,0x39,0xcc,0xc3,0x3c,0xb8,0xc1,0x39,0xc8, + 0xc3,0x3b,0xd4,0x03,0x3c,0xcc,0x48,0xb4,0x71,0x08,0x07,0x76,0x60,0x07,0x71,0x08, + 0x87,0x71,0x58,0x87,0x19,0xdb,0xc6,0x0e,0xec,0x60,0x0f,0xed,0xe0,0x06,0xf0,0x20, + 0x0f,0xe5,0x30,0x0f,0xe5,0x20,0x0f,0xf6,0x50,0x0e,0x6e,0x10,0x0e,0xe3,0x30,0x0e, + 0xe5,0x30,0x0f,0xf3,0xe0,0x06,0xe9,0xe0,0x0e,0xe4,0x50,0x0e,0xf8,0x30,0x23,0xe2, + 0xec,0x61,0x1c,0xc2,0x81,0x1d,0xd8,0xe1,0x17,0xec,0x21,0x1d,0xe6,0x21,0x1d,0xc4, + 0x21,0x1d,0xd8,0x21,0x1d,0xe8,0x21,0x1f,0x66,0x20,0x9d,0x3b,0xbc,0x43,0x3d,0xb8, + 0x03,0x39,0x94,0x83,0x39,0xcc,0x58,0xbc,0x70,0x70,0x07,0x77,0x78,0x07,0x7a,0x08, + 0x07,0x7a,0x48,0x87,0x77,0x70,0x87,0x19,0xce,0x87,0x0e,0xe5,0x10,0x0e,0xf0,0x10, + 0x0e,0xec,0xc0,0x0e,0xef,0x30,0x0e,0xf3,0x90,0x0e,0xf4,0x50,0x0e,0x33,0x28,0x30, + 0x08,0x87,0x74,0x90,0x07,0x37,0x30,0x87,0x7a,0x70,0x87,0x71,0xa0,0x87,0x74,0x78, + 0x07,0x77,0xf8,0x85,0x73,0x90,0x87,0x77,0xa8,0x07,0x78,0x98,0x07,0x00,0x00,0x00, + 0x00,0x71,0x20,0x00,0x00,0x08,0x00,0x00,0x00,0x16,0xb0,0x01,0x48,0xe4,0x4b,0x00, + 0xf3,0x2c,0xc4,0x3f,0x11,0xd7,0x44,0x45,0xc4,0x6f,0x0f,0x7e,0x85,0x17,0xb7,0x6d, + 0x00,0x05,0x03,0x20,0x0d,0x0d,0x00,0x00,0x00,0x61,0x20,0x00,0x00,0x14,0x00,0x00, + 0x00,0x13,0x04,0x41,0x2c,0x10,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0xc4,0x46,0x00, + 0xc6,0x12,0x80,0x80,0xd4,0x08,0x40,0x0d,0x90,0x98,0x01,0xa0,0x30,0x03,0x40,0x60, + 0x04,0x00,0x00,0x00,0x00,0x83,0x0c,0x8b,0x60,0x8c,0x18,0x28,0x43,0x40,0x29,0x49, + 0x50,0x20,0x86,0x60,0x01,0x23,0x9f,0xd9,0x06,0x23,0x00,0x32,0x08,0x88,0x01,0x00, + 0x00,0x02,0x00,0x00,0x00,0x5b,0x86,0xe0,0x88,0x03,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +}; +/* + #include + #include + + using namespace metal; + + struct vs_params + { + float4x4 mvp; + float4x4 tm; + }; + + struct main0_out + { + float4 uv [[user(locn0)]]; + float4 color [[user(locn1)]]; + float4 gl_Position [[position]]; + float gl_PointSize [[point_size]]; + }; + + struct main0_in + { + float4 position [[attribute(0)]]; + float2 texcoord0 [[attribute(1)]]; + float4 color0 [[attribute(2)]]; + float psize [[attribute(3)]]; + }; + + vertex main0_out main0(main0_in in [[stage_in]], constant vs_params& _19 [[buffer(0)]]) + { + main0_out out = {}; + out.gl_Position = _19.mvp * in.position; + out.gl_PointSize = in.psize; + out.uv = _19.tm * float4(in.texcoord0, 0.0, 1.0); + out.color = in.color0; + return out; + } +*/ +static const uint8_t _sgl_vs_source_metal_sim[756] = { + 0x23,0x69,0x6e,0x63,0x6c,0x75,0x64,0x65,0x20,0x3c,0x6d,0x65,0x74,0x61,0x6c,0x5f, + 0x73,0x74,0x64,0x6c,0x69,0x62,0x3e,0x0a,0x23,0x69,0x6e,0x63,0x6c,0x75,0x64,0x65, + 0x20,0x3c,0x73,0x69,0x6d,0x64,0x2f,0x73,0x69,0x6d,0x64,0x2e,0x68,0x3e,0x0a,0x0a, + 0x75,0x73,0x69,0x6e,0x67,0x20,0x6e,0x61,0x6d,0x65,0x73,0x70,0x61,0x63,0x65,0x20, + 0x6d,0x65,0x74,0x61,0x6c,0x3b,0x0a,0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x76, + 0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66, + 0x6c,0x6f,0x61,0x74,0x34,0x78,0x34,0x20,0x6d,0x76,0x70,0x3b,0x0a,0x20,0x20,0x20, + 0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x78,0x34,0x20,0x74,0x6d,0x3b,0x0a,0x7d,0x3b, + 0x0a,0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x6d,0x61,0x69,0x6e,0x30,0x5f,0x6f, + 0x75,0x74,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20, + 0x75,0x76,0x20,0x5b,0x5b,0x75,0x73,0x65,0x72,0x28,0x6c,0x6f,0x63,0x6e,0x30,0x29, + 0x5d,0x5d,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20,0x63, + 0x6f,0x6c,0x6f,0x72,0x20,0x5b,0x5b,0x75,0x73,0x65,0x72,0x28,0x6c,0x6f,0x63,0x6e, + 0x31,0x29,0x5d,0x5d,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34, + 0x20,0x67,0x6c,0x5f,0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x20,0x5b,0x5b,0x70, + 0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x5d,0x5d,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66, + 0x6c,0x6f,0x61,0x74,0x20,0x67,0x6c,0x5f,0x50,0x6f,0x69,0x6e,0x74,0x53,0x69,0x7a, + 0x65,0x20,0x5b,0x5b,0x70,0x6f,0x69,0x6e,0x74,0x5f,0x73,0x69,0x7a,0x65,0x5d,0x5d, + 0x3b,0x0a,0x7d,0x3b,0x0a,0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x6d,0x61,0x69, + 0x6e,0x30,0x5f,0x69,0x6e,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61, + 0x74,0x34,0x20,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x20,0x5b,0x5b,0x61,0x74, + 0x74,0x72,0x69,0x62,0x75,0x74,0x65,0x28,0x30,0x29,0x5d,0x5d,0x3b,0x0a,0x20,0x20, + 0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x32,0x20,0x74,0x65,0x78,0x63,0x6f,0x6f,0x72, + 0x64,0x30,0x20,0x5b,0x5b,0x61,0x74,0x74,0x72,0x69,0x62,0x75,0x74,0x65,0x28,0x31, + 0x29,0x5d,0x5d,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20, + 0x63,0x6f,0x6c,0x6f,0x72,0x30,0x20,0x5b,0x5b,0x61,0x74,0x74,0x72,0x69,0x62,0x75, + 0x74,0x65,0x28,0x32,0x29,0x5d,0x5d,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f, + 0x61,0x74,0x20,0x70,0x73,0x69,0x7a,0x65,0x20,0x5b,0x5b,0x61,0x74,0x74,0x72,0x69, + 0x62,0x75,0x74,0x65,0x28,0x33,0x29,0x5d,0x5d,0x3b,0x0a,0x7d,0x3b,0x0a,0x0a,0x76, + 0x65,0x72,0x74,0x65,0x78,0x20,0x6d,0x61,0x69,0x6e,0x30,0x5f,0x6f,0x75,0x74,0x20, + 0x6d,0x61,0x69,0x6e,0x30,0x28,0x6d,0x61,0x69,0x6e,0x30,0x5f,0x69,0x6e,0x20,0x69, + 0x6e,0x20,0x5b,0x5b,0x73,0x74,0x61,0x67,0x65,0x5f,0x69,0x6e,0x5d,0x5d,0x2c,0x20, + 0x63,0x6f,0x6e,0x73,0x74,0x61,0x6e,0x74,0x20,0x76,0x73,0x5f,0x70,0x61,0x72,0x61, + 0x6d,0x73,0x26,0x20,0x5f,0x31,0x39,0x20,0x5b,0x5b,0x62,0x75,0x66,0x66,0x65,0x72, + 0x28,0x30,0x29,0x5d,0x5d,0x29,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x6d,0x61,0x69, + 0x6e,0x30,0x5f,0x6f,0x75,0x74,0x20,0x6f,0x75,0x74,0x20,0x3d,0x20,0x7b,0x7d,0x3b, + 0x0a,0x20,0x20,0x20,0x20,0x6f,0x75,0x74,0x2e,0x67,0x6c,0x5f,0x50,0x6f,0x73,0x69, + 0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x5f,0x31,0x39,0x2e,0x6d,0x76,0x70,0x20,0x2a, + 0x20,0x69,0x6e,0x2e,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x3b,0x0a,0x20,0x20, + 0x20,0x20,0x6f,0x75,0x74,0x2e,0x67,0x6c,0x5f,0x50,0x6f,0x69,0x6e,0x74,0x53,0x69, + 0x7a,0x65,0x20,0x3d,0x20,0x69,0x6e,0x2e,0x70,0x73,0x69,0x7a,0x65,0x3b,0x0a,0x20, + 0x20,0x20,0x20,0x6f,0x75,0x74,0x2e,0x75,0x76,0x20,0x3d,0x20,0x5f,0x31,0x39,0x2e, + 0x74,0x6d,0x20,0x2a,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x28,0x69,0x6e,0x2e,0x74, + 0x65,0x78,0x63,0x6f,0x6f,0x72,0x64,0x30,0x2c,0x20,0x30,0x2e,0x30,0x2c,0x20,0x31, + 0x2e,0x30,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x6f,0x75,0x74,0x2e,0x63,0x6f,0x6c, + 0x6f,0x72,0x20,0x3d,0x20,0x69,0x6e,0x2e,0x63,0x6f,0x6c,0x6f,0x72,0x30,0x3b,0x0a, + 0x20,0x20,0x20,0x20,0x72,0x65,0x74,0x75,0x72,0x6e,0x20,0x6f,0x75,0x74,0x3b,0x0a, + 0x7d,0x0a,0x0a,0x00, +}; +/* + #include + #include + + using namespace metal; + + struct main0_out + { + float4 frag_color [[color(0)]]; + }; + + struct main0_in + { + float4 uv [[user(locn0)]]; + float4 color [[user(locn1)]]; + }; + + fragment main0_out main0(main0_in in [[stage_in]], texture2d tex [[texture(0)]], sampler smp [[sampler(0)]]) + { + main0_out out = {}; + out.frag_color = tex.sample(smp, in.uv.xy) * in.color; + return out; + } +*/ +static const uint8_t _sgl_fs_source_metal_sim[439] = { + 0x23,0x69,0x6e,0x63,0x6c,0x75,0x64,0x65,0x20,0x3c,0x6d,0x65,0x74,0x61,0x6c,0x5f, + 0x73,0x74,0x64,0x6c,0x69,0x62,0x3e,0x0a,0x23,0x69,0x6e,0x63,0x6c,0x75,0x64,0x65, + 0x20,0x3c,0x73,0x69,0x6d,0x64,0x2f,0x73,0x69,0x6d,0x64,0x2e,0x68,0x3e,0x0a,0x0a, + 0x75,0x73,0x69,0x6e,0x67,0x20,0x6e,0x61,0x6d,0x65,0x73,0x70,0x61,0x63,0x65,0x20, + 0x6d,0x65,0x74,0x61,0x6c,0x3b,0x0a,0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x6d, + 0x61,0x69,0x6e,0x30,0x5f,0x6f,0x75,0x74,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66, + 0x6c,0x6f,0x61,0x74,0x34,0x20,0x66,0x72,0x61,0x67,0x5f,0x63,0x6f,0x6c,0x6f,0x72, + 0x20,0x5b,0x5b,0x63,0x6f,0x6c,0x6f,0x72,0x28,0x30,0x29,0x5d,0x5d,0x3b,0x0a,0x7d, + 0x3b,0x0a,0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x6d,0x61,0x69,0x6e,0x30,0x5f, + 0x69,0x6e,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20, + 0x75,0x76,0x20,0x5b,0x5b,0x75,0x73,0x65,0x72,0x28,0x6c,0x6f,0x63,0x6e,0x30,0x29, + 0x5d,0x5d,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20,0x63, + 0x6f,0x6c,0x6f,0x72,0x20,0x5b,0x5b,0x75,0x73,0x65,0x72,0x28,0x6c,0x6f,0x63,0x6e, + 0x31,0x29,0x5d,0x5d,0x3b,0x0a,0x7d,0x3b,0x0a,0x0a,0x66,0x72,0x61,0x67,0x6d,0x65, + 0x6e,0x74,0x20,0x6d,0x61,0x69,0x6e,0x30,0x5f,0x6f,0x75,0x74,0x20,0x6d,0x61,0x69, + 0x6e,0x30,0x28,0x6d,0x61,0x69,0x6e,0x30,0x5f,0x69,0x6e,0x20,0x69,0x6e,0x20,0x5b, + 0x5b,0x73,0x74,0x61,0x67,0x65,0x5f,0x69,0x6e,0x5d,0x5d,0x2c,0x20,0x74,0x65,0x78, + 0x74,0x75,0x72,0x65,0x32,0x64,0x3c,0x66,0x6c,0x6f,0x61,0x74,0x3e,0x20,0x74,0x65, + 0x78,0x20,0x5b,0x5b,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x28,0x30,0x29,0x5d,0x5d, + 0x2c,0x20,0x73,0x61,0x6d,0x70,0x6c,0x65,0x72,0x20,0x73,0x6d,0x70,0x20,0x5b,0x5b, + 0x73,0x61,0x6d,0x70,0x6c,0x65,0x72,0x28,0x30,0x29,0x5d,0x5d,0x29,0x0a,0x7b,0x0a, + 0x20,0x20,0x20,0x20,0x6d,0x61,0x69,0x6e,0x30,0x5f,0x6f,0x75,0x74,0x20,0x6f,0x75, + 0x74,0x20,0x3d,0x20,0x7b,0x7d,0x3b,0x0a,0x20,0x20,0x20,0x20,0x6f,0x75,0x74,0x2e, + 0x66,0x72,0x61,0x67,0x5f,0x63,0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20,0x74,0x65,0x78, + 0x2e,0x73,0x61,0x6d,0x70,0x6c,0x65,0x28,0x73,0x6d,0x70,0x2c,0x20,0x69,0x6e,0x2e, + 0x75,0x76,0x2e,0x78,0x79,0x29,0x20,0x2a,0x20,0x69,0x6e,0x2e,0x63,0x6f,0x6c,0x6f, + 0x72,0x3b,0x0a,0x20,0x20,0x20,0x20,0x72,0x65,0x74,0x75,0x72,0x6e,0x20,0x6f,0x75, + 0x74,0x3b,0x0a,0x7d,0x0a,0x0a,0x00, +}; +#elif defined(SOKOL_D3D11) +/* + cbuffer vs_params : register(b0) + { + row_major float4x4 _19_mvp : packoffset(c0); + row_major float4x4 _19_tm : packoffset(c4); + }; + + + static float4 gl_Position; + static float gl_PointSize; + static float4 position; + static float psize; + static float4 uv; + static float2 texcoord0; + static float4 color; + static float4 color0; + + struct SPIRV_Cross_Input + { + float4 position : TEXCOORD0; + float2 texcoord0 : TEXCOORD1; + float4 color0 : TEXCOORD2; + float psize : TEXCOORD3; + }; + + struct SPIRV_Cross_Output + { + float4 uv : TEXCOORD0; + float4 color : TEXCOORD1; + float4 gl_Position : SV_Position; + }; + + void vert_main() + { + gl_Position = mul(position, _19_mvp); + gl_PointSize = psize; + uv = mul(float4(texcoord0, 0.0f, 1.0f), _19_tm); + color = color0; + } + + SPIRV_Cross_Output main(SPIRV_Cross_Input stage_input) + { + position = stage_input.position; + psize = stage_input.psize; + texcoord0 = stage_input.texcoord0; + color0 = stage_input.color0; + vert_main(); + SPIRV_Cross_Output stage_output; + stage_output.gl_Position = gl_Position; + stage_output.uv = uv; + stage_output.color = color; + return stage_output; + } +*/ +static const uint8_t _sgl_vs_bytecode_hlsl4[1032] = { + 0x44,0x58,0x42,0x43,0x74,0x7f,0x01,0xd9,0xf4,0xd5,0xed,0x1d,0x74,0xc1,0x30,0x27, + 0xd8,0xe9,0x9d,0x50,0x01,0x00,0x00,0x00,0x08,0x04,0x00,0x00,0x05,0x00,0x00,0x00, + 0x34,0x00,0x00,0x00,0x14,0x01,0x00,0x00,0x90,0x01,0x00,0x00,0x00,0x02,0x00,0x00, + 0x8c,0x03,0x00,0x00,0x52,0x44,0x45,0x46,0xd8,0x00,0x00,0x00,0x01,0x00,0x00,0x00, + 0x48,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x1c,0x00,0x00,0x00,0x00,0x04,0xfe,0xff, + 0x10,0x81,0x00,0x00,0xaf,0x00,0x00,0x00,0x3c,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d, + 0x73,0x00,0xab,0xab,0x3c,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x60,0x00,0x00,0x00, + 0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x90,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x40,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x98,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0xa8,0x00,0x00,0x00,0x40,0x00,0x00,0x00,0x40,0x00,0x00,0x00, + 0x02,0x00,0x00,0x00,0x98,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x5f,0x31,0x39,0x5f, + 0x6d,0x76,0x70,0x00,0x02,0x00,0x03,0x00,0x04,0x00,0x04,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x5f,0x31,0x39,0x5f,0x74,0x6d,0x00,0x4d,0x69,0x63,0x72,0x6f, + 0x73,0x6f,0x66,0x74,0x20,0x28,0x52,0x29,0x20,0x48,0x4c,0x53,0x4c,0x20,0x53,0x68, + 0x61,0x64,0x65,0x72,0x20,0x43,0x6f,0x6d,0x70,0x69,0x6c,0x65,0x72,0x20,0x31,0x30, + 0x2e,0x31,0x00,0xab,0x49,0x53,0x47,0x4e,0x74,0x00,0x00,0x00,0x04,0x00,0x00,0x00, + 0x08,0x00,0x00,0x00,0x68,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0f,0x0f,0x00,0x00,0x68,0x00,0x00,0x00, + 0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x01,0x00,0x00,0x00, + 0x03,0x03,0x00,0x00,0x68,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x03,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x0f,0x0f,0x00,0x00,0x68,0x00,0x00,0x00, + 0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x03,0x00,0x00,0x00, + 0x01,0x00,0x00,0x00,0x54,0x45,0x58,0x43,0x4f,0x4f,0x52,0x44,0x00,0xab,0xab,0xab, + 0x4f,0x53,0x47,0x4e,0x68,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x08,0x00,0x00,0x00, + 0x50,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x0f,0x00,0x00,0x00,0x50,0x00,0x00,0x00,0x01,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x0f,0x00,0x00,0x00, + 0x59,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x03,0x00,0x00,0x00, + 0x02,0x00,0x00,0x00,0x0f,0x00,0x00,0x00,0x54,0x45,0x58,0x43,0x4f,0x4f,0x52,0x44, + 0x00,0x53,0x56,0x5f,0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x00,0xab,0xab,0xab, + 0x53,0x48,0x44,0x52,0x84,0x01,0x00,0x00,0x40,0x00,0x01,0x00,0x61,0x00,0x00,0x00, + 0x59,0x00,0x00,0x04,0x46,0x8e,0x20,0x00,0x00,0x00,0x00,0x00,0x08,0x00,0x00,0x00, + 0x5f,0x00,0x00,0x03,0xf2,0x10,0x10,0x00,0x00,0x00,0x00,0x00,0x5f,0x00,0x00,0x03, + 0x32,0x10,0x10,0x00,0x01,0x00,0x00,0x00,0x5f,0x00,0x00,0x03,0xf2,0x10,0x10,0x00, + 0x02,0x00,0x00,0x00,0x65,0x00,0x00,0x03,0xf2,0x20,0x10,0x00,0x00,0x00,0x00,0x00, + 0x65,0x00,0x00,0x03,0xf2,0x20,0x10,0x00,0x01,0x00,0x00,0x00,0x67,0x00,0x00,0x04, + 0xf2,0x20,0x10,0x00,0x02,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x68,0x00,0x00,0x02, + 0x01,0x00,0x00,0x00,0x38,0x00,0x00,0x08,0xf2,0x00,0x10,0x00,0x00,0x00,0x00,0x00, + 0x56,0x15,0x10,0x00,0x01,0x00,0x00,0x00,0x46,0x8e,0x20,0x00,0x00,0x00,0x00,0x00, + 0x05,0x00,0x00,0x00,0x32,0x00,0x00,0x0a,0xf2,0x00,0x10,0x00,0x00,0x00,0x00,0x00, + 0x06,0x10,0x10,0x00,0x01,0x00,0x00,0x00,0x46,0x8e,0x20,0x00,0x00,0x00,0x00,0x00, + 0x04,0x00,0x00,0x00,0x46,0x0e,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x08, + 0xf2,0x20,0x10,0x00,0x00,0x00,0x00,0x00,0x46,0x0e,0x10,0x00,0x00,0x00,0x00,0x00, + 0x46,0x8e,0x20,0x00,0x00,0x00,0x00,0x00,0x07,0x00,0x00,0x00,0x36,0x00,0x00,0x05, + 0xf2,0x20,0x10,0x00,0x01,0x00,0x00,0x00,0x46,0x1e,0x10,0x00,0x02,0x00,0x00,0x00, + 0x38,0x00,0x00,0x08,0xf2,0x00,0x10,0x00,0x00,0x00,0x00,0x00,0x56,0x15,0x10,0x00, + 0x00,0x00,0x00,0x00,0x46,0x8e,0x20,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00, + 0x32,0x00,0x00,0x0a,0xf2,0x00,0x10,0x00,0x00,0x00,0x00,0x00,0x06,0x10,0x10,0x00, + 0x00,0x00,0x00,0x00,0x46,0x8e,0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x46,0x0e,0x10,0x00,0x00,0x00,0x00,0x00,0x32,0x00,0x00,0x0a,0xf2,0x00,0x10,0x00, + 0x00,0x00,0x00,0x00,0xa6,0x1a,0x10,0x00,0x00,0x00,0x00,0x00,0x46,0x8e,0x20,0x00, + 0x00,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x46,0x0e,0x10,0x00,0x00,0x00,0x00,0x00, + 0x32,0x00,0x00,0x0a,0xf2,0x20,0x10,0x00,0x02,0x00,0x00,0x00,0xf6,0x1f,0x10,0x00, + 0x00,0x00,0x00,0x00,0x46,0x8e,0x20,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00, + 0x46,0x0e,0x10,0x00,0x00,0x00,0x00,0x00,0x3e,0x00,0x00,0x01,0x53,0x54,0x41,0x54, + 0x74,0x00,0x00,0x00,0x09,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x06,0x00,0x00,0x00,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +}; +/* + Texture2D tex : register(t0); + SamplerState smp : register(s0); + + static float4 frag_color; + static float4 uv; + static float4 color; + + struct SPIRV_Cross_Input + { + float4 uv : TEXCOORD0; + float4 color : TEXCOORD1; + }; + + struct SPIRV_Cross_Output + { + float4 frag_color : SV_Target0; + }; + + void frag_main() + { + frag_color = tex.Sample(smp, uv.xy) * color; + } + + SPIRV_Cross_Output main(SPIRV_Cross_Input stage_input) + { + uv = stage_input.uv; + color = stage_input.color; + frag_main(); + SPIRV_Cross_Output stage_output; + stage_output.frag_color = frag_color; + return stage_output; + } +*/ +static const uint8_t _sgl_fs_bytecode_hlsl4[608] = { + 0x44,0x58,0x42,0x43,0xc8,0x9b,0x66,0x64,0x80,0x2f,0xbe,0x14,0xd9,0x88,0xa0,0x97, + 0x64,0x14,0x66,0xff,0x01,0x00,0x00,0x00,0x60,0x02,0x00,0x00,0x05,0x00,0x00,0x00, + 0x34,0x00,0x00,0x00,0xc8,0x00,0x00,0x00,0x14,0x01,0x00,0x00,0x48,0x01,0x00,0x00, + 0xe4,0x01,0x00,0x00,0x52,0x44,0x45,0x46,0x8c,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x1c,0x00,0x00,0x00,0x00,0x04,0xff,0xff, + 0x10,0x81,0x00,0x00,0x64,0x00,0x00,0x00,0x5c,0x00,0x00,0x00,0x03,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x60,0x00,0x00,0x00,0x02,0x00,0x00,0x00, + 0x05,0x00,0x00,0x00,0x04,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00, + 0x01,0x00,0x00,0x00,0x0d,0x00,0x00,0x00,0x73,0x6d,0x70,0x00,0x74,0x65,0x78,0x00, + 0x4d,0x69,0x63,0x72,0x6f,0x73,0x6f,0x66,0x74,0x20,0x28,0x52,0x29,0x20,0x48,0x4c, + 0x53,0x4c,0x20,0x53,0x68,0x61,0x64,0x65,0x72,0x20,0x43,0x6f,0x6d,0x70,0x69,0x6c, + 0x65,0x72,0x20,0x31,0x30,0x2e,0x31,0x00,0x49,0x53,0x47,0x4e,0x44,0x00,0x00,0x00, + 0x02,0x00,0x00,0x00,0x08,0x00,0x00,0x00,0x38,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0f,0x03,0x00,0x00, + 0x38,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00, + 0x01,0x00,0x00,0x00,0x0f,0x0f,0x00,0x00,0x54,0x45,0x58,0x43,0x4f,0x4f,0x52,0x44, + 0x00,0xab,0xab,0xab,0x4f,0x53,0x47,0x4e,0x2c,0x00,0x00,0x00,0x01,0x00,0x00,0x00, + 0x08,0x00,0x00,0x00,0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0f,0x00,0x00,0x00,0x53,0x56,0x5f,0x54, + 0x61,0x72,0x67,0x65,0x74,0x00,0xab,0xab,0x53,0x48,0x44,0x52,0x94,0x00,0x00,0x00, + 0x40,0x00,0x00,0x00,0x25,0x00,0x00,0x00,0x5a,0x00,0x00,0x03,0x00,0x60,0x10,0x00, + 0x00,0x00,0x00,0x00,0x58,0x18,0x00,0x04,0x00,0x70,0x10,0x00,0x00,0x00,0x00,0x00, + 0x55,0x55,0x00,0x00,0x62,0x10,0x00,0x03,0x32,0x10,0x10,0x00,0x00,0x00,0x00,0x00, + 0x62,0x10,0x00,0x03,0xf2,0x10,0x10,0x00,0x01,0x00,0x00,0x00,0x65,0x00,0x00,0x03, + 0xf2,0x20,0x10,0x00,0x00,0x00,0x00,0x00,0x68,0x00,0x00,0x02,0x01,0x00,0x00,0x00, + 0x45,0x00,0x00,0x09,0xf2,0x00,0x10,0x00,0x00,0x00,0x00,0x00,0x46,0x10,0x10,0x00, + 0x00,0x00,0x00,0x00,0x46,0x7e,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x60,0x10,0x00, + 0x00,0x00,0x00,0x00,0x38,0x00,0x00,0x07,0xf2,0x20,0x10,0x00,0x00,0x00,0x00,0x00, + 0x46,0x0e,0x10,0x00,0x00,0x00,0x00,0x00,0x46,0x1e,0x10,0x00,0x01,0x00,0x00,0x00, + 0x3e,0x00,0x00,0x01,0x53,0x54,0x41,0x54,0x74,0x00,0x00,0x00,0x03,0x00,0x00,0x00, + 0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x01,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +}; +#elif defined(SOKOL_WGPU) +/* + diagnostic(off, derivative_uniformity); + + struct vs_params { + /_ @offset(0) _/ + mvp : mat4x4f, + /_ @offset(64) _/ + tm : mat4x4f, + } + + @group(0) @binding(0) var x_19 : vs_params; + + var position_1 : vec4f; + + var uv : vec4f; + + var texcoord0 : vec2f; + + var color : vec4f; + + var color0 : vec4f; + + var psize : f32; + + var gl_Position : vec4f; + + fn main_1() { + let x_22 : mat4x4f = x_19.mvp; + let x_25 : vec4f = position_1; + gl_Position = (x_22 * x_25); + let x_32 : mat4x4f = x_19.tm; + let x_36 : vec2f = texcoord0; + uv = (x_32 * vec4f(x_36.x, x_36.y, 0.0f, 1.0f)); + let x_45 : vec4f = color0; + color = x_45; + return; + } + + struct main_out { + @builtin(position) + gl_Position : vec4f, + @location(0) + uv_1 : vec4f, + @location(1) + color_1 : vec4f, + } + + @vertex + fn main(@location(0) position_1_param : vec4f, @location(1) texcoord0_param : vec2f, @location(2) color0_param : vec4f, @location(3) psize_param : f32) -> main_out { + position_1 = position_1_param; + texcoord0 = texcoord0_param; + color0 = color0_param; + psize = psize_param; + main_1(); + return main_out(gl_Position, uv, color); + } +*/ +static const uint8_t _sgl_vs_source_wgsl[1162] = { + 0x64,0x69,0x61,0x67,0x6e,0x6f,0x73,0x74,0x69,0x63,0x28,0x6f,0x66,0x66,0x2c,0x20, + 0x64,0x65,0x72,0x69,0x76,0x61,0x74,0x69,0x76,0x65,0x5f,0x75,0x6e,0x69,0x66,0x6f, + 0x72,0x6d,0x69,0x74,0x79,0x29,0x3b,0x0a,0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20, + 0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x20,0x7b,0x0a,0x20,0x20,0x2f,0x2a, + 0x20,0x40,0x6f,0x66,0x66,0x73,0x65,0x74,0x28,0x30,0x29,0x20,0x2a,0x2f,0x0a,0x20, + 0x20,0x6d,0x76,0x70,0x20,0x3a,0x20,0x6d,0x61,0x74,0x34,0x78,0x34,0x66,0x2c,0x0a, + 0x20,0x20,0x2f,0x2a,0x20,0x40,0x6f,0x66,0x66,0x73,0x65,0x74,0x28,0x36,0x34,0x29, + 0x20,0x2a,0x2f,0x0a,0x20,0x20,0x74,0x6d,0x20,0x3a,0x20,0x6d,0x61,0x74,0x34,0x78, + 0x34,0x66,0x2c,0x0a,0x7d,0x0a,0x0a,0x40,0x67,0x72,0x6f,0x75,0x70,0x28,0x30,0x29, + 0x20,0x40,0x62,0x69,0x6e,0x64,0x69,0x6e,0x67,0x28,0x30,0x29,0x20,0x76,0x61,0x72, + 0x3c,0x75,0x6e,0x69,0x66,0x6f,0x72,0x6d,0x3e,0x20,0x78,0x5f,0x31,0x39,0x20,0x3a, + 0x20,0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x3b,0x0a,0x0a,0x76,0x61,0x72, + 0x3c,0x70,0x72,0x69,0x76,0x61,0x74,0x65,0x3e,0x20,0x70,0x6f,0x73,0x69,0x74,0x69, + 0x6f,0x6e,0x5f,0x31,0x20,0x3a,0x20,0x76,0x65,0x63,0x34,0x66,0x3b,0x0a,0x0a,0x76, + 0x61,0x72,0x3c,0x70,0x72,0x69,0x76,0x61,0x74,0x65,0x3e,0x20,0x75,0x76,0x20,0x3a, + 0x20,0x76,0x65,0x63,0x34,0x66,0x3b,0x0a,0x0a,0x76,0x61,0x72,0x3c,0x70,0x72,0x69, + 0x76,0x61,0x74,0x65,0x3e,0x20,0x74,0x65,0x78,0x63,0x6f,0x6f,0x72,0x64,0x30,0x20, + 0x3a,0x20,0x76,0x65,0x63,0x32,0x66,0x3b,0x0a,0x0a,0x76,0x61,0x72,0x3c,0x70,0x72, + 0x69,0x76,0x61,0x74,0x65,0x3e,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x20,0x3a,0x20,0x76, + 0x65,0x63,0x34,0x66,0x3b,0x0a,0x0a,0x76,0x61,0x72,0x3c,0x70,0x72,0x69,0x76,0x61, + 0x74,0x65,0x3e,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x30,0x20,0x3a,0x20,0x76,0x65,0x63, + 0x34,0x66,0x3b,0x0a,0x0a,0x76,0x61,0x72,0x3c,0x70,0x72,0x69,0x76,0x61,0x74,0x65, + 0x3e,0x20,0x70,0x73,0x69,0x7a,0x65,0x20,0x3a,0x20,0x66,0x33,0x32,0x3b,0x0a,0x0a, + 0x76,0x61,0x72,0x3c,0x70,0x72,0x69,0x76,0x61,0x74,0x65,0x3e,0x20,0x67,0x6c,0x5f, + 0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x20,0x3a,0x20,0x76,0x65,0x63,0x34,0x66, + 0x3b,0x0a,0x0a,0x66,0x6e,0x20,0x6d,0x61,0x69,0x6e,0x5f,0x31,0x28,0x29,0x20,0x7b, + 0x0a,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x32,0x32,0x20,0x3a,0x20,0x6d,0x61, + 0x74,0x34,0x78,0x34,0x66,0x20,0x3d,0x20,0x78,0x5f,0x31,0x39,0x2e,0x6d,0x76,0x70, + 0x3b,0x0a,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x32,0x35,0x20,0x3a,0x20,0x76, + 0x65,0x63,0x34,0x66,0x20,0x3d,0x20,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x5f, + 0x31,0x3b,0x0a,0x20,0x20,0x67,0x6c,0x5f,0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e, + 0x20,0x3d,0x20,0x28,0x78,0x5f,0x32,0x32,0x20,0x2a,0x20,0x78,0x5f,0x32,0x35,0x29, + 0x3b,0x0a,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x33,0x32,0x20,0x3a,0x20,0x6d, + 0x61,0x74,0x34,0x78,0x34,0x66,0x20,0x3d,0x20,0x78,0x5f,0x31,0x39,0x2e,0x74,0x6d, + 0x3b,0x0a,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x33,0x36,0x20,0x3a,0x20,0x76, + 0x65,0x63,0x32,0x66,0x20,0x3d,0x20,0x74,0x65,0x78,0x63,0x6f,0x6f,0x72,0x64,0x30, + 0x3b,0x0a,0x20,0x20,0x75,0x76,0x20,0x3d,0x20,0x28,0x78,0x5f,0x33,0x32,0x20,0x2a, + 0x20,0x76,0x65,0x63,0x34,0x66,0x28,0x78,0x5f,0x33,0x36,0x2e,0x78,0x2c,0x20,0x78, + 0x5f,0x33,0x36,0x2e,0x79,0x2c,0x20,0x30,0x2e,0x30,0x66,0x2c,0x20,0x31,0x2e,0x30, + 0x66,0x29,0x29,0x3b,0x0a,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x34,0x35,0x20, + 0x3a,0x20,0x76,0x65,0x63,0x34,0x66,0x20,0x3d,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x30, + 0x3b,0x0a,0x20,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20,0x78,0x5f,0x34,0x35, + 0x3b,0x0a,0x20,0x20,0x72,0x65,0x74,0x75,0x72,0x6e,0x3b,0x0a,0x7d,0x0a,0x0a,0x73, + 0x74,0x72,0x75,0x63,0x74,0x20,0x6d,0x61,0x69,0x6e,0x5f,0x6f,0x75,0x74,0x20,0x7b, + 0x0a,0x20,0x20,0x40,0x62,0x75,0x69,0x6c,0x74,0x69,0x6e,0x28,0x70,0x6f,0x73,0x69, + 0x74,0x69,0x6f,0x6e,0x29,0x0a,0x20,0x20,0x67,0x6c,0x5f,0x50,0x6f,0x73,0x69,0x74, + 0x69,0x6f,0x6e,0x20,0x3a,0x20,0x76,0x65,0x63,0x34,0x66,0x2c,0x0a,0x20,0x20,0x40, + 0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x28,0x30,0x29,0x0a,0x20,0x20,0x75,0x76, + 0x5f,0x31,0x20,0x3a,0x20,0x76,0x65,0x63,0x34,0x66,0x2c,0x0a,0x20,0x20,0x40,0x6c, + 0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x28,0x31,0x29,0x0a,0x20,0x20,0x63,0x6f,0x6c, + 0x6f,0x72,0x5f,0x31,0x20,0x3a,0x20,0x76,0x65,0x63,0x34,0x66,0x2c,0x0a,0x7d,0x0a, + 0x0a,0x40,0x76,0x65,0x72,0x74,0x65,0x78,0x0a,0x66,0x6e,0x20,0x6d,0x61,0x69,0x6e, + 0x28,0x40,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x28,0x30,0x29,0x20,0x70,0x6f, + 0x73,0x69,0x74,0x69,0x6f,0x6e,0x5f,0x31,0x5f,0x70,0x61,0x72,0x61,0x6d,0x20,0x3a, + 0x20,0x76,0x65,0x63,0x34,0x66,0x2c,0x20,0x40,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f, + 0x6e,0x28,0x31,0x29,0x20,0x74,0x65,0x78,0x63,0x6f,0x6f,0x72,0x64,0x30,0x5f,0x70, + 0x61,0x72,0x61,0x6d,0x20,0x3a,0x20,0x76,0x65,0x63,0x32,0x66,0x2c,0x20,0x40,0x6c, + 0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x28,0x32,0x29,0x20,0x63,0x6f,0x6c,0x6f,0x72, + 0x30,0x5f,0x70,0x61,0x72,0x61,0x6d,0x20,0x3a,0x20,0x76,0x65,0x63,0x34,0x66,0x2c, + 0x20,0x40,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x28,0x33,0x29,0x20,0x70,0x73, + 0x69,0x7a,0x65,0x5f,0x70,0x61,0x72,0x61,0x6d,0x20,0x3a,0x20,0x66,0x33,0x32,0x29, + 0x20,0x2d,0x3e,0x20,0x6d,0x61,0x69,0x6e,0x5f,0x6f,0x75,0x74,0x20,0x7b,0x0a,0x20, + 0x20,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x5f,0x31,0x20,0x3d,0x20,0x70,0x6f, + 0x73,0x69,0x74,0x69,0x6f,0x6e,0x5f,0x31,0x5f,0x70,0x61,0x72,0x61,0x6d,0x3b,0x0a, + 0x20,0x20,0x74,0x65,0x78,0x63,0x6f,0x6f,0x72,0x64,0x30,0x20,0x3d,0x20,0x74,0x65, + 0x78,0x63,0x6f,0x6f,0x72,0x64,0x30,0x5f,0x70,0x61,0x72,0x61,0x6d,0x3b,0x0a,0x20, + 0x20,0x63,0x6f,0x6c,0x6f,0x72,0x30,0x20,0x3d,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x30, + 0x5f,0x70,0x61,0x72,0x61,0x6d,0x3b,0x0a,0x20,0x20,0x70,0x73,0x69,0x7a,0x65,0x20, + 0x3d,0x20,0x70,0x73,0x69,0x7a,0x65,0x5f,0x70,0x61,0x72,0x61,0x6d,0x3b,0x0a,0x20, + 0x20,0x6d,0x61,0x69,0x6e,0x5f,0x31,0x28,0x29,0x3b,0x0a,0x20,0x20,0x72,0x65,0x74, + 0x75,0x72,0x6e,0x20,0x6d,0x61,0x69,0x6e,0x5f,0x6f,0x75,0x74,0x28,0x67,0x6c,0x5f, + 0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x2c,0x20,0x75,0x76,0x2c,0x20,0x63,0x6f, + 0x6c,0x6f,0x72,0x29,0x3b,0x0a,0x7d,0x0a,0x0a,0x00, +}; +/* + diagnostic(off, derivative_uniformity); + + var frag_color : vec4f; + + @group(1) @binding(64) var tex : texture_2d; + + @group(1) @binding(80) var smp : sampler; + + var uv : vec4f; + + var color : vec4f; + + fn main_1() { + let x_23 : vec4f = uv; + let x_25 : vec4f = textureSample(tex, smp, vec2f(x_23.x, x_23.y)); + let x_27 : vec4f = color; + frag_color = (x_25 * x_27); + return; + } + + struct main_out { + @location(0) + frag_color_1 : vec4f, + } + + @fragment + fn main(@location(0) uv_param : vec4f, @location(1) color_param : vec4f) -> main_out { + uv = uv_param; + color = color_param; + main_1(); + return main_out(frag_color); + } +*/ +static const uint8_t _sgl_fs_source_wgsl[647] = { + 0x64,0x69,0x61,0x67,0x6e,0x6f,0x73,0x74,0x69,0x63,0x28,0x6f,0x66,0x66,0x2c,0x20, + 0x64,0x65,0x72,0x69,0x76,0x61,0x74,0x69,0x76,0x65,0x5f,0x75,0x6e,0x69,0x66,0x6f, + 0x72,0x6d,0x69,0x74,0x79,0x29,0x3b,0x0a,0x0a,0x76,0x61,0x72,0x3c,0x70,0x72,0x69, + 0x76,0x61,0x74,0x65,0x3e,0x20,0x66,0x72,0x61,0x67,0x5f,0x63,0x6f,0x6c,0x6f,0x72, + 0x20,0x3a,0x20,0x76,0x65,0x63,0x34,0x66,0x3b,0x0a,0x0a,0x40,0x67,0x72,0x6f,0x75, + 0x70,0x28,0x31,0x29,0x20,0x40,0x62,0x69,0x6e,0x64,0x69,0x6e,0x67,0x28,0x36,0x34, + 0x29,0x20,0x76,0x61,0x72,0x20,0x74,0x65,0x78,0x20,0x3a,0x20,0x74,0x65,0x78,0x74, + 0x75,0x72,0x65,0x5f,0x32,0x64,0x3c,0x66,0x33,0x32,0x3e,0x3b,0x0a,0x0a,0x40,0x67, + 0x72,0x6f,0x75,0x70,0x28,0x31,0x29,0x20,0x40,0x62,0x69,0x6e,0x64,0x69,0x6e,0x67, + 0x28,0x38,0x30,0x29,0x20,0x76,0x61,0x72,0x20,0x73,0x6d,0x70,0x20,0x3a,0x20,0x73, + 0x61,0x6d,0x70,0x6c,0x65,0x72,0x3b,0x0a,0x0a,0x76,0x61,0x72,0x3c,0x70,0x72,0x69, + 0x76,0x61,0x74,0x65,0x3e,0x20,0x75,0x76,0x20,0x3a,0x20,0x76,0x65,0x63,0x34,0x66, + 0x3b,0x0a,0x0a,0x76,0x61,0x72,0x3c,0x70,0x72,0x69,0x76,0x61,0x74,0x65,0x3e,0x20, + 0x63,0x6f,0x6c,0x6f,0x72,0x20,0x3a,0x20,0x76,0x65,0x63,0x34,0x66,0x3b,0x0a,0x0a, + 0x66,0x6e,0x20,0x6d,0x61,0x69,0x6e,0x5f,0x31,0x28,0x29,0x20,0x7b,0x0a,0x20,0x20, + 0x6c,0x65,0x74,0x20,0x78,0x5f,0x32,0x33,0x20,0x3a,0x20,0x76,0x65,0x63,0x34,0x66, + 0x20,0x3d,0x20,0x75,0x76,0x3b,0x0a,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x32, + 0x35,0x20,0x3a,0x20,0x76,0x65,0x63,0x34,0x66,0x20,0x3d,0x20,0x74,0x65,0x78,0x74, + 0x75,0x72,0x65,0x53,0x61,0x6d,0x70,0x6c,0x65,0x28,0x74,0x65,0x78,0x2c,0x20,0x73, + 0x6d,0x70,0x2c,0x20,0x76,0x65,0x63,0x32,0x66,0x28,0x78,0x5f,0x32,0x33,0x2e,0x78, + 0x2c,0x20,0x78,0x5f,0x32,0x33,0x2e,0x79,0x29,0x29,0x3b,0x0a,0x20,0x20,0x6c,0x65, + 0x74,0x20,0x78,0x5f,0x32,0x37,0x20,0x3a,0x20,0x76,0x65,0x63,0x34,0x66,0x20,0x3d, + 0x20,0x63,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x20,0x20,0x66,0x72,0x61,0x67,0x5f,0x63, + 0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20,0x28,0x78,0x5f,0x32,0x35,0x20,0x2a,0x20,0x78, + 0x5f,0x32,0x37,0x29,0x3b,0x0a,0x20,0x20,0x72,0x65,0x74,0x75,0x72,0x6e,0x3b,0x0a, + 0x7d,0x0a,0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x6d,0x61,0x69,0x6e,0x5f,0x6f, + 0x75,0x74,0x20,0x7b,0x0a,0x20,0x20,0x40,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e, + 0x28,0x30,0x29,0x0a,0x20,0x20,0x66,0x72,0x61,0x67,0x5f,0x63,0x6f,0x6c,0x6f,0x72, + 0x5f,0x31,0x20,0x3a,0x20,0x76,0x65,0x63,0x34,0x66,0x2c,0x0a,0x7d,0x0a,0x0a,0x40, + 0x66,0x72,0x61,0x67,0x6d,0x65,0x6e,0x74,0x0a,0x66,0x6e,0x20,0x6d,0x61,0x69,0x6e, + 0x28,0x40,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x28,0x30,0x29,0x20,0x75,0x76, + 0x5f,0x70,0x61,0x72,0x61,0x6d,0x20,0x3a,0x20,0x76,0x65,0x63,0x34,0x66,0x2c,0x20, + 0x40,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x28,0x31,0x29,0x20,0x63,0x6f,0x6c, + 0x6f,0x72,0x5f,0x70,0x61,0x72,0x61,0x6d,0x20,0x3a,0x20,0x76,0x65,0x63,0x34,0x66, + 0x29,0x20,0x2d,0x3e,0x20,0x6d,0x61,0x69,0x6e,0x5f,0x6f,0x75,0x74,0x20,0x7b,0x0a, + 0x20,0x20,0x75,0x76,0x20,0x3d,0x20,0x75,0x76,0x5f,0x70,0x61,0x72,0x61,0x6d,0x3b, + 0x0a,0x20,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20,0x63,0x6f,0x6c,0x6f,0x72, + 0x5f,0x70,0x61,0x72,0x61,0x6d,0x3b,0x0a,0x20,0x20,0x6d,0x61,0x69,0x6e,0x5f,0x31, + 0x28,0x29,0x3b,0x0a,0x20,0x20,0x72,0x65,0x74,0x75,0x72,0x6e,0x20,0x6d,0x61,0x69, + 0x6e,0x5f,0x6f,0x75,0x74,0x28,0x66,0x72,0x61,0x67,0x5f,0x63,0x6f,0x6c,0x6f,0x72, + 0x29,0x3b,0x0a,0x7d,0x0a,0x0a,0x00, +}; +#elif defined(SOKOL_DUMMY_BACKEND) +static const char* _sgl_vs_source_dummy = ""; +static const char* _sgl_fs_source_dummy = ""; +#else +#error "Please define one of SOKOL_GLCORE, SOKOL_GLES3, SOKOL_D3D11, SOKOL_METAL, SOKOL_WGPU or SOKOL_DUMMY_BACKEND!" +#endif + +// ████████ ██ ██ ██████ ███████ ███████ +// ██ ██ ██ ██ ██ ██ ██ +// ██ ████ ██████ █████ ███████ +// ██ ██ ██ ██ ██ +// ██ ██ ██ ███████ ███████ +// +// >>types +typedef enum { + SGL_PRIMITIVETYPE_POINTS = 0, + SGL_PRIMITIVETYPE_LINES, + SGL_PRIMITIVETYPE_LINE_STRIP, + SGL_PRIMITIVETYPE_TRIANGLES, + SGL_PRIMITIVETYPE_TRIANGLE_STRIP, + SGL_PRIMITIVETYPE_QUADS, + SGL_NUM_PRIMITIVE_TYPES, +} _sgl_primitive_type_t; + +typedef struct { + uint32_t id; + sg_resource_state state; +} _sgl_slot_t; + +typedef struct { + int size; + int queue_top; + uint32_t* gen_ctrs; + int* free_queue; +} _sgl_pool_t; + +typedef struct { + _sgl_slot_t slot; + sg_pipeline pip[SGL_NUM_PRIMITIVE_TYPES]; +} _sgl_pipeline_t; + +typedef struct { + _sgl_pool_t pool; + _sgl_pipeline_t* pips; +} _sgl_pipeline_pool_t; + +typedef enum { + SGL_MATRIXMODE_MODELVIEW, + SGL_MATRIXMODE_PROJECTION, + SGL_MATRIXMODE_TEXTURE, + SGL_NUM_MATRIXMODES +} _sgl_matrix_mode_t; + +typedef struct { + float pos[3]; + float uv[2]; + uint32_t rgba; + float psize; +} _sgl_vertex_t; + +typedef struct { + float v[4][4]; +} _sgl_matrix_t; + +typedef struct { + _sgl_matrix_t mvp; /* model-view-projection matrix */ + _sgl_matrix_t tm; /* texture matrix */ +} _sgl_uniform_t; + +typedef enum { + SGL_COMMAND_DRAW, + SGL_COMMAND_VIEWPORT, + SGL_COMMAND_SCISSOR_RECT, +} _sgl_command_type_t; + +typedef struct { + sg_pipeline pip; + sg_view view; + sg_sampler smp; + int base_vertex; + int num_vertices; + int uniform_index; +} _sgl_draw_args_t; + +typedef struct { + int x, y, w, h; + bool origin_top_left; +} _sgl_viewport_args_t; + +typedef struct { + int x, y, w, h; + bool origin_top_left; +} _sgl_scissor_rect_args_t; + +typedef union { + _sgl_draw_args_t draw; + _sgl_viewport_args_t viewport; + _sgl_scissor_rect_args_t scissor_rect; +} _sgl_args_t; + +typedef struct { + _sgl_command_type_t cmd; + int layer_id; + _sgl_args_t args; +} _sgl_command_t; + +#define _SGL_INVALID_SLOT_INDEX (0) +#define _SGL_MAX_STACK_DEPTH (64) +#define _SGL_DEFAULT_CONTEXT_POOL_SIZE (4) +#define _SGL_DEFAULT_PIPELINE_POOL_SIZE (64) +#define _SGL_DEFAULT_MAX_VERTICES (1<<16) +#define _SGL_DEFAULT_MAX_COMMANDS (1<<14) +#define _SGL_SLOT_SHIFT (16) +#define _SGL_MAX_POOL_SIZE (1<<_SGL_SLOT_SHIFT) +#define _SGL_SLOT_MASK (_SGL_MAX_POOL_SIZE-1) + +typedef struct { + _sgl_slot_t slot; + sgl_context_desc_t desc; + uint32_t frame_id; + uint32_t update_frame_id; + struct { + int cap; + int next; + _sgl_vertex_t* ptr; + } vertices; + struct { + int cap; + int next; + _sgl_uniform_t* ptr; + } uniforms; + struct { + int cap; + int next; + _sgl_command_t* ptr; + } commands; + + /* state tracking */ + int base_vertex; + int quad_vtx_count; /* number of times vtx function has been called, used for non-triangle primitives */ + sgl_error_t error; + bool in_begin; + int layer_id; + float u, v; + uint32_t rgba; + float point_size; + _sgl_primitive_type_t cur_prim_type; + sg_view cur_view; + sg_sampler cur_smp; + bool texturing_enabled; + bool matrix_dirty; /* reset in sgl_end(), set in any of the matrix stack functions */ + + /* sokol-gfx resources */ + sg_buffer vbuf; + sgl_pipeline def_pip; + sg_bindings bind; + + /* pipeline stack */ + int pip_tos; + sgl_pipeline pip_stack[_SGL_MAX_STACK_DEPTH]; + + /* matrix stacks */ + _sgl_matrix_mode_t cur_matrix_mode; + int matrix_tos[SGL_NUM_MATRIXMODES]; + _sgl_matrix_t matrix_stack[SGL_NUM_MATRIXMODES][_SGL_MAX_STACK_DEPTH]; +} _sgl_context_t; + +typedef struct { + _sgl_pool_t pool; + _sgl_context_t* contexts; +} _sgl_context_pool_t; + +typedef struct { + uint32_t init_cookie; + sgl_desc_t desc; + sg_image def_img; // a default white texture + sg_view def_view; // ...and the texture view for the default image + sg_sampler def_smp; // a default sampler + sg_shader shd; // same shader for all contexts + sgl_context def_ctx_id; + sgl_context cur_ctx_id; + _sgl_context_t* cur_ctx; // may be 0! + _sgl_pipeline_pool_t pip_pool; + _sgl_context_pool_t context_pool; +} _sgl_t; +static _sgl_t _sgl; + +// ██ ██████ ██████ ██████ ██ ███ ██ ██████ +// ██ ██ ██ ██ ██ ██ ████ ██ ██ +// ██ ██ ██ ██ ███ ██ ███ ██ ██ ██ ██ ██ ███ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ███████ ██████ ██████ ██████ ██ ██ ████ ██████ +// +// >>logging +#if defined(SOKOL_DEBUG) +#define _SGL_LOGITEM_XMACRO(item,msg) #item ": " msg, +static const char* _sgl_log_messages[] = { + _SGL_LOG_ITEMS +}; +#undef _SGL_LOGITEM_XMACRO +#endif // SOKOL_DEBUG + +#define _SGL_PANIC(code) _sgl_log(SGL_LOGITEM_ ##code, 0, __LINE__) +#define _SGL_ERROR(code) _sgl_log(SGL_LOGITEM_ ##code, 1, __LINE__) +#define _SGL_WARN(code) _sgl_log(SGL_LOGITEM_ ##code, 2, __LINE__) +#define _SGL_INFO(code) _sgl_log(SGL_LOGITEM_ ##code, 3, __LINE__) + +static void _sgl_log(sgl_log_item_t log_item, uint32_t log_level, uint32_t line_nr) { + if (_sgl.desc.logger.func) { + #if defined(SOKOL_DEBUG) + const char* filename = __FILE__; + const char* message = _sgl_log_messages[log_item]; + #else + const char* filename = 0; + const char* message = 0; + #endif + _sgl.desc.logger.func("sgl", log_level, (uint32_t)log_item, message, line_nr, filename, _sgl.desc.logger.user_data); + } else { + // for log level PANIC it would be 'undefined behaviour' to continue + if (log_level == 0) { + abort(); + } + } +} + +// ███ ███ ███████ ███ ███ ██████ ██████ ██ ██ +// ████ ████ ██ ████ ████ ██ ██ ██ ██ ██ ██ +// ██ ████ ██ █████ ██ ████ ██ ██ ██ ██████ ████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██ ██ ███████ ██ ██ ██████ ██ ██ ██ +// +// >>memory +static void _sgl_clear(void* ptr, size_t size) { + SOKOL_ASSERT(ptr && (size > 0)); + memset(ptr, 0, size); +} + +static void* _sgl_malloc(size_t size) { + SOKOL_ASSERT(size > 0); + void* ptr; + if (_sgl.desc.allocator.alloc_fn) { + ptr = _sgl.desc.allocator.alloc_fn(size, _sgl.desc.allocator.user_data); + } else { + ptr = malloc(size); + } + if (0 == ptr) { + _SGL_PANIC(MALLOC_FAILED); + } + return ptr; +} + +static void* _sgl_malloc_clear(size_t size) { + void* ptr = _sgl_malloc(size); + _sgl_clear(ptr, size); + return ptr; +} + +static void _sgl_free(void* ptr) { + if (_sgl.desc.allocator.free_fn) { + _sgl.desc.allocator.free_fn(ptr, _sgl.desc.allocator.user_data); + } else { + free(ptr); + } +} + +// ██████ ██████ ██████ ██ +// ██ ██ ██ ██ ██ ██ ██ +// ██████ ██ ██ ██ ██ ██ +// ██ ██ ██ ██ ██ ██ +// ██ ██████ ██████ ███████ +// +// >>pool +static void _sgl_init_pool(_sgl_pool_t* pool, int num) { + SOKOL_ASSERT(pool && (num >= 1)); + /* slot 0 is reserved for the 'invalid id', so bump the pool size by 1 */ + pool->size = num + 1; + pool->queue_top = 0; + /* generation counters indexable by pool slot index, slot 0 is reserved */ + size_t gen_ctrs_size = sizeof(uint32_t) * (size_t)pool->size; + pool->gen_ctrs = (uint32_t*) _sgl_malloc_clear(gen_ctrs_size); + /* it's not a bug to only reserve 'num' here */ + pool->free_queue = (int*) _sgl_malloc_clear(sizeof(int) * (size_t)num); + /* never allocate the zero-th pool item since the invalid id is 0 */ + for (int i = pool->size-1; i >= 1; i--) { + pool->free_queue[pool->queue_top++] = i; + } +} + +static void _sgl_discard_pool(_sgl_pool_t* pool) { + SOKOL_ASSERT(pool); + SOKOL_ASSERT(pool->free_queue); + _sgl_free(pool->free_queue); + pool->free_queue = 0; + SOKOL_ASSERT(pool->gen_ctrs); + _sgl_free(pool->gen_ctrs); + pool->gen_ctrs = 0; + pool->size = 0; + pool->queue_top = 0; +} + +static int _sgl_pool_alloc_index(_sgl_pool_t* pool) { + SOKOL_ASSERT(pool); + SOKOL_ASSERT(pool->free_queue); + if (pool->queue_top > 0) { + int slot_index = pool->free_queue[--pool->queue_top]; + SOKOL_ASSERT((slot_index > 0) && (slot_index < pool->size)); + return slot_index; + } else { + // pool exhausted + return _SGL_INVALID_SLOT_INDEX; + } +} + +static void _sgl_pool_free_index(_sgl_pool_t* pool, int slot_index) { + SOKOL_ASSERT((slot_index > _SGL_INVALID_SLOT_INDEX) && (slot_index < pool->size)); + SOKOL_ASSERT(pool); + SOKOL_ASSERT(pool->free_queue); + SOKOL_ASSERT(pool->queue_top < pool->size); + #ifdef SOKOL_DEBUG + /* debug check against double-free */ + for (int i = 0; i < pool->queue_top; i++) { + SOKOL_ASSERT(pool->free_queue[i] != slot_index); + } + #endif + pool->free_queue[pool->queue_top++] = slot_index; + SOKOL_ASSERT(pool->queue_top <= (pool->size-1)); +} + +/* allocate the slot at slot_index: + - bump the slot's generation counter + - create a resource id from the generation counter and slot index + - set the slot's id to this id + - set the slot's state to ALLOC + - return the resource id +*/ +static uint32_t _sgl_slot_alloc(_sgl_pool_t* pool, _sgl_slot_t* slot, int slot_index) { + /* FIXME: add handling for an overflowing generation counter, + for now, just overflow (another option is to disable + the slot) + */ + SOKOL_ASSERT(pool && pool->gen_ctrs); + SOKOL_ASSERT((slot_index > _SGL_INVALID_SLOT_INDEX) && (slot_index < pool->size)); + SOKOL_ASSERT((slot->state == SG_RESOURCESTATE_INITIAL) && (slot->id == SG_INVALID_ID)); + uint32_t ctr = ++pool->gen_ctrs[slot_index]; + slot->id = (ctr<<_SGL_SLOT_SHIFT)|(slot_index & _SGL_SLOT_MASK); + slot->state = SG_RESOURCESTATE_ALLOC; + return slot->id; +} + +/* extract slot index from id */ +static int _sgl_slot_index(uint32_t id) { + int slot_index = (int) (id & _SGL_SLOT_MASK); + SOKOL_ASSERT(_SGL_INVALID_SLOT_INDEX != slot_index); + return slot_index; +} + +// ██████ ██ ██████ ███████ ██ ██ ███ ██ ███████ ███████ +// ██ ██ ██ ██ ██ ██ ██ ██ ████ ██ ██ ██ +// ██████ ██ ██████ █████ ██ ██ ██ ██ ██ █████ ███████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██ ██ ██ ███████ ███████ ██ ██ ████ ███████ ███████ +// +// >>pipelines +static void _sgl_reset_pipeline(_sgl_pipeline_t* pip) { + SOKOL_ASSERT(pip); + _sgl_clear(pip, sizeof(_sgl_pipeline_t)); +} + +static void _sgl_setup_pipeline_pool(int pool_size) { + /* note: the pools here will have an additional item, since slot 0 is reserved */ + SOKOL_ASSERT((pool_size > 0) && (pool_size < _SGL_MAX_POOL_SIZE)); + _sgl_init_pool(&_sgl.pip_pool.pool, pool_size); + size_t pool_byte_size = sizeof(_sgl_pipeline_t) * (size_t)_sgl.pip_pool.pool.size; + _sgl.pip_pool.pips = (_sgl_pipeline_t*) _sgl_malloc_clear(pool_byte_size); +} + +static void _sgl_discard_pipeline_pool(void) { + SOKOL_ASSERT(0 != _sgl.pip_pool.pips); + _sgl_free(_sgl.pip_pool.pips); _sgl.pip_pool.pips = 0; + _sgl_discard_pool(&_sgl.pip_pool.pool); +} + +/* get pipeline pointer without id-check */ +static _sgl_pipeline_t* _sgl_pipeline_at(uint32_t pip_id) { + SOKOL_ASSERT(SG_INVALID_ID != pip_id); + int slot_index = _sgl_slot_index(pip_id); + SOKOL_ASSERT((slot_index > _SGL_INVALID_SLOT_INDEX) && (slot_index < _sgl.pip_pool.pool.size)); + return &_sgl.pip_pool.pips[slot_index]; +} + +/* get pipeline pointer with id-check, returns 0 if no match */ +static _sgl_pipeline_t* _sgl_lookup_pipeline(uint32_t pip_id) { + if (SG_INVALID_ID != pip_id) { + _sgl_pipeline_t* pip = _sgl_pipeline_at(pip_id); + if (pip->slot.id == pip_id) { + return pip; + } + } + return 0; +} + +/* make pipeline id from uint32_t id */ +static sgl_pipeline _sgl_make_pip_id(uint32_t pip_id) { + sgl_pipeline pip = { pip_id }; + return pip; +} + +static sgl_pipeline _sgl_alloc_pipeline(void) { + sgl_pipeline res; + int slot_index = _sgl_pool_alloc_index(&_sgl.pip_pool.pool); + if (_SGL_INVALID_SLOT_INDEX != slot_index) { + res = _sgl_make_pip_id(_sgl_slot_alloc(&_sgl.pip_pool.pool, &_sgl.pip_pool.pips[slot_index].slot, slot_index)); + } else { + /* pool is exhausted */ + res = _sgl_make_pip_id(SG_INVALID_ID); + } + return res; +} + +static void _sgl_init_pipeline(sgl_pipeline pip_id, const sg_pipeline_desc* in_desc, const sgl_context_desc_t* ctx_desc) { + SOKOL_ASSERT((pip_id.id != SG_INVALID_ID) && in_desc && ctx_desc); + + /* create a new desc with 'patched' shader and pixel format state */ + sg_pipeline_desc desc = *in_desc; + desc.layout.buffers[0].stride = sizeof(_sgl_vertex_t); + { + sg_vertex_attr_state* pos = &desc.layout.attrs[0]; + pos->offset = offsetof(_sgl_vertex_t, pos); + pos->format = SG_VERTEXFORMAT_FLOAT3; + } + { + sg_vertex_attr_state* uv = &desc.layout.attrs[1]; + uv->offset = offsetof(_sgl_vertex_t, uv); + uv->format = SG_VERTEXFORMAT_FLOAT2; + } + { + sg_vertex_attr_state* rgba = &desc.layout.attrs[2]; + rgba->offset = offsetof(_sgl_vertex_t, rgba); + rgba->format = SG_VERTEXFORMAT_UBYTE4N; + } + { + sg_vertex_attr_state* psize = &desc.layout.attrs[3]; + psize->offset = offsetof(_sgl_vertex_t, psize); + psize->format = SG_VERTEXFORMAT_FLOAT; + } + if (in_desc->shader.id == SG_INVALID_ID) { + desc.shader = _sgl.shd; + } + desc.index_type = SG_INDEXTYPE_NONE; + desc.sample_count = ctx_desc->sample_count; + if (desc.face_winding == _SG_FACEWINDING_DEFAULT) { + desc.face_winding = _sgl.desc.face_winding; + } + desc.depth.pixel_format = ctx_desc->depth_format; + if (ctx_desc->depth_format == SG_PIXELFORMAT_NONE) { + desc.depth.write_enabled = false; + } + desc.colors[0].pixel_format = ctx_desc->color_format; + if (desc.colors[0].write_mask == _SG_COLORMASK_DEFAULT) { + desc.colors[0].write_mask = SG_COLORMASK_RGB; + } + + _sgl_pipeline_t* pip = _sgl_lookup_pipeline(pip_id.id); + SOKOL_ASSERT(pip && (pip->slot.state == SG_RESOURCESTATE_ALLOC)); + pip->slot.state = SG_RESOURCESTATE_VALID; + for (int i = 0; i < SGL_NUM_PRIMITIVE_TYPES; i++) { + switch (i) { + case SGL_PRIMITIVETYPE_POINTS: + desc.primitive_type = SG_PRIMITIVETYPE_POINTS; + break; + case SGL_PRIMITIVETYPE_LINES: + desc.primitive_type = SG_PRIMITIVETYPE_LINES; + break; + case SGL_PRIMITIVETYPE_LINE_STRIP: + desc.primitive_type = SG_PRIMITIVETYPE_LINE_STRIP; + break; + case SGL_PRIMITIVETYPE_TRIANGLES: + desc.primitive_type = SG_PRIMITIVETYPE_TRIANGLES; + break; + case SGL_PRIMITIVETYPE_TRIANGLE_STRIP: + case SGL_PRIMITIVETYPE_QUADS: + desc.primitive_type = SG_PRIMITIVETYPE_TRIANGLE_STRIP; + break; + } + if (SGL_PRIMITIVETYPE_QUADS == i) { + /* quads are emulated via triangles, use the same pipeline object */ + pip->pip[i] = pip->pip[SGL_PRIMITIVETYPE_TRIANGLES]; + } else { + pip->pip[i] = sg_make_pipeline(&desc); + if (pip->pip[i].id == SG_INVALID_ID) { + _SGL_ERROR(MAKE_PIPELINE_FAILED); + pip->slot.state = SG_RESOURCESTATE_FAILED; + } + } + } +} + +static sgl_pipeline _sgl_make_pipeline(const sg_pipeline_desc* desc, const sgl_context_desc_t* ctx_desc) { + SOKOL_ASSERT(desc && ctx_desc); + sgl_pipeline pip_id = _sgl_alloc_pipeline(); + if (pip_id.id != SG_INVALID_ID) { + _sgl_init_pipeline(pip_id, desc, ctx_desc); + } else { + _SGL_ERROR(PIPELINE_POOL_EXHAUSTED); + } + return pip_id; +} + +static void _sgl_destroy_pipeline(sgl_pipeline pip_id) { + _sgl_pipeline_t* pip = _sgl_lookup_pipeline(pip_id.id); + if (pip) { + sg_push_debug_group("sokol-gl"); + for (int i = 0; i < SGL_NUM_PRIMITIVE_TYPES; i++) { + if (i != SGL_PRIMITIVETYPE_QUADS) { + sg_destroy_pipeline(pip->pip[i]); + } + } + sg_pop_debug_group(); + _sgl_reset_pipeline(pip); + _sgl_pool_free_index(&_sgl.pip_pool.pool, _sgl_slot_index(pip_id.id)); + } +} + +static sg_pipeline _sgl_get_pipeline(sgl_pipeline pip_id, _sgl_primitive_type_t prim_type) { + _sgl_pipeline_t* pip = _sgl_lookup_pipeline(pip_id.id); + if (pip) { + return pip->pip[prim_type]; + } else { + sg_pipeline dummy_id = { SG_INVALID_ID }; + return dummy_id; + } +} + +// ██████ ██████ ███ ██ ████████ ███████ ██ ██ ████████ ███████ +// ██ ██ ██ ████ ██ ██ ██ ██ ██ ██ ██ +// ██ ██ ██ ██ ██ ██ ██ █████ ███ ██ ███████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██████ ██████ ██ ████ ██ ███████ ██ ██ ██ ███████ +// +// >>contexts +static void _sgl_reset_context(_sgl_context_t* ctx) { + SOKOL_ASSERT(ctx); + SOKOL_ASSERT(0 == ctx->vertices.ptr); + SOKOL_ASSERT(0 == ctx->uniforms.ptr); + SOKOL_ASSERT(0 == ctx->commands.ptr); + _sgl_clear(ctx, sizeof(_sgl_context_t)); +} + +static void _sgl_setup_context_pool(int pool_size) { + /* note: the pools here will have an additional item, since slot 0 is reserved */ + SOKOL_ASSERT((pool_size > 0) && (pool_size < _SGL_MAX_POOL_SIZE)); + _sgl_init_pool(&_sgl.context_pool.pool, pool_size); + size_t pool_byte_size = sizeof(_sgl_context_t) * (size_t)_sgl.context_pool.pool.size; + _sgl.context_pool.contexts = (_sgl_context_t*) _sgl_malloc_clear(pool_byte_size); +} + +static void _sgl_discard_context_pool(void) { + SOKOL_ASSERT(0 != _sgl.context_pool.contexts); + _sgl_free(_sgl.context_pool.contexts); _sgl.context_pool.contexts = 0; + _sgl_discard_pool(&_sgl.context_pool.pool); +} + +// get context pointer without id-check +static _sgl_context_t* _sgl_context_at(uint32_t ctx_id) { + SOKOL_ASSERT(SG_INVALID_ID != ctx_id); + int slot_index = _sgl_slot_index(ctx_id); + SOKOL_ASSERT((slot_index > _SGL_INVALID_SLOT_INDEX) && (slot_index < _sgl.context_pool.pool.size)); + return &_sgl.context_pool.contexts[slot_index]; +} + +// get context pointer with id-check, returns 0 if no match +static _sgl_context_t* _sgl_lookup_context(uint32_t ctx_id) { + if (SG_INVALID_ID != ctx_id) { + _sgl_context_t* ctx = _sgl_context_at(ctx_id); + if (ctx->slot.id == ctx_id) { + return ctx; + } + } + return 0; +} + +// make context id from uint32_t id +static sgl_context _sgl_make_ctx_id(uint32_t ctx_id) { + sgl_context ctx = { ctx_id }; + return ctx; +} + +static sgl_context _sgl_alloc_context(void) { + sgl_context res; + int slot_index = _sgl_pool_alloc_index(&_sgl.context_pool.pool); + if (_SGL_INVALID_SLOT_INDEX != slot_index) { + res = _sgl_make_ctx_id(_sgl_slot_alloc(&_sgl.context_pool.pool, &_sgl.context_pool.contexts[slot_index].slot, slot_index)); + } else { + // pool is exhausted + res = _sgl_make_ctx_id(SG_INVALID_ID); + } + return res; +} + +// return sgl_context_desc_t with patched defaults +static sgl_context_desc_t _sgl_context_desc_defaults(const sgl_context_desc_t* desc) { + sgl_context_desc_t res = *desc; + res.max_vertices = _sgl_def(desc->max_vertices, _SGL_DEFAULT_MAX_VERTICES); + res.max_commands = _sgl_def(desc->max_commands, _SGL_DEFAULT_MAX_COMMANDS); + return res; +} + +static void _sgl_identity(_sgl_matrix_t*); +static sg_commit_listener _sgl_make_commit_listener(_sgl_context_t* ctx); +static void _sgl_init_context(sgl_context ctx_id, const sgl_context_desc_t* in_desc) { + SOKOL_ASSERT((ctx_id.id != SG_INVALID_ID) && in_desc); + _sgl_context_t* ctx = _sgl_lookup_context(ctx_id.id); + SOKOL_ASSERT(ctx); + ctx->desc = _sgl_context_desc_defaults(in_desc); + // NOTE: frame_id must be non-zero, so that updates trigger in first frame + ctx->frame_id = 1; + ctx->cur_view = _sgl.def_view; + ctx->cur_smp = _sgl.def_smp; + + // allocate buffers and pools + ctx->vertices.cap = ctx->desc.max_vertices; + ctx->commands.cap = ctx->uniforms.cap = ctx->desc.max_commands; + ctx->vertices.ptr = (_sgl_vertex_t*) _sgl_malloc((size_t)ctx->vertices.cap * sizeof(_sgl_vertex_t)); + ctx->uniforms.ptr = (_sgl_uniform_t*) _sgl_malloc((size_t)ctx->uniforms.cap * sizeof(_sgl_uniform_t)); + ctx->commands.ptr = (_sgl_command_t*) _sgl_malloc((size_t)ctx->commands.cap * sizeof(_sgl_command_t)); + + // create sokol-gfx resource objects + sg_push_debug_group("sokol-gl"); + + sg_buffer_desc vbuf_desc; + _sgl_clear(&vbuf_desc, sizeof(vbuf_desc)); + vbuf_desc.size = (size_t)ctx->vertices.cap * sizeof(_sgl_vertex_t); + vbuf_desc.usage.vertex_buffer = true; + vbuf_desc.usage.stream_update = true; + vbuf_desc.label = "sgl-vertex-buffer"; + ctx->vbuf = sg_make_buffer(&vbuf_desc); + SOKOL_ASSERT(SG_INVALID_ID != ctx->vbuf.id); + ctx->bind.vertex_buffers[0] = ctx->vbuf; + + sg_pipeline_desc def_pip_desc; + _sgl_clear(&def_pip_desc, sizeof(def_pip_desc)); + def_pip_desc.depth.write_enabled = true; + ctx->def_pip = _sgl_make_pipeline(&def_pip_desc, &ctx->desc); + if (!sg_add_commit_listener(_sgl_make_commit_listener(ctx))) { + _SGL_ERROR(ADD_COMMIT_LISTENER_FAILED); + } + sg_pop_debug_group(); + + // default state + ctx->rgba = 0xFFFFFFFF; + ctx->point_size = 1.0f; + for (int i = 0; i < SGL_NUM_MATRIXMODES; i++) { + _sgl_identity(&ctx->matrix_stack[i][0]); + } + ctx->pip_stack[0] = ctx->def_pip; + ctx->matrix_dirty = true; +} + +static sgl_context _sgl_make_context(const sgl_context_desc_t* desc) { + SOKOL_ASSERT(desc); + sgl_context ctx_id = _sgl_alloc_context(); + if (ctx_id.id != SG_INVALID_ID) { + _sgl_init_context(ctx_id, desc); + } else { + _SGL_ERROR(CONTEXT_POOL_EXHAUSTED); + } + return ctx_id; +} + +static void _sgl_destroy_context(sgl_context ctx_id) { + _sgl_context_t* ctx = _sgl_lookup_context(ctx_id.id); + if (ctx) { + SOKOL_ASSERT(ctx->vertices.ptr); + SOKOL_ASSERT(ctx->uniforms.ptr); + SOKOL_ASSERT(ctx->commands.ptr); + + _sgl_free(ctx->vertices.ptr); + _sgl_free(ctx->uniforms.ptr); + _sgl_free(ctx->commands.ptr); + ctx->vertices.ptr = 0; + ctx->uniforms.ptr = 0; + ctx->commands.ptr = 0; + + sg_push_debug_group("sokol-gl"); + sg_destroy_buffer(ctx->vbuf); + _sgl_destroy_pipeline(ctx->def_pip); + sg_remove_commit_listener(_sgl_make_commit_listener(ctx)); + sg_pop_debug_group(); + + _sgl_reset_context(ctx); + _sgl_pool_free_index(&_sgl.context_pool.pool, _sgl_slot_index(ctx_id.id)); + } +} + +// ███ ███ ██ ███████ ██████ +// ████ ████ ██ ██ ██ +// ██ ████ ██ ██ ███████ ██ +// ██ ██ ██ ██ ██ ██ +// ██ ██ ██ ███████ ██████ +// +// >>misc + +static sgl_error_t _sgl_error_defaults(void) { + sgl_error_t defaults; + _sgl_clear(&defaults, sizeof(defaults)); + return defaults; +} + +static int _sgl_num_vertices(_sgl_context_t* ctx) { + return ctx->vertices.next; +} + +static int _sgl_num_commands(_sgl_context_t* ctx) { + return ctx->commands.next; +} + +static void _sgl_begin(_sgl_context_t* ctx, _sgl_primitive_type_t mode) { + ctx->in_begin = true; + ctx->base_vertex = ctx->vertices.next; + ctx->quad_vtx_count = 0; + ctx->cur_prim_type = mode; +} + +static void _sgl_rewind(_sgl_context_t* ctx) { + ctx->frame_id++; + ctx->vertices.next = 0; + ctx->uniforms.next = 0; + ctx->commands.next = 0; + ctx->base_vertex = 0; + ctx->error = _sgl_error_defaults(); + ctx->layer_id = 0; + ctx->matrix_dirty = true; +} + +// called from inside sokol-gfx sg_commit() +static void _sgl_commit_listener(void* userdata) { + _sgl_context_t* ctx = _sgl_lookup_context((uint32_t)(uintptr_t)userdata); + if (ctx) { + _sgl_rewind(ctx); + } +} + +static sg_commit_listener _sgl_make_commit_listener(_sgl_context_t* ctx) { + sg_commit_listener listener = { _sgl_commit_listener, (void*)(uintptr_t)(ctx->slot.id) }; + return listener; +} + +static _sgl_vertex_t* _sgl_next_vertex(_sgl_context_t* ctx) { + if (ctx->vertices.next < ctx->vertices.cap) { + return &ctx->vertices.ptr[ctx->vertices.next++]; + } else { + ctx->error.vertices_full = true; + ctx->error.any = true; + return 0; + } +} + +static _sgl_uniform_t* _sgl_next_uniform(_sgl_context_t* ctx) { + if (ctx->uniforms.next < ctx->uniforms.cap) { + return &ctx->uniforms.ptr[ctx->uniforms.next++]; + } else { + ctx->error.uniforms_full = true; + ctx->error.any = true; + return 0; + } +} + +static _sgl_command_t* _sgl_cur_command(_sgl_context_t* ctx) { + if (ctx->commands.next > 0) { + return &ctx->commands.ptr[ctx->commands.next - 1]; + } else { + return 0; + } +} + +static _sgl_command_t* _sgl_next_command(_sgl_context_t* ctx) { + if (ctx->commands.next < ctx->commands.cap) { + return &ctx->commands.ptr[ctx->commands.next++]; + } else { + ctx->error.commands_full = true; + ctx->error.any = true; + return 0; + } +} + +static uint32_t _sgl_pack_rgbab(uint8_t r, uint8_t g, uint8_t b, uint8_t a) { + return (uint32_t)(((uint32_t)a<<24)|((uint32_t)b<<16)|((uint32_t)g<<8)|r); +} + +static float _sgl_clamp(float v, float lo, float hi) { + if (v < lo) return lo; + else if (v > hi) return hi; + else return v; +} + +static uint32_t _sgl_pack_rgbaf(float r, float g, float b, float a) { + uint8_t r_u8 = (uint8_t) (_sgl_clamp(r, 0.0f, 1.0f) * 255.0f); + uint8_t g_u8 = (uint8_t) (_sgl_clamp(g, 0.0f, 1.0f) * 255.0f); + uint8_t b_u8 = (uint8_t) (_sgl_clamp(b, 0.0f, 1.0f) * 255.0f); + uint8_t a_u8 = (uint8_t) (_sgl_clamp(a, 0.0f, 1.0f) * 255.0f); + return _sgl_pack_rgbab(r_u8, g_u8, b_u8, a_u8); +} + +static void _sgl_vtx(_sgl_context_t* ctx, float x, float y, float z, float u, float v, uint32_t rgba) { + SOKOL_ASSERT(ctx->in_begin); + _sgl_vertex_t* vtx; + /* handle non-native primitive types */ + if ((ctx->cur_prim_type == SGL_PRIMITIVETYPE_QUADS) && ((ctx->quad_vtx_count & 3) == 3)) { + /* for quads, before writing the last quad vertex, reuse + the first and third vertex to start the second triangle in the quad + */ + vtx = _sgl_next_vertex(ctx); + if (vtx) { *vtx = *(vtx - 3); } + vtx = _sgl_next_vertex(ctx); + if (vtx) { *vtx = *(vtx - 2); } + } + vtx = _sgl_next_vertex(ctx); + if (vtx) { + vtx->pos[0] = x; vtx->pos[1] = y; vtx->pos[2] = z; + vtx->uv[0] = u; vtx->uv[1] = v; + vtx->rgba = rgba; + vtx->psize = ctx->point_size; + } + ctx->quad_vtx_count++; +} + +static void _sgl_identity(_sgl_matrix_t* m) { + for (int c = 0; c < 4; c++) { + for (int r = 0; r < 4; r++) { + m->v[c][r] = (r == c) ? 1.0f : 0.0f; + } + } +} + +static void _sgl_transpose(_sgl_matrix_t* dst, const _sgl_matrix_t* m) { + SOKOL_ASSERT(dst != m); + for (int c = 0; c < 4; c++) { + for (int r = 0; r < 4; r++) { + dst->v[r][c] = m->v[c][r]; + } + } +} + +/* _sgl_rotate, _sgl_frustum, _sgl_ortho from MESA m_matric.c */ +static void _sgl_matmul4(_sgl_matrix_t* p, const _sgl_matrix_t* a, const _sgl_matrix_t* b) { + for (int r = 0; r < 4; r++) { + float ai0=a->v[0][r], ai1=a->v[1][r], ai2=a->v[2][r], ai3=a->v[3][r]; + p->v[0][r] = ai0*b->v[0][0] + ai1*b->v[0][1] + ai2*b->v[0][2] + ai3*b->v[0][3]; + p->v[1][r] = ai0*b->v[1][0] + ai1*b->v[1][1] + ai2*b->v[1][2] + ai3*b->v[1][3]; + p->v[2][r] = ai0*b->v[2][0] + ai1*b->v[2][1] + ai2*b->v[2][2] + ai3*b->v[2][3]; + p->v[3][r] = ai0*b->v[3][0] + ai1*b->v[3][1] + ai2*b->v[3][2] + ai3*b->v[3][3]; + } +} + +static void _sgl_mul(_sgl_matrix_t* dst, const _sgl_matrix_t* m) { + _sgl_matmul4(dst, dst, m); +} + +static void _sgl_rotate(_sgl_matrix_t* dst, float a, float x, float y, float z) { + + float s = sinf(a); + float c = cosf(a); + + float mag = sqrtf(x*x + y*y + z*z); + if (mag < 1.0e-4F) { + return; + } + x /= mag; + y /= mag; + z /= mag; + float xx = x * x; + float yy = y * y; + float zz = z * z; + float xy = x * y; + float yz = y * z; + float zx = z * x; + float xs = x * s; + float ys = y * s; + float zs = z * s; + float one_c = 1.0f - c; + + _sgl_matrix_t m; + m.v[0][0] = (one_c * xx) + c; + m.v[1][0] = (one_c * xy) - zs; + m.v[2][0] = (one_c * zx) + ys; + m.v[3][0] = 0.0f; + m.v[0][1] = (one_c * xy) + zs; + m.v[1][1] = (one_c * yy) + c; + m.v[2][1] = (one_c * yz) - xs; + m.v[3][1] = 0.0f; + m.v[0][2] = (one_c * zx) - ys; + m.v[1][2] = (one_c * yz) + xs; + m.v[2][2] = (one_c * zz) + c; + m.v[3][2] = 0.0f; + m.v[0][3] = 0.0f; + m.v[1][3] = 0.0f; + m.v[2][3] = 0.0f; + m.v[3][3] = 1.0f; + _sgl_mul(dst, &m); +} + +static void _sgl_scale(_sgl_matrix_t* dst, float x, float y, float z) { + for (int r = 0; r < 4; r++) { + dst->v[0][r] *= x; + dst->v[1][r] *= y; + dst->v[2][r] *= z; + } +} + +static void _sgl_translate(_sgl_matrix_t* dst, float x, float y, float z) { + for (int r = 0; r < 4; r++) { + dst->v[3][r] = dst->v[0][r]*x + dst->v[1][r]*y + dst->v[2][r]*z + dst->v[3][r]; + } +} + +static void _sgl_frustum(_sgl_matrix_t* dst, float left, float right, float bottom, float top, float znear, float zfar) { + float x = (2.0f * znear) / (right - left); + float y = (2.0f * znear) / (top - bottom); + float a = (right + left) / (right - left); + float b = (top + bottom) / (top - bottom); + float c = -(zfar + znear) / (zfar - znear); + float d = -(2.0f * zfar * znear) / (zfar - znear); + _sgl_matrix_t m; + m.v[0][0] = x; m.v[0][1] = 0.0f; m.v[0][2] = 0.0f; m.v[0][3] = 0.0f; + m.v[1][0] = 0.0f; m.v[1][1] = y; m.v[1][2] = 0.0f; m.v[1][3] = 0.0f; + m.v[2][0] = a; m.v[2][1] = b; m.v[2][2] = c; m.v[2][3] = -1.0f; + m.v[3][0] = 0.0f; m.v[3][1] = 0.0f; m.v[3][2] = d; m.v[3][3] = 0.0f; + _sgl_mul(dst, &m); +} + +static void _sgl_ortho(_sgl_matrix_t* dst, float left, float right, float bottom, float top, float znear, float zfar) { + _sgl_matrix_t m; + m.v[0][0] = 2.0f / (right - left); + m.v[1][0] = 0.0f; + m.v[2][0] = 0.0f; + m.v[3][0] = -(right + left) / (right - left); + m.v[0][1] = 0.0f; + m.v[1][1] = 2.0f / (top - bottom); + m.v[2][1] = 0.0f; + m.v[3][1] = -(top + bottom) / (top - bottom); + m.v[0][2] = 0.0f; + m.v[1][2] = 0.0f; + m.v[2][2] = -2.0f / (zfar - znear); + m.v[3][2] = -(zfar + znear) / (zfar - znear); + m.v[0][3] = 0.0f; + m.v[1][3] = 0.0f; + m.v[2][3] = 0.0f; + m.v[3][3] = 1.0f; + + _sgl_mul(dst, &m); +} + +/* _sgl_perspective, _sgl_lookat from Regal project.c */ +static void _sgl_perspective(_sgl_matrix_t* dst, float fovy, float aspect, float znear, float zfar) { + float sine = sinf(fovy / 2.0f); + float delta_z = zfar - znear; + if ((delta_z == 0.0f) || (sine == 0.0f) || (aspect == 0.0f)) { + return; + } + float cotan = cosf(fovy / 2.0f) / sine; + _sgl_matrix_t m; + _sgl_identity(&m); + m.v[0][0] = cotan / aspect; + m.v[1][1] = cotan; + m.v[2][2] = -(zfar + znear) / delta_z; + m.v[2][3] = -1.0f; + m.v[3][2] = -2.0f * znear * zfar / delta_z; + m.v[3][3] = 0.0f; + _sgl_mul(dst, &m); +} + +static void _sgl_normalize(float v[3]) { + float r = sqrtf(v[0]*v[0] + v[1]*v[1] + v[2]*v[2]); + if (r == 0.0f) { + return; + } + v[0] /= r; + v[1] /= r; + v[2] /= r; +} + +static void _sgl_cross(float v1[3], float v2[3], float res[3]) { + res[0] = v1[1]*v2[2] - v1[2]*v2[1]; + res[1] = v1[2]*v2[0] - v1[0]*v2[2]; + res[2] = v1[0]*v2[1] - v1[1]*v2[0]; +} + +static void _sgl_lookat(_sgl_matrix_t* dst, + float eye_x, float eye_y, float eye_z, + float center_x, float center_y, float center_z, + float up_x, float up_y, float up_z) +{ + float fwd[3], side[3], up[3]; + + fwd[0] = center_x - eye_x; fwd[1] = center_y - eye_y; fwd[2] = center_z - eye_z; + up[0] = up_x; up[1] = up_y; up[2] = up_z; + _sgl_normalize(fwd); + _sgl_cross(fwd, up, side); + _sgl_normalize(side); + _sgl_cross(side, fwd, up); + + _sgl_matrix_t m; + _sgl_identity(&m); + m.v[0][0] = side[0]; + m.v[1][0] = side[1]; + m.v[2][0] = side[2]; + m.v[0][1] = up[0]; + m.v[1][1] = up[1]; + m.v[2][1] = up[2]; + m.v[0][2] = -fwd[0]; + m.v[1][2] = -fwd[1]; + m.v[2][2] = -fwd[2]; + _sgl_mul(dst, &m); + _sgl_translate(dst, -eye_x, -eye_y, -eye_z); +} + +/* current top-of-stack projection matrix */ +static _sgl_matrix_t* _sgl_matrix_projection(_sgl_context_t* ctx) { + return &ctx->matrix_stack[SGL_MATRIXMODE_PROJECTION][ctx->matrix_tos[SGL_MATRIXMODE_PROJECTION]]; +} + +/* get top-of-stack modelview matrix */ +static _sgl_matrix_t* _sgl_matrix_modelview(_sgl_context_t* ctx) { + return &ctx->matrix_stack[SGL_MATRIXMODE_MODELVIEW][ctx->matrix_tos[SGL_MATRIXMODE_MODELVIEW]]; +} + +/* get top-of-stack texture matrix */ +static _sgl_matrix_t* _sgl_matrix_texture(_sgl_context_t* ctx) { + return &ctx->matrix_stack[SGL_MATRIXMODE_TEXTURE][ctx->matrix_tos[SGL_MATRIXMODE_TEXTURE]]; +} + +/* get pointer to current top-of-stack of current matrix mode */ +static _sgl_matrix_t* _sgl_matrix(_sgl_context_t* ctx) { + return &ctx->matrix_stack[ctx->cur_matrix_mode][ctx->matrix_tos[ctx->cur_matrix_mode]]; +} + +static sgl_desc_t _sgl_desc_defaults(const sgl_desc_t* desc) { + SOKOL_ASSERT((desc->allocator.alloc_fn && desc->allocator.free_fn) || (!desc->allocator.alloc_fn && !desc->allocator.free_fn)); + sgl_desc_t res = *desc; + res.max_vertices = _sgl_def(desc->max_vertices, _SGL_DEFAULT_MAX_VERTICES); + res.max_commands = _sgl_def(desc->max_commands, _SGL_DEFAULT_MAX_COMMANDS); + res.context_pool_size = _sgl_def(desc->context_pool_size, _SGL_DEFAULT_CONTEXT_POOL_SIZE); + res.pipeline_pool_size = _sgl_def(desc->pipeline_pool_size, _SGL_DEFAULT_PIPELINE_POOL_SIZE); + res.face_winding = _sgl_def(desc->face_winding, SG_FACEWINDING_CCW); + return res; +} + +// create resources which are shared between all contexts +static void _sgl_setup_common(void) { + sg_push_debug_group("sokol-gl"); + + uint32_t pixels[64]; + for (int i = 0; i < 64; i++) { + pixels[i] = 0xFFFFFFFF; + } + sg_image_desc img_desc; + _sgl_clear(&img_desc, sizeof(img_desc)); + img_desc.type = SG_IMAGETYPE_2D; + img_desc.width = 8; + img_desc.height = 8; + img_desc.num_mipmaps = 1; + img_desc.pixel_format = SG_PIXELFORMAT_RGBA8; + img_desc.data.mip_levels[0] = SG_RANGE(pixels); + img_desc.label = "sgl-default-texture"; + _sgl.def_img = sg_make_image(&img_desc); + SOKOL_ASSERT(SG_INVALID_ID != _sgl.def_img.id); + + sg_view_desc view_desc; + _sgl_clear(&view_desc, sizeof(view_desc)); + view_desc.texture.image = _sgl.def_img; + view_desc.label = "sgl-default-texture-view"; + _sgl.def_view = sg_make_view(&view_desc); + SOKOL_ASSERT(SG_INVALID_ID != _sgl.def_view.id); + + sg_sampler_desc smp_desc; + _sgl_clear(&smp_desc, sizeof(smp_desc)); + smp_desc.min_filter = SG_FILTER_NEAREST; + smp_desc.mag_filter = SG_FILTER_NEAREST; + smp_desc.label = "sgl-default-sampler"; + _sgl.def_smp = sg_make_sampler(&smp_desc); + SOKOL_ASSERT(SG_INVALID_ID != _sgl.def_smp.id); + + // one shader for all contexts + sg_shader_desc shd_desc; + _sgl_clear(&shd_desc, sizeof(shd_desc)); + shd_desc.attrs[0].glsl_name = "position"; + shd_desc.attrs[1].glsl_name = "texcoord0"; + shd_desc.attrs[2].glsl_name = "color0"; + shd_desc.attrs[3].glsl_name = "psize"; + shd_desc.attrs[0].hlsl_sem_name = "TEXCOORD"; + shd_desc.attrs[0].hlsl_sem_index = 0; + shd_desc.attrs[1].hlsl_sem_name = "TEXCOORD"; + shd_desc.attrs[1].hlsl_sem_index = 1; + shd_desc.attrs[2].hlsl_sem_name = "TEXCOORD"; + shd_desc.attrs[2].hlsl_sem_index = 2; + shd_desc.attrs[3].hlsl_sem_name = "TEXCOORD"; + shd_desc.attrs[3].hlsl_sem_index = 3; + shd_desc.uniform_blocks[0].stage = SG_SHADERSTAGE_VERTEX; + shd_desc.uniform_blocks[0].size = sizeof(_sgl_uniform_t); + shd_desc.uniform_blocks[0].hlsl_register_b_n = 0; + shd_desc.uniform_blocks[0].msl_buffer_n = 0; + shd_desc.uniform_blocks[0].wgsl_group0_binding_n = 0; + shd_desc.uniform_blocks[0].glsl_uniforms[0].glsl_name = "vs_params"; + shd_desc.uniform_blocks[0].glsl_uniforms[0].type = SG_UNIFORMTYPE_FLOAT4; + shd_desc.uniform_blocks[0].glsl_uniforms[0].array_count = 8; + shd_desc.views[0].texture.stage = SG_SHADERSTAGE_FRAGMENT; + shd_desc.views[0].texture.image_type = SG_IMAGETYPE_2D; + shd_desc.views[0].texture.sample_type = SG_IMAGESAMPLETYPE_FLOAT; + shd_desc.views[0].texture.hlsl_register_t_n = 0; + shd_desc.views[0].texture.msl_texture_n = 0; + shd_desc.views[0].texture.wgsl_group1_binding_n = 64; + shd_desc.samplers[0].stage = SG_SHADERSTAGE_FRAGMENT; + shd_desc.samplers[0].sampler_type = SG_SAMPLERTYPE_FILTERING; + shd_desc.samplers[0].hlsl_register_s_n = 0; + shd_desc.samplers[0].msl_sampler_n = 0; + shd_desc.samplers[0].wgsl_group1_binding_n = 80; + shd_desc.texture_sampler_pairs[0].stage = SG_SHADERSTAGE_FRAGMENT; + shd_desc.texture_sampler_pairs[0].view_slot = 0; + shd_desc.texture_sampler_pairs[0].sampler_slot = 0; + shd_desc.texture_sampler_pairs[0].glsl_name = "tex_smp"; + shd_desc.label = "sgl-shader"; + #if defined(SOKOL_GLCORE) + shd_desc.vertex_func.source = (const char*)_sgl_vs_source_glsl410; + shd_desc.fragment_func.source = (const char*)_sgl_fs_source_glsl410; + #elif defined(SOKOL_GLES3) + shd_desc.vertex_func.source = (const char*)_sgl_vs_source_glsl300es; + shd_desc.fragment_func.source = (const char*)_sgl_fs_source_glsl300es; + #elif defined(SOKOL_METAL) + shd_desc.vertex_func.entry = "main0"; + shd_desc.fragment_func.entry = "main0"; + switch (sg_query_backend()) { + case SG_BACKEND_METAL_MACOS: + shd_desc.vertex_func.bytecode = SG_RANGE(_sgl_vs_bytecode_metal_macos); + shd_desc.fragment_func.bytecode = SG_RANGE(_sgl_fs_bytecode_metal_macos); + break; + case SG_BACKEND_METAL_IOS: + shd_desc.vertex_func.bytecode = SG_RANGE(_sgl_vs_bytecode_metal_ios); + shd_desc.fragment_func.bytecode = SG_RANGE(_sgl_fs_bytecode_metal_ios); + break; + default: + shd_desc.vertex_func.source = (const char*)_sgl_vs_source_metal_sim; + shd_desc.fragment_func.source = (const char*)_sgl_fs_source_metal_sim; + break; + } + #elif defined(SOKOL_D3D11) + shd_desc.vertex_func.bytecode = SG_RANGE(_sgl_vs_bytecode_hlsl4); + shd_desc.fragment_func.bytecode = SG_RANGE(_sgl_fs_bytecode_hlsl4); + #elif defined(SOKOL_WGPU) + shd_desc.vertex_func.source = (const char*)_sgl_vs_source_wgsl; + shd_desc.fragment_func.source = (const char*)_sgl_fs_source_wgsl; + #else + shd_desc.vertex_func.source = _sgl_vs_source_dummy; + shd_desc.fragment_func.source = _sgl_fs_source_dummy; + #endif + _sgl.shd = sg_make_shader(&shd_desc); + SOKOL_ASSERT(SG_INVALID_ID != _sgl.shd.id); + sg_pop_debug_group(); +} + +// discard resources which are shared between all contexts +static void _sgl_discard_common(void) { + sg_push_debug_group("sokol-gl"); + sg_destroy_view(_sgl.def_view); + sg_destroy_image(_sgl.def_img); + sg_destroy_sampler(_sgl.def_smp); + sg_destroy_shader(_sgl.shd); + sg_pop_debug_group(); +} + +static bool _sgl_is_default_context(sgl_context ctx_id) { + return ctx_id.id == SGL_DEFAULT_CONTEXT.id; +} + +static void _sgl_draw(_sgl_context_t* ctx, int layer_id) { + SOKOL_ASSERT(ctx); + if ((ctx->vertices.next > 0) && (ctx->commands.next > 0)) { + sg_push_debug_group("sokol-gl"); + + uint32_t cur_pip_id = SG_INVALID_ID; + uint32_t cur_tex_id = SG_INVALID_ID; + uint32_t cur_smp_id = SG_INVALID_ID; + int cur_uniform_index = -1; + + if (ctx->update_frame_id != ctx->frame_id) { + ctx->update_frame_id = ctx->frame_id; + const sg_range range = { ctx->vertices.ptr, (size_t)ctx->vertices.next * sizeof(_sgl_vertex_t) }; + sg_update_buffer(ctx->vbuf, &range); + } + + // render all successfully recorded commands (this may be less than the + // issued commands if we're in an error state) + for (int i = 0; i < ctx->commands.next; i++) { + const _sgl_command_t* cmd = &ctx->commands.ptr[i]; + if (cmd->layer_id != layer_id) { + continue; + } + switch (cmd->cmd) { + case SGL_COMMAND_VIEWPORT: + { + const _sgl_viewport_args_t* args = &cmd->args.viewport; + sg_apply_viewport(args->x, args->y, args->w, args->h, args->origin_top_left); + } + break; + case SGL_COMMAND_SCISSOR_RECT: + { + const _sgl_scissor_rect_args_t* args = &cmd->args.scissor_rect; + sg_apply_scissor_rect(args->x, args->y, args->w, args->h, args->origin_top_left); + } + break; + case SGL_COMMAND_DRAW: + { + const _sgl_draw_args_t* args = &cmd->args.draw; + if (args->pip.id != cur_pip_id) { + sg_apply_pipeline(args->pip); + cur_pip_id = args->pip.id; + // when pipeline changes, also need to re-apply uniforms and bindings + cur_tex_id = SG_INVALID_ID; + cur_smp_id = SG_INVALID_ID; + cur_uniform_index = -1; + } + if ((cur_tex_id != args->view.id) || (cur_smp_id != args->smp.id)) { + ctx->bind.views[0] = args->view; + ctx->bind.samplers[0] = args->smp; + sg_apply_bindings(&ctx->bind); + cur_tex_id = args->view.id; + cur_smp_id = args->smp.id; + } + if (cur_uniform_index != args->uniform_index) { + const sg_range ub_range = { &ctx->uniforms.ptr[args->uniform_index], sizeof(_sgl_uniform_t) }; + sg_apply_uniforms(0, &ub_range); + cur_uniform_index = args->uniform_index; + } + // FIXME: what if number of vertices doesn't match the primitive type? + if (args->num_vertices > 0) { + sg_draw(args->base_vertex, args->num_vertices, 1); + } + } + break; + } + } + sg_pop_debug_group(); + } +} + +static sgl_context_desc_t _sgl_as_context_desc(const sgl_desc_t* desc) { + sgl_context_desc_t ctx_desc; + _sgl_clear(&ctx_desc, sizeof(ctx_desc)); + ctx_desc.max_vertices = desc->max_vertices; + ctx_desc.max_commands = desc->max_commands; + ctx_desc.color_format = desc->color_format; + ctx_desc.depth_format = desc->depth_format; + ctx_desc.sample_count = desc->sample_count; + return ctx_desc; +} + +// ██████ ██ ██ ██████ ██ ██ ██████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██████ ██ ██ ██████ ██ ██ ██ +// ██ ██ ██ ██ ██ ██ ██ ██ +// ██ ██████ ██████ ███████ ██ ██████ +// +// >>public +SOKOL_API_IMPL void sgl_setup(const sgl_desc_t* desc) { + SOKOL_ASSERT(desc); + _sgl_clear(&_sgl, sizeof(_sgl)); + _sgl.init_cookie = _SGL_INIT_COOKIE; + _sgl.desc = _sgl_desc_defaults(desc); + _sgl_setup_pipeline_pool(_sgl.desc.pipeline_pool_size); + _sgl_setup_context_pool(_sgl.desc.context_pool_size); + _sgl_setup_common(); + const sgl_context_desc_t ctx_desc = _sgl_as_context_desc(&_sgl.desc); + _sgl.def_ctx_id = sgl_make_context(&ctx_desc); + SOKOL_ASSERT(SGL_DEFAULT_CONTEXT.id == _sgl.def_ctx_id.id); + sgl_set_context(_sgl.def_ctx_id); +} + +SOKOL_API_IMPL void sgl_shutdown(void) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + // contexts own a pipeline, so destroy contexts before pipelines + for (int i = 0; i < _sgl.context_pool.pool.size; i++) { + _sgl_context_t* ctx = &_sgl.context_pool.contexts[i]; + _sgl_destroy_context(_sgl_make_ctx_id(ctx->slot.id)); + } + for (int i = 0; i < _sgl.pip_pool.pool.size; i++) { + _sgl_pipeline_t* pip = &_sgl.pip_pool.pips[i]; + _sgl_destroy_pipeline(_sgl_make_pip_id(pip->slot.id)); + } + _sgl_discard_context_pool(); + _sgl_discard_pipeline_pool(); + _sgl_discard_common(); + _sgl.init_cookie = 0; +} + +SOKOL_API_IMPL sgl_error_t sgl_error(void) { + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + return ctx->error; + } else { + sgl_error_t err = _sgl_error_defaults(); + err.no_context = true; + err.any = true; + return err; + } +} + +SOKOL_API_IMPL sgl_error_t sgl_context_error(sgl_context ctx_id) { + const _sgl_context_t* ctx = _sgl_lookup_context(ctx_id.id); + if (ctx) { + return ctx->error; + } else { + sgl_error_t err = _sgl_error_defaults(); + err.no_context = true; + err.any = true; + return err; + } +} + +SOKOL_API_IMPL float sgl_rad(float deg) { + return (deg * (float)M_PI) / 180.0f; +} + +SOKOL_API_IMPL float sgl_deg(float rad) { + return (rad * 180.0f) / (float)M_PI; +} + +SOKOL_API_IMPL sgl_context sgl_make_context(const sgl_context_desc_t* desc) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + return _sgl_make_context(desc); +} + +SOKOL_API_IMPL void sgl_destroy_context(sgl_context ctx_id) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + if (_sgl_is_default_context(ctx_id)) { + _SGL_WARN(CANNOT_DESTROY_DEFAULT_CONTEXT); + return; + } + _sgl_destroy_context(ctx_id); + // re-validate the current context pointer (this will return a nullptr + // if we just destroyed the current context) + _sgl.cur_ctx = _sgl_lookup_context(_sgl.cur_ctx_id.id); +} + +SOKOL_API_IMPL void sgl_set_context(sgl_context ctx_id) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + if (_sgl_is_default_context(ctx_id)) { + _sgl.cur_ctx_id = _sgl.def_ctx_id; + } else { + _sgl.cur_ctx_id = ctx_id; + } + // this will return null if the handle isn't valid + _sgl.cur_ctx = _sgl_lookup_context(_sgl.cur_ctx_id.id); +} + +SOKOL_API_IMPL sgl_context sgl_get_context(void) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + return _sgl.cur_ctx_id; +} + +SOKOL_API_IMPL sgl_context sgl_default_context(void) { + return SGL_DEFAULT_CONTEXT; +} + +SOKOL_API_IMPL int sgl_num_vertices(void) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + return _sgl_num_vertices(ctx); + } else { + return 0; + } +} + +SOKOL_API_IMPL int sgl_num_commands(void) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + return _sgl_num_commands(ctx); + } else { + return 0; + } +} + +SOKOL_API_IMPL sgl_pipeline sgl_make_pipeline(const sg_pipeline_desc* desc) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + return _sgl_make_pipeline(desc, &ctx->desc); + } else { + return _sgl_make_pip_id(SG_INVALID_ID); + } +} + +SOKOL_API_IMPL sgl_pipeline sgl_context_make_pipeline(sgl_context ctx_id, const sg_pipeline_desc* desc) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + const _sgl_context_t* ctx = _sgl_lookup_context(ctx_id.id); + if (ctx) { + return _sgl_make_pipeline(desc, &ctx->desc); + } else { + return _sgl_make_pip_id(SG_INVALID_ID); + } +} + +SOKOL_API_IMPL void sgl_destroy_pipeline(sgl_pipeline pip_id) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_destroy_pipeline(pip_id); +} + +SOKOL_API_IMPL void sgl_load_pipeline(sgl_pipeline pip_id) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (!ctx) { + return; + } + SOKOL_ASSERT((ctx->pip_tos >= 0) && (ctx->pip_tos < _SGL_MAX_STACK_DEPTH)); + ctx->pip_stack[ctx->pip_tos] = pip_id; +} + +SOKOL_API_IMPL void sgl_load_default_pipeline(void) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (!ctx) { + return; + } + SOKOL_ASSERT((ctx->pip_tos >= 0) && (ctx->pip_tos < _SGL_MAX_STACK_DEPTH)); + ctx->pip_stack[ctx->pip_tos] = ctx->def_pip; +} + +SOKOL_API_IMPL void sgl_push_pipeline(void) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (!ctx) { + return; + } + if (ctx->pip_tos < (_SGL_MAX_STACK_DEPTH - 1)) { + ctx->pip_tos++; + ctx->pip_stack[ctx->pip_tos] = ctx->pip_stack[ctx->pip_tos-1]; + } else { + ctx->error.stack_overflow = true; + ctx->error.any = true; + } +} + +SOKOL_API_IMPL void sgl_pop_pipeline(void) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (!ctx) { + return; + } + if (ctx->pip_tos > 0) { + ctx->pip_tos--; + } else { + ctx->error.stack_underflow = true; + ctx->error.any = true; + } +} + +SOKOL_API_IMPL void sgl_defaults(void) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (!ctx) { + return; + } + SOKOL_ASSERT(!ctx->in_begin); + ctx->u = 0.0f; ctx->v = 0.0f; + ctx->rgba = 0xFFFFFFFF; + ctx->point_size = 1.0f; + ctx->texturing_enabled = false; + ctx->cur_view = _sgl.def_view; + ctx->cur_smp = _sgl.def_smp; + sgl_load_default_pipeline(); + _sgl_identity(_sgl_matrix_texture(ctx)); + _sgl_identity(_sgl_matrix_modelview(ctx)); + _sgl_identity(_sgl_matrix_projection(ctx)); + ctx->cur_matrix_mode = SGL_MATRIXMODE_MODELVIEW; + ctx->matrix_dirty = true; +} + +SOKOL_API_IMPL void sgl_layer(int layer_id) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (!ctx) { + return; + } + SOKOL_ASSERT(!ctx->in_begin); + ctx->layer_id = layer_id; +} + +SOKOL_API_IMPL void sgl_viewport(int x, int y, int w, int h, bool origin_top_left) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (!ctx) { + return; + } + SOKOL_ASSERT(!ctx->in_begin); + _sgl_command_t* cmd = _sgl_next_command(ctx); + if (cmd) { + cmd->cmd = SGL_COMMAND_VIEWPORT; + cmd->layer_id = ctx->layer_id; + cmd->args.viewport.x = x; + cmd->args.viewport.y = y; + cmd->args.viewport.w = w; + cmd->args.viewport.h = h; + cmd->args.viewport.origin_top_left = origin_top_left; + } +} + +SOKOL_API_IMPL void sgl_viewportf(float x, float y, float w, float h, bool origin_top_left) { + sgl_viewport((int)x, (int)y, (int)w, (int)h, origin_top_left); +} + +SOKOL_API_IMPL void sgl_scissor_rect(int x, int y, int w, int h, bool origin_top_left) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (!ctx) { + return; + } + SOKOL_ASSERT(!ctx->in_begin); + _sgl_command_t* cmd = _sgl_next_command(ctx); + if (cmd) { + cmd->cmd = SGL_COMMAND_SCISSOR_RECT; + cmd->layer_id = ctx->layer_id; + cmd->args.scissor_rect.x = x; + cmd->args.scissor_rect.y = y; + cmd->args.scissor_rect.w = w; + cmd->args.scissor_rect.h = h; + cmd->args.scissor_rect.origin_top_left = origin_top_left; + } +} + +SOKOL_API_IMPL void sgl_scissor_rectf(float x, float y, float w, float h, bool origin_top_left) { + sgl_scissor_rect((int)x, (int)y, (int)w, (int)h, origin_top_left); +} + +SOKOL_API_IMPL void sgl_enable_texture(void) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (!ctx) { + return; + } + SOKOL_ASSERT(!ctx->in_begin); + ctx->texturing_enabled = true; +} + +SOKOL_API_IMPL void sgl_disable_texture(void) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (!ctx) { + return; + } + SOKOL_ASSERT(!ctx->in_begin); + ctx->texturing_enabled = false; +} + +SOKOL_API_IMPL void sgl_texture(sg_view tex_view, sg_sampler smp) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (!ctx) { + return; + } + SOKOL_ASSERT(!ctx->in_begin); + if (SG_INVALID_ID != tex_view.id) { + ctx->cur_view = tex_view; + } else { + ctx->cur_view = _sgl.def_view; + } + if (SG_INVALID_ID != smp.id) { + ctx->cur_smp = smp; + } else { + ctx->cur_smp = _sgl.def_smp; + } +} + +SOKOL_API_IMPL void sgl_begin_points(void) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (!ctx) { + return; + } + SOKOL_ASSERT(!ctx->in_begin); + _sgl_begin(ctx, SGL_PRIMITIVETYPE_POINTS); +} + +SOKOL_API_IMPL void sgl_begin_lines(void) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (!ctx) { + return; + } + SOKOL_ASSERT(!ctx->in_begin); + _sgl_begin(ctx, SGL_PRIMITIVETYPE_LINES); +} + +SOKOL_API_IMPL void sgl_begin_line_strip(void) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (!ctx) { + return; + } + SOKOL_ASSERT(!ctx->in_begin); + _sgl_begin(ctx, SGL_PRIMITIVETYPE_LINE_STRIP); +} + +SOKOL_API_IMPL void sgl_begin_triangles(void) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (!ctx) { + return; + } + SOKOL_ASSERT(!ctx->in_begin); + _sgl_begin(ctx, SGL_PRIMITIVETYPE_TRIANGLES); +} + +SOKOL_API_IMPL void sgl_begin_triangle_strip(void) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (!ctx) { + return; + } + SOKOL_ASSERT(!ctx->in_begin); + _sgl_begin(ctx, SGL_PRIMITIVETYPE_TRIANGLE_STRIP); +} + +SOKOL_API_IMPL void sgl_begin_quads(void) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (!ctx) { + return; + } + SOKOL_ASSERT(!ctx->in_begin); + _sgl_begin(ctx, SGL_PRIMITIVETYPE_QUADS); +} + +SOKOL_API_IMPL void sgl_end(void) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (!ctx) { + return; + } + SOKOL_ASSERT(ctx->in_begin); + SOKOL_ASSERT(ctx->vertices.next >= ctx->base_vertex); + ctx->in_begin = false; + + bool matrix_dirty = ctx->matrix_dirty; + if (matrix_dirty) { + ctx->matrix_dirty = false; + _sgl_uniform_t* uni = _sgl_next_uniform(ctx); + if (uni) { + _sgl_matmul4(&uni->mvp, _sgl_matrix_projection(ctx), _sgl_matrix_modelview(ctx)); + uni->tm = *_sgl_matrix_texture(ctx); + } + } + + // don't record any new commands when we're in an error state + if (ctx->error.any) { + return; + } + + // check if command can be merged with current command + sg_pipeline pip = _sgl_get_pipeline(ctx->pip_stack[ctx->pip_tos], ctx->cur_prim_type); + sg_view view = ctx->texturing_enabled ? ctx->cur_view : _sgl.def_view; + sg_sampler smp = ctx->texturing_enabled ? ctx->cur_smp : _sgl.def_smp; + _sgl_command_t* cur_cmd = _sgl_cur_command(ctx); + bool merge_cmd = false; + if (cur_cmd) { + if ((cur_cmd->cmd == SGL_COMMAND_DRAW) && + (cur_cmd->layer_id == ctx->layer_id) && + (ctx->cur_prim_type != SGL_PRIMITIVETYPE_LINE_STRIP) && + (ctx->cur_prim_type != SGL_PRIMITIVETYPE_TRIANGLE_STRIP) && + !matrix_dirty && + (cur_cmd->args.draw.view.id == view.id) && + (cur_cmd->args.draw.smp.id == smp.id) && + (cur_cmd->args.draw.pip.id == pip.id)) + { + merge_cmd = true; + } + } + if (merge_cmd) { + // draw command can be merged with the previous command + cur_cmd->args.draw.num_vertices += ctx->vertices.next - ctx->base_vertex; + } else { + // append a new draw command + _sgl_command_t* cmd = _sgl_next_command(ctx); + if (cmd) { + SOKOL_ASSERT(ctx->uniforms.next > 0); + cmd->cmd = SGL_COMMAND_DRAW; + cmd->layer_id = ctx->layer_id; + cmd->args.draw.view = view; + cmd->args.draw.smp = smp; + cmd->args.draw.pip = _sgl_get_pipeline(ctx->pip_stack[ctx->pip_tos], ctx->cur_prim_type); + cmd->args.draw.base_vertex = ctx->base_vertex; + cmd->args.draw.num_vertices = ctx->vertices.next - ctx->base_vertex; + cmd->args.draw.uniform_index = ctx->uniforms.next - 1; + } + } +} + +SOKOL_API_IMPL void sgl_point_size(float s) { + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + ctx->point_size = s; + } +} + +SOKOL_API_IMPL void sgl_t2f(float u, float v) { + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + ctx->u = u; + ctx->v = v; + } +} + +SOKOL_API_IMPL void sgl_c3f(float r, float g, float b) { + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + ctx->rgba = _sgl_pack_rgbaf(r, g, b, 1.0f); + } +} + +SOKOL_API_IMPL void sgl_c4f(float r, float g, float b, float a) { + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + ctx->rgba = _sgl_pack_rgbaf(r, g, b, a); + } +} + +SOKOL_API_IMPL void sgl_c3b(uint8_t r, uint8_t g, uint8_t b) { + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + ctx->rgba = _sgl_pack_rgbab(r, g, b, 255); + } +} + +SOKOL_API_IMPL void sgl_c4b(uint8_t r, uint8_t g, uint8_t b, uint8_t a) { + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + ctx->rgba = _sgl_pack_rgbab(r, g, b, a); + } +} + +SOKOL_API_IMPL void sgl_c1i(uint32_t rgba) { + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + ctx->rgba = rgba; + } +} + +SOKOL_API_IMPL void sgl_v2f(float x, float y) { + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + _sgl_vtx(ctx, x, y, 0.0f, ctx->u, ctx->v, ctx->rgba); + } +} + +SOKOL_API_IMPL void sgl_v3f(float x, float y, float z) { + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + _sgl_vtx(ctx, x, y, z, ctx->u, ctx->v, ctx->rgba); + } +} + +SOKOL_API_IMPL void sgl_v2f_t2f(float x, float y, float u, float v) { + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + _sgl_vtx(ctx, x, y, 0.0f, u, v, ctx->rgba); + } +} + +SOKOL_API_IMPL void sgl_v3f_t2f(float x, float y, float z, float u, float v) { + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + _sgl_vtx(ctx, x, y, z, u, v, ctx->rgba); + } +} + +SOKOL_API_IMPL void sgl_v2f_c3f(float x, float y, float r, float g, float b) { + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + _sgl_vtx(ctx, x, y, 0.0f, ctx->u, ctx->v, _sgl_pack_rgbaf(r, g, b, 1.0f)); + } +} + +SOKOL_API_IMPL void sgl_v2f_c3b(float x, float y, uint8_t r, uint8_t g, uint8_t b) { + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + _sgl_vtx(ctx, x, y, 0.0f, ctx->u, ctx->v, _sgl_pack_rgbab(r, g, b, 255)); + } +} + +SOKOL_API_IMPL void sgl_v2f_c4f(float x, float y, float r, float g, float b, float a) { + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + _sgl_vtx(ctx, x, y, 0.0f, ctx->u, ctx->v, _sgl_pack_rgbaf(r, g, b, a)); + } +} + +SOKOL_API_IMPL void sgl_v2f_c4b(float x, float y, uint8_t r, uint8_t g, uint8_t b, uint8_t a) { + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + _sgl_vtx(ctx, x, y, 0.0f, ctx->u, ctx->v, _sgl_pack_rgbab(r, g, b, a)); + } +} + +SOKOL_API_IMPL void sgl_v2f_c1i(float x, float y, uint32_t rgba) { + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + _sgl_vtx(ctx, x, y, 0.0f, ctx->u, ctx->v, rgba); + } +} + +SOKOL_API_IMPL void sgl_v3f_c3f(float x, float y, float z, float r, float g, float b) { + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + _sgl_vtx(ctx, x, y, z, ctx->u, ctx->v, _sgl_pack_rgbaf(r, g, b, 1.0f)); + } +} + +SOKOL_API_IMPL void sgl_v3f_c3b(float x, float y, float z, uint8_t r, uint8_t g, uint8_t b) { + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + _sgl_vtx(ctx, x, y, z, ctx->u, ctx->v, _sgl_pack_rgbab(r, g, b, 255)); + } +} + +SOKOL_API_IMPL void sgl_v3f_c4f(float x, float y, float z, float r, float g, float b, float a) { + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + _sgl_vtx(ctx, x, y, z, ctx->u, ctx->v, _sgl_pack_rgbaf(r, g, b, a)); + } +} + +SOKOL_API_IMPL void sgl_v3f_c4b(float x, float y, float z, uint8_t r, uint8_t g, uint8_t b, uint8_t a) { + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + _sgl_vtx(ctx, x, y, z, ctx->u, ctx->v, _sgl_pack_rgbab(r, g, b, a)); + } +} + +SOKOL_API_IMPL void sgl_v3f_c1i(float x, float y, float z, uint32_t rgba) { + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + _sgl_vtx(ctx, x, y, z, ctx->u, ctx->v, rgba); + } +} + +SOKOL_API_IMPL void sgl_v2f_t2f_c3f(float x, float y, float u, float v, float r, float g, float b) { + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + _sgl_vtx(ctx, x, y, 0.0f, u, v, _sgl_pack_rgbaf(r, g, b, 1.0f)); + } +} + +SOKOL_API_IMPL void sgl_v2f_t2f_c3b(float x, float y, float u, float v, uint8_t r, uint8_t g, uint8_t b) { + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + _sgl_vtx(ctx, x, y, 0.0f, u, v, _sgl_pack_rgbab(r, g, b, 255)); + } +} + +SOKOL_API_IMPL void sgl_v2f_t2f_c4f(float x, float y, float u, float v, float r, float g, float b, float a) { + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + _sgl_vtx(ctx, x, y, 0.0f, u, v, _sgl_pack_rgbaf(r, g, b, a)); + } +} + +SOKOL_API_IMPL void sgl_v2f_t2f_c4b(float x, float y, float u, float v, uint8_t r, uint8_t g, uint8_t b, uint8_t a) { + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + _sgl_vtx(ctx, x, y, 0.0f, u, v, _sgl_pack_rgbab(r, g, b, a)); + } +} + +SOKOL_API_IMPL void sgl_v2f_t2f_c1i(float x, float y, float u, float v, uint32_t rgba) { + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + _sgl_vtx(ctx, x, y, 0.0f, u, v, rgba); + } +} + +SOKOL_API_IMPL void sgl_v3f_t2f_c3f(float x, float y, float z, float u, float v, float r, float g, float b) { + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + _sgl_vtx(ctx, x, y, z, u, v, _sgl_pack_rgbaf(r, g, b, 1.0f)); + } +} + +SOKOL_API_IMPL void sgl_v3f_t2f_c3b(float x, float y, float z, float u, float v, uint8_t r, uint8_t g, uint8_t b) { + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + _sgl_vtx(ctx, x, y, z, u, v, _sgl_pack_rgbab(r, g, b, 255)); + } +} + +SOKOL_API_IMPL void sgl_v3f_t2f_c4f(float x, float y, float z, float u, float v, float r, float g, float b, float a) { + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + _sgl_vtx(ctx, x, y, z, u, v, _sgl_pack_rgbaf(r, g, b, a)); + } +} + +SOKOL_API_IMPL void sgl_v3f_t2f_c4b(float x, float y, float z, float u, float v, uint8_t r, uint8_t g, uint8_t b, uint8_t a) { + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + _sgl_vtx(ctx, x, y, z, u, v, _sgl_pack_rgbab(r, g, b, a)); + } +} + +SOKOL_API_IMPL void sgl_v3f_t2f_c1i(float x, float y, float z, float u, float v, uint32_t rgba) { + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + _sgl_vtx(ctx,x, y, z, u, v, rgba); + } +} + +SOKOL_API_IMPL void sgl_matrix_mode_modelview(void) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + ctx->cur_matrix_mode = SGL_MATRIXMODE_MODELVIEW; + } +} + +SOKOL_API_IMPL void sgl_matrix_mode_projection(void) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + ctx->cur_matrix_mode = SGL_MATRIXMODE_PROJECTION; + } +} + +SOKOL_API_IMPL void sgl_matrix_mode_texture(void) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + ctx->cur_matrix_mode = SGL_MATRIXMODE_TEXTURE; + } +} + +SOKOL_API_IMPL void sgl_load_identity(void) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (!ctx) { + return; + } + ctx->matrix_dirty = true; + _sgl_identity(_sgl_matrix(ctx)); +} + +SOKOL_API_IMPL void sgl_load_matrix(const float m[16]) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (!ctx) { + return; + } + ctx->matrix_dirty = true; + memcpy(&_sgl_matrix(ctx)->v[0][0], &m[0], 64); +} + +SOKOL_API_IMPL void sgl_load_transpose_matrix(const float m[16]) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (!ctx) { + return; + } + ctx->matrix_dirty = true; + _sgl_transpose(_sgl_matrix(ctx), (const _sgl_matrix_t*) &m[0]); +} + +SOKOL_API_IMPL void sgl_mult_matrix(const float m[16]) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (!ctx) { + return; + } + ctx->matrix_dirty = true; + const _sgl_matrix_t* m0 = (const _sgl_matrix_t*) &m[0]; + _sgl_mul(_sgl_matrix(ctx), m0); +} + +SOKOL_API_IMPL void sgl_mult_transpose_matrix(const float m[16]) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (!ctx) { + return; + } + ctx->matrix_dirty = true; + _sgl_matrix_t m0; + _sgl_transpose(&m0, (const _sgl_matrix_t*) &m[0]); + _sgl_mul(_sgl_matrix(ctx), &m0); +} + +SOKOL_API_IMPL void sgl_rotate(float angle_rad, float x, float y, float z) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (!ctx) { + return; + } + ctx->matrix_dirty = true; + _sgl_rotate(_sgl_matrix(ctx), angle_rad, x, y, z); +} + +SOKOL_API_IMPL void sgl_scale(float x, float y, float z) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (!ctx) { + return; + } + ctx->matrix_dirty = true; + _sgl_scale(_sgl_matrix(ctx), x, y, z); +} + +SOKOL_API_IMPL void sgl_translate(float x, float y, float z) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (!ctx) { + return; + } + ctx->matrix_dirty = true; + _sgl_translate(_sgl_matrix(ctx), x, y, z); +} + +SOKOL_API_IMPL void sgl_frustum(float l, float r, float b, float t, float n, float f) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (!ctx) { + return; + } + ctx->matrix_dirty = true; + _sgl_frustum(_sgl_matrix(ctx), l, r, b, t, n, f); +} + +SOKOL_API_IMPL void sgl_ortho(float l, float r, float b, float t, float n, float f) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (!ctx) { + return; + } + ctx->matrix_dirty = true; + _sgl_ortho(_sgl_matrix(ctx), l, r, b, t, n, f); +} + +SOKOL_API_IMPL void sgl_perspective(float fov_y, float aspect, float z_near, float z_far) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (!ctx) { + return; + } + ctx->matrix_dirty = true; + _sgl_perspective(_sgl_matrix(ctx), fov_y, aspect, z_near, z_far); +} + +SOKOL_API_IMPL void sgl_lookat(float eye_x, float eye_y, float eye_z, float center_x, float center_y, float center_z, float up_x, float up_y, float up_z) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (!ctx) { + return; + } + ctx->matrix_dirty = true; + _sgl_lookat(_sgl_matrix(ctx), eye_x, eye_y, eye_z, center_x, center_y, center_z, up_x, up_y, up_z); +} + +SOKOL_GL_API_DECL void sgl_push_matrix(void) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (!ctx) { + return; + } + SOKOL_ASSERT((ctx->cur_matrix_mode >= 0) && (ctx->cur_matrix_mode < SGL_NUM_MATRIXMODES)); + ctx->matrix_dirty = true; + if (ctx->matrix_tos[ctx->cur_matrix_mode] < (_SGL_MAX_STACK_DEPTH - 1)) { + const _sgl_matrix_t* src = _sgl_matrix(ctx); + ctx->matrix_tos[ctx->cur_matrix_mode]++; + _sgl_matrix_t* dst = _sgl_matrix(ctx); + *dst = *src; + } else { + ctx->error.stack_overflow = true; + ctx->error.any = true; + } +} + +SOKOL_GL_API_DECL void sgl_pop_matrix(void) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (!ctx) { + return; + } + SOKOL_ASSERT((ctx->cur_matrix_mode >= 0) && (ctx->cur_matrix_mode < SGL_NUM_MATRIXMODES)); + ctx->matrix_dirty = true; + if (ctx->matrix_tos[ctx->cur_matrix_mode] > 0) { + ctx->matrix_tos[ctx->cur_matrix_mode]--; + } else { + ctx->error.stack_underflow = true; + ctx->error.any = true; + } +} + +SOKOL_API_IMPL void sgl_draw(void) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + _sgl_draw(ctx, 0); + } +} + +SOKOL_API_IMPL void sgl_draw_layer(int layer_id) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + _sgl_draw(ctx, layer_id); + } +} + +SOKOL_API_IMPL void sgl_context_draw(sgl_context ctx_id) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl_lookup_context(ctx_id.id); + if (ctx) { + _sgl_draw(ctx, 0); + } +} + +SOKOL_API_IMPL void sgl_context_draw_layer(sgl_context ctx_id, int layer_id) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl_lookup_context(ctx_id.id); + if (ctx) { + _sgl_draw(ctx, layer_id); + } +} + +#endif /* SOKOL_GL_IMPL */ diff --git a/third-party/sokol/sokol_glue.h b/third-party/sokol/sokol_glue.h new file mode 100644 index 0000000..a715b17 --- /dev/null +++ b/third-party/sokol/sokol_glue.h @@ -0,0 +1,162 @@ +#if defined(SOKOL_IMPL) && !defined(SOKOL_GLUE_IMPL) +#define SOKOL_GLUE_IMPL +#endif +#ifndef SOKOL_GLUE_INCLUDED +/* + sokol_glue.h -- glue helper functions for sokol headers + + Project URL: https://github.com/floooh/sokol + + Do this: + #define SOKOL_IMPL or + #define SOKOL_GLUE_IMPL + before you include this file in *one* C or C++ file to create the + implementation. + + ...optionally provide the following macros to override defaults: + + SOKOL_ASSERT(c) - your own assert macro (default: assert(c)) + SOKOL_GLUE_API_DECL - public function declaration prefix (default: extern) + SOKOL_API_DECL - same as SOKOL_GLUE_API_DECL + SOKOL_API_IMPL - public function implementation prefix (default: -) + + If sokol_glue.h is compiled as a DLL, define the following before + including the declaration or implementation: + + SOKOL_DLL + + On Windows, SOKOL_DLL will define SOKOL_GLUE_API_DECL as __declspec(dllexport) + or __declspec(dllimport) as needed. + + OVERVIEW + ======== + sokol_glue.h provides glue helper functions between sokol_gfx.h and sokol_app.h, + so that sokol_gfx.h doesn't need to depend on sokol_app.h but can be + used with different window system glue libraries. + + PROVIDED FUNCTIONS + ================== + + sg_environment sglue_environment(void) + + Returns an sg_environment struct initialized by calling sokol_app.h + functions. Use this in the sg_setup() call like this: + + sg_setup(&(sg_desc){ + .environment = sglue_environment(), + ... + }); + + sg_swapchain sglue_swapchain(void) + + Returns an sg_swapchain struct initialized by calling sokol_app.h + functions. Use this in sg_begin_pass() for a 'swapchain pass' like + this: + + sg_begin_pass(&(sg_pass){ .swapchain = sglue_swapchain(), ... }); + + LICENSE + ======= + zlib/libpng license + + Copyright (c) 2018 Andre Weissflog + + This software is provided 'as-is', without any express or implied warranty. + In no event will the authors be held liable for any damages arising from the + use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software in a + product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not + be misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ +#define SOKOL_GLUE_INCLUDED + +#if defined(SOKOL_API_DECL) && !defined(SOKOL_GLUE_API_DECL) +#define SOKOL_GLUE_API_DECL SOKOL_API_DECL +#endif +#ifndef SOKOL_GLUE_API_DECL +#if defined(_WIN32) && defined(SOKOL_DLL) && defined(SOKOL_GLUE_IMPL) +#define SOKOL_GLUE_API_DECL __declspec(dllexport) +#elif defined(_WIN32) && defined(SOKOL_DLL) +#define SOKOL_GLUE_API_DECL __declspec(dllimport) +#else +#define SOKOL_GLUE_API_DECL extern +#endif +#endif + +#ifndef SOKOL_GFX_INCLUDED +#error "Please include sokol_gfx.h before sokol_glue.h" +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +SOKOL_GLUE_API_DECL sg_environment sglue_environment(void); +SOKOL_GLUE_API_DECL sg_swapchain sglue_swapchain(void); + +#ifdef __cplusplus +} /* extern "C" */ +#endif +#endif /* SOKOL_GLUE_INCLUDED */ + +/*-- IMPLEMENTATION ----------------------------------------------------------*/ +#ifdef SOKOL_GLUE_IMPL +#define SOKOL_GLUE_IMPL_INCLUDED (1) +#include /* memset */ + +#ifndef SOKOL_APP_INCLUDED +#error "Please include sokol_app.h before the sokol_glue.h implementation" +#endif + +#ifndef SOKOL_API_IMPL +#define SOKOL_API_IMPL +#endif + + +SOKOL_API_IMPL sg_environment sglue_environment(void) { + sg_environment env; + memset(&env, 0, sizeof(env)); + env.defaults.color_format = (sg_pixel_format) sapp_color_format(); + env.defaults.depth_format = (sg_pixel_format) sapp_depth_format(); + env.defaults.sample_count = sapp_sample_count(); + env.metal.device = sapp_metal_get_device(); + env.d3d11.device = sapp_d3d11_get_device(); + env.d3d11.device_context = sapp_d3d11_get_device_context(); + env.wgpu.device = sapp_wgpu_get_device(); + return env; +} + +SOKOL_API_IMPL sg_swapchain sglue_swapchain(void) { + sg_swapchain swapchain; + memset(&swapchain, 0, sizeof(swapchain)); + swapchain.width = sapp_width(); + swapchain.height = sapp_height(); + swapchain.sample_count = sapp_sample_count(); + swapchain.color_format = (sg_pixel_format)sapp_color_format(); + swapchain.depth_format = (sg_pixel_format)sapp_depth_format(); + swapchain.metal.current_drawable = sapp_metal_get_current_drawable(); + swapchain.metal.depth_stencil_texture = sapp_metal_get_depth_stencil_texture(); + swapchain.metal.msaa_color_texture = sapp_metal_get_msaa_color_texture(); + swapchain.d3d11.render_view = sapp_d3d11_get_render_view(); + swapchain.d3d11.resolve_view = sapp_d3d11_get_resolve_view(); + swapchain.d3d11.depth_stencil_view = sapp_d3d11_get_depth_stencil_view(); + swapchain.wgpu.render_view = sapp_wgpu_get_render_view(); + swapchain.wgpu.resolve_view = sapp_wgpu_get_resolve_view(); + swapchain.wgpu.depth_stencil_view = sapp_wgpu_get_depth_stencil_view(); + swapchain.gl.framebuffer = sapp_gl_get_framebuffer(); + return swapchain; +} + +#endif /* SOKOL_GLUE_IMPL */ diff --git a/third-party/sokol/sokol_log.h b/third-party/sokol/sokol_log.h new file mode 100644 index 0000000..081ea0d --- /dev/null +++ b/third-party/sokol/sokol_log.h @@ -0,0 +1,334 @@ +#if defined(SOKOL_IMPL) && !defined(SOKOL_LOG_IMPL) +#define SOKOL_LOG_IMPL +#endif +#ifndef SOKOL_LOG_INCLUDED +/* + sokol_log.h -- common logging callback for sokol headers + + Project URL: https://github.com/floooh/sokol + + Example code: https://github.com/floooh/sokol-samples + + Do this: + #define SOKOL_IMPL or + #define SOKOL_LOG_IMPL + before you include this file in *one* C or C++ file to create the + implementation. + + Optionally provide the following defines when building the implementation: + + SOKOL_ASSERT(c) - your own assert macro (default: assert(c)) + SOKOL_UNREACHABLE() - a guard macro for unreachable code (default: assert(false)) + SOKOL_LOG_API_DECL - public function declaration prefix (default: extern) + SOKOL_API_DECL - same as SOKOL_GFX_API_DECL + SOKOL_API_IMPL - public function implementation prefix (default: -) + + Optionally define the following for verbose output: + + SOKOL_DEBUG - by default this is defined if NDEBUG is not defined + + + OVERVIEW + ======== + sokol_log.h provides a default logging callback for other sokol headers. + + To use the default log callback, just include sokol_log.h and provide + a function pointer to the 'slog_func' function when setting up the + sokol library: + + For instance with sokol_audio.h: + + #include "sokol_log.h" + ... + saudio_setup(&(saudio_desc){ .logger.func = slog_func }); + + Logging output goes to stderr and/or a platform specific logging subsystem + (which means that in some scenarios you might see logging messages duplicated): + + - Windows: stderr + OutputDebugStringA() + - macOS/iOS/Linux: stderr + syslog() + - Emscripten: console.info()/warn()/error() + - Android: __android_log_write() + + On Windows with sokol_app.h also note the runtime config items to make + stdout/stderr output visible on the console for WinMain() applications + via sapp_desc.win32_console_attach or sapp_desc.win32_console_create, + however when running in a debugger on Windows, the logging output should + show up on the debug output UI panel. + + In debug mode, a log message might look like this: + + [sspine][error][id:12] /Users/floh/projects/sokol/util/sokol_spine.h:3472:0: + SKELETON_DESC_NO_ATLAS: no atlas object provided in sspine_skeleton_desc.atlas + + The source path and line number is formatted like compiler errors, in some IDEs (like VSCode) + such error messages are clickable. + + In release mode, logging is less verbose as to not bloat the executable with string data, but you still get + enough information to identify the type and location of an error: + + [sspine][error][id:12][line:3472] + + RULES FOR WRITING YOUR OWN LOGGING FUNCTION + =========================================== + - must be re-entrant because it might be called from different threads + - must treat **all** provided string pointers as optional (can be null) + - don't store the string pointers, copy the string data instead + - must not return for log level panic + + LICENSE + ======= + zlib/libpng license + + Copyright (c) 2023 Andre Weissflog + + This software is provided 'as-is', without any express or implied warranty. + In no event will the authors be held liable for any damages arising from the + use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software in a + product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not + be misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ +#define SOKOL_LOG_INCLUDED (1) +#include + +#if defined(SOKOL_API_DECL) && !defined(SOKOL_LOG_API_DECL) +#define SOKOL_LOG_API_DECL SOKOL_API_DECL +#endif +#ifndef SOKOL_LOG_API_DECL +#if defined(_WIN32) && defined(SOKOL_DLL) && defined(SOKOL_LOG_IMPL) +#define SOKOL_LOG_API_DECL __declspec(dllexport) +#elif defined(_WIN32) && defined(SOKOL_DLL) +#define SOKOL_LOG_API_DECL __declspec(dllimport) +#else +#define SOKOL_LOG_API_DECL extern +#endif +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/* + Plug this function into the 'logger.func' struct item when initializing any of the sokol + headers. For instance for sokol_audio.h it would look like this: + + saudio_setup(&(saudio_desc){ + .logger = { + .func = slog_func + } + }); +*/ +SOKOL_LOG_API_DECL void slog_func(const char* tag, uint32_t log_level, uint32_t log_item, const char* message, uint32_t line_nr, const char* filename, void* user_data); + +#ifdef __cplusplus +} // extern "C" +#endif +#endif // SOKOL_LOG_INCLUDED + +// ██ ███ ███ ██████ ██ ███████ ███ ███ ███████ ███ ██ ████████ █████ ████████ ██ ██████ ███ ██ +// ██ ████ ████ ██ ██ ██ ██ ████ ████ ██ ████ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██ +// ██ ██ ████ ██ ██████ ██ █████ ██ ████ ██ █████ ██ ██ ██ ██ ███████ ██ ██ ██ ██ ██ ██ ██ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██ ██ ██ ██ ███████ ███████ ██ ██ ███████ ██ ████ ██ ██ ██ ██ ██ ██████ ██ ████ +// +// >>implementation +#ifdef SOKOL_LOG_IMPL +#define SOKOL_LOG_IMPL_INCLUDED (1) + +#ifndef SOKOL_API_IMPL + #define SOKOL_API_IMPL +#endif +#ifndef SOKOL_DEBUG + #ifndef NDEBUG + #define SOKOL_DEBUG + #endif +#endif +#ifndef SOKOL_ASSERT + #include + #define SOKOL_ASSERT(c) assert(c) +#endif + +#ifndef _SOKOL_PRIVATE + #if defined(__GNUC__) || defined(__clang__) + #define _SOKOL_PRIVATE __attribute__((unused)) static + #else + #define _SOKOL_PRIVATE static + #endif +#endif + +#ifndef _SOKOL_UNUSED + #define _SOKOL_UNUSED(x) (void)(x) +#endif + +// platform detection +#if defined(__APPLE__) + #define _SLOG_APPLE (1) +#elif defined(__EMSCRIPTEN__) + #define _SLOG_EMSCRIPTEN (1) +#elif defined(_WIN32) + #define _SLOG_WINDOWS (1) +#elif defined(__ANDROID__) + #define _SLOG_ANDROID (1) +#elif defined(__linux__) || defined(__unix__) + #define _SLOG_LINUX (1) +#else +#error "sokol_log.h: unknown platform" +#endif + +#include // abort +#include // fputs +#include // size_t + +#if defined(_SLOG_EMSCRIPTEN) +#include +#elif defined(_SLOG_WINDOWS) +#ifndef WIN32_LEAN_AND_MEAN + #define WIN32_LEAN_AND_MEAN +#endif +#ifndef NOMINMAX + #define NOMINMAX +#endif +#include +#elif defined(_SLOG_ANDROID) +#include +#elif defined(_SLOG_LINUX) || defined(_SLOG_APPLE) +#include +#endif + +// size of line buffer (on stack!) in bytes including terminating zero +#define _SLOG_LINE_LENGTH (512) + +_SOKOL_PRIVATE char* _slog_append(const char* str, char* dst, char* end) { + if (str) { + char c; + while (((c = *str++) != 0) && (dst < (end - 1))) { + *dst++ = c; + } + } + *dst = 0; + return dst; +} + +_SOKOL_PRIVATE char* _slog_itoa(uint32_t x, char* buf, size_t buf_size) { + const size_t max_digits_and_null = 11; + if (buf_size < max_digits_and_null) { + return 0; + } + char* p = buf + max_digits_and_null; + *--p = 0; + do { + *--p = '0' + (x % 10); + x /= 10; + } while (x != 0); + return p; +} + +#if defined(_SLOG_EMSCRIPTEN) +EM_JS(void, slog_js_log, (uint32_t level, const char* c_str), { + const str = UTF8ToString(c_str); + switch (level) { + case 0: console.error(str); break; + case 1: console.error(str); break; + case 2: console.warn(str); break; + default: console.info(str); break; + } +}) +#endif + +SOKOL_API_IMPL void slog_func(const char* tag, uint32_t log_level, uint32_t log_item, const char* message, uint32_t line_nr, const char* filename, void* user_data) { + _SOKOL_UNUSED(user_data); + + const char* log_level_str; + switch (log_level) { + case 0: log_level_str = "panic"; break; + case 1: log_level_str = "error"; break; + case 2: log_level_str = "warning"; break; + default: log_level_str = "info"; break; + } + + // build log output line + char line_buf[_SLOG_LINE_LENGTH]; + char* str = line_buf; + char* end = line_buf + sizeof(line_buf); + char num_buf[32]; + if (tag) { + str = _slog_append("[", str, end); + str = _slog_append(tag, str, end); + str = _slog_append("]", str, end); + } + str = _slog_append("[", str, end); + str = _slog_append(log_level_str, str, end); + str = _slog_append("]", str, end); + str = _slog_append("[id:", str, end); + str = _slog_append(_slog_itoa(log_item, num_buf, sizeof(num_buf)), str, end); + str = _slog_append("]", str, end); + // if a filename is provided, build a clickable log message that's compatible with compiler error messages + if (filename) { + str = _slog_append(" ", str, end); + #if defined(_MSC_VER) + // MSVC compiler error format + str = _slog_append(filename, str, end); + str = _slog_append("(", str, end); + str = _slog_append(_slog_itoa(line_nr, num_buf, sizeof(num_buf)), str, end); + str = _slog_append("): ", str, end); + #else + // gcc/clang compiler error format + str = _slog_append(filename, str, end); + str = _slog_append(":", str, end); + str = _slog_append(_slog_itoa(line_nr, num_buf, sizeof(num_buf)), str, end); + str = _slog_append(":0: ", str, end); + #endif + } + else { + str = _slog_append("[line:", str, end); + str = _slog_append(_slog_itoa(line_nr, num_buf, sizeof(num_buf)), str, end); + str = _slog_append("] ", str, end); + } + if (message) { + str = _slog_append("\n\t", str, end); + str = _slog_append(message, str, end); + } + str = _slog_append("\n\n", str, end); + if (0 == log_level) { + str = _slog_append("ABORTING because of [panic]\n", str, end); + (void)str; + } + + // print to stderr? + #if defined(_SLOG_LINUX) || defined(_SLOG_WINDOWS) || defined(_SLOG_APPLE) + fputs(line_buf, stderr); + #endif + + // platform specific logging calls + #if defined(_SLOG_WINDOWS) + OutputDebugStringA(line_buf); + #elif defined(_SLOG_ANDROID) + int prio; + switch (log_level) { + case 0: prio = ANDROID_LOG_FATAL; break; + case 1: prio = ANDROID_LOG_ERROR; break; + case 2: prio = ANDROID_LOG_WARN; break; + default: prio = ANDROID_LOG_INFO; break; + } + __android_log_write(prio, "SOKOL", line_buf); + #elif defined(_SLOG_EMSCRIPTEN) + slog_js_log(log_level, line_buf); + #endif + if (0 == log_level) { + abort(); + } +} +#endif // SOKOL_LOG_IMPL -- cgit v1.2.3