From 20a44faafa3686aaa45cb853394a104bd496c165 Mon Sep 17 00:00:00 2001 From: Abner Coimbre Date: Thu, 29 Jan 2026 17:55:20 -0800 Subject: [PATCH] Initial project --- build.zig | 191 + data/images/majora.png | Bin 0 -> 983231 bytes data/images/perlin.png | Bin 0 -> 151833 bytes data/shaders/sample-effect.glsl | 47 + data/shaders/sample-effect.glsl.h | 1488 ++ inc/sokol/sokol_app.h | 12097 +++++++++++++++ inc/sokol/sokol_gfx.h | 22314 ++++++++++++++++++++++++++++ inc/sokol/sokol_glue.h | 162 + inc/sokol/sokol_gp.h | 3044 ++++ inc/sokol/sokol_log.h | 343 + inc/sokol/sokol_time.h | 319 + inc/stb/stb_image.h | 6755 +++++++++ main.c | 206 + 13 files changed, 46966 insertions(+) create mode 100644 build.zig create mode 100644 data/images/majora.png create mode 100644 data/images/perlin.png create mode 100644 data/shaders/sample-effect.glsl create mode 100644 data/shaders/sample-effect.glsl.h create mode 100644 inc/sokol/sokol_app.h create mode 100644 inc/sokol/sokol_gfx.h create mode 100644 inc/sokol/sokol_glue.h create mode 100644 inc/sokol/sokol_gp.h create mode 100644 inc/sokol/sokol_log.h create mode 100644 inc/sokol/sokol_time.h create mode 100644 inc/stb/stb_image.h create mode 100644 main.c diff --git a/build.zig b/build.zig new file mode 100644 index 0000000..a6cd9c6 --- /dev/null +++ b/build.zig @@ -0,0 +1,191 @@ +// Majora's Screensaver (expects zig v0.15.x) +// @author: Abner Coimbre +// @description: Sample C app for Windows, Mac & Linux. Adapted from floooh's sokol examples. + +// The goal is to demonstrate how to write a cross-platform native app/game in vanilla C, using only Zig to compile. + +// Single-Header Libraries: +// - Sokol (https://github.com/floooh/sokol): Windowing, input, and/or graphics +// - STB (https://github.com/nothings/stb): Loading images/assets + +// Instructions +// --- +// 1. Ensure Zig is on your PATH: download from https://ziglang.org/download +// 2. Navigate to this project's folder +// 3. Run the `zig build` command +// 4. For a macOS App Bundle try `zig build bundle` instead +// --- +// You may now run the executable under zig-out/ + +// The command `zig build` executes build() below. Learn more at https://zig.guide/ + +const std = @import("std"); + +pub fn build(b: *std.Build) void { + // Initial exe + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + const exe = b.addExecutable(.{ + .name = "screensaver", + .root_module = b.createModule(.{ + .optimize = optimize, + .target = target, + }), + }); + + // Compiler flags: update as needed + const win_flags = &[_][]const u8{ + "-DSOKOL_D3D11", + "-O0", + "-ggdb3", + "-std=c99", + "-Werror", // Without this we can't emit warnings. This is a Zig bug: https://github.com/ziglang/zig/issues/15912 + }; + + const macos_flags = &[_][]const u8{ + "-ObjC", + "-DSOKOL_METAL", + "-O0", + "-ggdb3", + "-std=c99", + "-Werror", + }; + + const linux_flags = &[_][]const u8{ + "-DSOKOL_GLCORE33", + "-O0", + "-ggdb3", + "-std=c99", + "-Werror", + }; + + // Add include folder(s) + exe.addIncludePath(b.path("inc")); + exe.addIncludePath(b.path("data/shaders")); + + // Which OS are we in? + const tag = target.result.os.tag; + + // Select appropriate compiler flags + const target_flags = switch (tag) { + .windows => win_flags, + .macos => macos_flags, + .linux => linux_flags, + else => { + std.debug.print("Error: Unsupported OS! Contact abner@terminal.click\n", .{}); + std.process.exit(1); + }, + }; + + // Add our source code + exe.addCSourceFile(.{ .file = b.path("main.c"), .flags = target_flags }); + + // Link against C's standard library + exe.linkLibC(); + + // Link against appropriate system libraries + switch (tag) { + .windows => { + exe.linkSystemLibrary("kernel32"); + exe.linkSystemLibrary("gdi32"); + exe.linkSystemLibrary("d3d11"); + }, + + .macos => { + exe.linkFramework("MetalKit"); + exe.linkFramework("Metal"); + exe.linkFramework("Cocoa"); + exe.linkFramework("QuartzCore"); + }, + + .linux => { + exe.linkSystemLibrary("GL"); + exe.linkSystemLibrary("X11"); + exe.linkSystemLibrary("Xi"); + exe.linkSystemLibrary("Xcursor"); + }, + + else => unreachable, + } + + b.installArtifact(exe); + + // + // macOS App Bundle - prepares one if user called `zig build bundle` + // + // --- + if (tag == .macos) { + const app_name = "Screensaver"; + const bundle_path = b.fmt("zig-out/{s}.app/Contents", .{app_name}); + + const mkdir = b.addSystemCommand(&.{ + "mkdir", "-p", + b.fmt("{s}/MacOS", .{bundle_path}), + }); + + const mkdir_res = b.addSystemCommand(&.{ + "mkdir", "-p", + b.fmt("{s}/Resources", .{bundle_path}), + }); + + const copy_exe = b.addSystemCommand(&.{ + "cp", + b.getInstallPath(.bin, "screensaver"), + b.fmt("{s}/MacOS/screensaver", .{bundle_path}), + }); + copy_exe.step.dependOn(&mkdir.step); + copy_exe.step.dependOn(b.getInstallStep()); + + const plist_content = + \\ + \\ + \\ + \\ + \\ CFBundleExecutable + \\ screensaver + \\ CFBundleIdentifier + \\ click.terminal.screensaver + \\ CFBundleName + \\ Screensaver + \\ CFBundlePackageType + \\ APPL + \\ CFBundleVersion + \\ 1.0 + \\ + \\ + ; + + const write_files = b.addWriteFiles(); + const plist_file = write_files.add("Info.plist", plist_content); + const copy_plist = b.addInstallFile(plist_file, b.fmt("{s}.app/Contents/Info.plist", .{app_name})); + + const copy_data = b.addSystemCommand(&.{ + "cp", "-R", + "data", + b.fmt("{s}/Resources/", .{bundle_path}), + }); + copy_data.step.dependOn(&mkdir_res.step); + + const bundle_step = b.step("bundle", "Create macOS app bundle"); + bundle_step.dependOn(©_exe.step); + bundle_step.dependOn(©_plist.step); + bundle_step.dependOn(&mkdir_res.step); + bundle_step.dependOn(©_data.step); + bundle_step.dependOn(©_plist.step); + } + // --- + + // + // We're done! Complete Zig's build graph + // + //--- + const run_cmd = b.addRunArtifact(exe); + run_cmd.step.dependOn(b.getInstallStep()); + if (b.args) |args| { + run_cmd.addArgs(args); + } + + const run_step = b.step("run", "Run the app"); + run_step.dependOn(&run_cmd.step); + //--- +} diff --git a/data/images/majora.png b/data/images/majora.png new file mode 100644 index 0000000000000000000000000000000000000000..ffc12fe8450f671fc6ed06f60df920c7dc588a84 GIT binary patch literal 983231 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 S(WipzJt2m^>QHNiB@iKg+sj}Hwi)^$R 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

CH}ye2EHR!)ZlX_XupZ(-d*#iR6 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�F)+C?1YiI2LVk#Z5jz(pSs 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%0DxxP!7XoR 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 z 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

^d{Nj$v#DY1F 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 @Vce!qVl3j_`wGPGoZdcbM5I(m)Frk(ek2~ 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

j2X1JZj(6x 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

  • @gWjc_v?6jR&kS$lJXO1K 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

    !h%q_+ffb-6B=a#|H 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⋘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 +?UYmk*SfUd%~&IU39 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

    0g3&ELEi*QTSAw(-@T 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%0D7E5s?kjKAh>iMJX|sRn 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 0 + ATTR_program_color => 1 + Bindings: + Uniform block 'fs_uniforms': + C struct: effect_fs_uniforms_t + Bind slot: UB_fs_uniforms => 1 + Image 'iTexChannel1': + Image type: SG_IMAGETYPE_2D + Sample type: SG_IMAGESAMPLETYPE_FLOAT + Multisampled: false + Bind slot: IMG_iTexChannel1 => 1 + Image 'iTexChannel0': + Image type: SG_IMAGETYPE_2D + Sample type: SG_IMAGESAMPLETYPE_FLOAT + Multisampled: false + Bind slot: IMG_iTexChannel0 => 0 + Sampler 'iSmpChannel1': + Type: SG_SAMPLERTYPE_FILTERING + Bind slot: SMP_iSmpChannel1 => 1 + Sampler 'iSmpChannel0': + Type: SG_SAMPLERTYPE_FILTERING + Bind slot: SMP_iSmpChannel0 => 0 +*/ +#if !defined(SOKOL_GFX_INCLUDED) +#error "Please include sokol_gfx.h before sample-effect.glsl.h" +#endif +#if !defined(SOKOL_SHDC_ALIGN) +#if defined(_MSC_VER) +#define SOKOL_SHDC_ALIGN(a) __declspec(align(a)) +#else +#define SOKOL_SHDC_ALIGN(a) __attribute__((aligned(a))) +#endif +#endif +const sg_shader_desc* effect_program_shader_desc(sg_backend backend); +#define ATTR_program_coord (0) +#define ATTR_program_color (1) +#define UB_fs_uniforms (1) +#define IMG_iTexChannel1 (1) +#define IMG_iTexChannel0 (0) +#define SMP_iSmpChannel1 (1) +#define SMP_iSmpChannel0 (0) +#pragma pack(push,1) +SOKOL_SHDC_ALIGN(16) typedef struct effect_fs_uniforms_t { + sgp_vec2 iVelocity; + float iPressure; + float iTime; + float iWarpiness; + float iRatio; + float iZoom; + float iLevel; +} effect_fs_uniforms_t; +#pragma pack(pop) +#if defined(SOKOL_SHDC_IMPL) +/* + #version 410 + + layout(location = 0) in vec4 coord; + layout(location = 0) out vec2 texUV; + layout(location = 1) out vec4 iColor; + layout(location = 1) in vec4 color; + + void main() + { + gl_Position = vec4(coord.xy, 0.0, 1.0); + texUV = coord.zw; + iColor = color; + } + +*/ +static const uint8_t effect_vs_source_glsl410[266] = { + 0x23,0x76,0x65,0x72,0x73,0x69,0x6f,0x6e,0x20,0x34,0x31,0x30,0x0a,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,0x63,0x6f,0x6f,0x72,0x64, + 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,0x32,0x20, + 0x74,0x65,0x78,0x55,0x56,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,0x69,0x43,0x6f,0x6c,0x6f,0x72,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,0x67,0x6c,0x5f,0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x20, + 0x3d,0x20,0x76,0x65,0x63,0x34,0x28,0x63,0x6f,0x6f,0x72,0x64,0x2e,0x78,0x79,0x2c, + 0x20,0x30,0x2e,0x30,0x2c,0x20,0x31,0x2e,0x30,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20, + 0x74,0x65,0x78,0x55,0x56,0x20,0x3d,0x20,0x63,0x6f,0x6f,0x72,0x64,0x2e,0x7a,0x77, + 0x3b,0x0a,0x20,0x20,0x20,0x20,0x69,0x43,0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20,0x63, + 0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x7d,0x0a,0x0a,0x00, +}; +/* + #version 410 + + uniform vec4 fs_uniforms[2]; + uniform sampler2D iTexChannel0_iSmpChannel0; + uniform sampler2D iTexChannel1_iSmpChannel1; + + layout(location = 0) in vec2 texUV; + layout(location = 1) in vec4 iColor; + layout(location = 0) out vec4 fragColor; + + float _noise(vec2 p) + { + return texture(iTexChannel1_iSmpChannel1, p).x; + } + + void main() + { + vec2 _60 = (texUV * vec2(fs_uniforms[1].y, 1.0)) * fs_uniforms[1].z; + vec2 param = _60 - (fs_uniforms[0].xy * fs_uniforms[0].w); + vec2 param_1 = _60 + vec2(_noise(param) * fs_uniforms[1].x); + fragColor = vec4(mix(texture(iTexChannel0_iSmpChannel0, texUV).xyz, vec3(_noise(param_1)) * iColor.xyz, vec3(fs_uniforms[0].z)), 1.0); + } + +*/ +static const uint8_t effect_fs_source_glsl410[685] = { + 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,0x66,0x73,0x5f,0x75,0x6e, + 0x69,0x66,0x6f,0x72,0x6d,0x73,0x5b,0x32,0x5d,0x3b,0x0a,0x75,0x6e,0x69,0x66,0x6f, + 0x72,0x6d,0x20,0x73,0x61,0x6d,0x70,0x6c,0x65,0x72,0x32,0x44,0x20,0x69,0x54,0x65, + 0x78,0x43,0x68,0x61,0x6e,0x6e,0x65,0x6c,0x30,0x5f,0x69,0x53,0x6d,0x70,0x43,0x68, + 0x61,0x6e,0x6e,0x65,0x6c,0x30,0x3b,0x0a,0x75,0x6e,0x69,0x66,0x6f,0x72,0x6d,0x20, + 0x73,0x61,0x6d,0x70,0x6c,0x65,0x72,0x32,0x44,0x20,0x69,0x54,0x65,0x78,0x43,0x68, + 0x61,0x6e,0x6e,0x65,0x6c,0x31,0x5f,0x69,0x53,0x6d,0x70,0x43,0x68,0x61,0x6e,0x6e, + 0x65,0x6c,0x31,0x3b,0x0a,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,0x32,0x20,0x74,0x65,0x78,0x55,0x56,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,0x69,0x43,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,0x6f,0x75,0x74,0x20,0x76,0x65,0x63,0x34,0x20,0x66,0x72,0x61, + 0x67,0x43,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x0a,0x66,0x6c,0x6f,0x61,0x74,0x20,0x5f, + 0x6e,0x6f,0x69,0x73,0x65,0x28,0x76,0x65,0x63,0x32,0x20,0x70,0x29,0x0a,0x7b,0x0a, + 0x20,0x20,0x20,0x20,0x72,0x65,0x74,0x75,0x72,0x6e,0x20,0x74,0x65,0x78,0x74,0x75, + 0x72,0x65,0x28,0x69,0x54,0x65,0x78,0x43,0x68,0x61,0x6e,0x6e,0x65,0x6c,0x31,0x5f, + 0x69,0x53,0x6d,0x70,0x43,0x68,0x61,0x6e,0x6e,0x65,0x6c,0x31,0x2c,0x20,0x70,0x29, + 0x2e,0x78,0x3b,0x0a,0x7d,0x0a,0x0a,0x76,0x6f,0x69,0x64,0x20,0x6d,0x61,0x69,0x6e, + 0x28,0x29,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x76,0x65,0x63,0x32,0x20,0x5f,0x36, + 0x30,0x20,0x3d,0x20,0x28,0x74,0x65,0x78,0x55,0x56,0x20,0x2a,0x20,0x76,0x65,0x63, + 0x32,0x28,0x66,0x73,0x5f,0x75,0x6e,0x69,0x66,0x6f,0x72,0x6d,0x73,0x5b,0x31,0x5d, + 0x2e,0x79,0x2c,0x20,0x31,0x2e,0x30,0x29,0x29,0x20,0x2a,0x20,0x66,0x73,0x5f,0x75, + 0x6e,0x69,0x66,0x6f,0x72,0x6d,0x73,0x5b,0x31,0x5d,0x2e,0x7a,0x3b,0x0a,0x20,0x20, + 0x20,0x20,0x76,0x65,0x63,0x32,0x20,0x70,0x61,0x72,0x61,0x6d,0x20,0x3d,0x20,0x5f, + 0x36,0x30,0x20,0x2d,0x20,0x28,0x66,0x73,0x5f,0x75,0x6e,0x69,0x66,0x6f,0x72,0x6d, + 0x73,0x5b,0x30,0x5d,0x2e,0x78,0x79,0x20,0x2a,0x20,0x66,0x73,0x5f,0x75,0x6e,0x69, + 0x66,0x6f,0x72,0x6d,0x73,0x5b,0x30,0x5d,0x2e,0x77,0x29,0x3b,0x0a,0x20,0x20,0x20, + 0x20,0x76,0x65,0x63,0x32,0x20,0x70,0x61,0x72,0x61,0x6d,0x5f,0x31,0x20,0x3d,0x20, + 0x5f,0x36,0x30,0x20,0x2b,0x20,0x76,0x65,0x63,0x32,0x28,0x5f,0x6e,0x6f,0x69,0x73, + 0x65,0x28,0x70,0x61,0x72,0x61,0x6d,0x29,0x20,0x2a,0x20,0x66,0x73,0x5f,0x75,0x6e, + 0x69,0x66,0x6f,0x72,0x6d,0x73,0x5b,0x31,0x5d,0x2e,0x78,0x29,0x3b,0x0a,0x20,0x20, + 0x20,0x20,0x66,0x72,0x61,0x67,0x43,0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20,0x76,0x65, + 0x63,0x34,0x28,0x6d,0x69,0x78,0x28,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x28,0x69, + 0x54,0x65,0x78,0x43,0x68,0x61,0x6e,0x6e,0x65,0x6c,0x30,0x5f,0x69,0x53,0x6d,0x70, + 0x43,0x68,0x61,0x6e,0x6e,0x65,0x6c,0x30,0x2c,0x20,0x74,0x65,0x78,0x55,0x56,0x29, + 0x2e,0x78,0x79,0x7a,0x2c,0x20,0x76,0x65,0x63,0x33,0x28,0x5f,0x6e,0x6f,0x69,0x73, + 0x65,0x28,0x70,0x61,0x72,0x61,0x6d,0x5f,0x31,0x29,0x29,0x20,0x2a,0x20,0x69,0x43, + 0x6f,0x6c,0x6f,0x72,0x2e,0x78,0x79,0x7a,0x2c,0x20,0x76,0x65,0x63,0x33,0x28,0x66, + 0x73,0x5f,0x75,0x6e,0x69,0x66,0x6f,0x72,0x6d,0x73,0x5b,0x30,0x5d,0x2e,0x7a,0x29, + 0x29,0x2c,0x20,0x31,0x2e,0x30,0x29,0x3b,0x0a,0x7d,0x0a,0x0a,0x00, +}; +/* + #version 300 es + + layout(location = 0) in vec4 coord; + out vec2 texUV; + out vec4 iColor; + layout(location = 1) in vec4 color; + + void main() + { + gl_Position = vec4(coord.xy, 0.0, 1.0); + texUV = coord.zw; + iColor = color; + } + +*/ +static const uint8_t effect_vs_source_glsl300es[227] = { + 0x23,0x76,0x65,0x72,0x73,0x69,0x6f,0x6e,0x20,0x33,0x30,0x30,0x20,0x65,0x73,0x0a, + 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,0x63,0x6f, + 0x6f,0x72,0x64,0x3b,0x0a,0x6f,0x75,0x74,0x20,0x76,0x65,0x63,0x32,0x20,0x74,0x65, + 0x78,0x55,0x56,0x3b,0x0a,0x6f,0x75,0x74,0x20,0x76,0x65,0x63,0x34,0x20,0x69,0x43, + 0x6f,0x6c,0x6f,0x72,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,0x67,0x6c,0x5f, + 0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x76,0x65,0x63,0x34,0x28, + 0x63,0x6f,0x6f,0x72,0x64,0x2e,0x78,0x79,0x2c,0x20,0x30,0x2e,0x30,0x2c,0x20,0x31, + 0x2e,0x30,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x74,0x65,0x78,0x55,0x56,0x20,0x3d, + 0x20,0x63,0x6f,0x6f,0x72,0x64,0x2e,0x7a,0x77,0x3b,0x0a,0x20,0x20,0x20,0x20,0x69, + 0x43,0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x7d, + 0x0a,0x0a,0x00, +}; +/* + #version 300 es + precision mediump float; + precision highp int; + + uniform highp vec4 fs_uniforms[2]; + uniform highp sampler2D iTexChannel0_iSmpChannel0; + uniform highp sampler2D iTexChannel1_iSmpChannel1; + + in highp vec2 texUV; + in highp vec4 iColor; + layout(location = 0) out highp vec4 fragColor; + + highp float _noise(highp vec2 p) + { + return texture(iTexChannel1_iSmpChannel1, p).x; + } + + void main() + { + highp vec2 _60 = (texUV * vec2(fs_uniforms[1].y, 1.0)) * fs_uniforms[1].z; + highp vec2 param = _60 - (fs_uniforms[0].xy * fs_uniforms[0].w); + highp vec2 param_1 = _60 + vec2(_noise(param) * fs_uniforms[1].x); + fragColor = vec4(mix(texture(iTexChannel0_iSmpChannel0, texUV).xyz, vec3(_noise(param_1)) * iColor.xyz, vec3(fs_uniforms[0].z)), 1.0); + } + +*/ +static const uint8_t effect_fs_source_glsl300es[758] = { + 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,0x76,0x65,0x63, + 0x34,0x20,0x66,0x73,0x5f,0x75,0x6e,0x69,0x66,0x6f,0x72,0x6d,0x73,0x5b,0x32,0x5d, + 0x3b,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,0x69,0x54,0x65,0x78,0x43,0x68, + 0x61,0x6e,0x6e,0x65,0x6c,0x30,0x5f,0x69,0x53,0x6d,0x70,0x43,0x68,0x61,0x6e,0x6e, + 0x65,0x6c,0x30,0x3b,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,0x69,0x54,0x65, + 0x78,0x43,0x68,0x61,0x6e,0x6e,0x65,0x6c,0x31,0x5f,0x69,0x53,0x6d,0x70,0x43,0x68, + 0x61,0x6e,0x6e,0x65,0x6c,0x31,0x3b,0x0a,0x0a,0x69,0x6e,0x20,0x68,0x69,0x67,0x68, + 0x70,0x20,0x76,0x65,0x63,0x32,0x20,0x74,0x65,0x78,0x55,0x56,0x3b,0x0a,0x69,0x6e, + 0x20,0x68,0x69,0x67,0x68,0x70,0x20,0x76,0x65,0x63,0x34,0x20,0x69,0x43,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,0x6f,0x75,0x74,0x20,0x68,0x69,0x67, + 0x68,0x70,0x20,0x76,0x65,0x63,0x34,0x20,0x66,0x72,0x61,0x67,0x43,0x6f,0x6c,0x6f, + 0x72,0x3b,0x0a,0x0a,0x68,0x69,0x67,0x68,0x70,0x20,0x66,0x6c,0x6f,0x61,0x74,0x20, + 0x5f,0x6e,0x6f,0x69,0x73,0x65,0x28,0x68,0x69,0x67,0x68,0x70,0x20,0x76,0x65,0x63, + 0x32,0x20,0x70,0x29,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x72,0x65,0x74,0x75,0x72, + 0x6e,0x20,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x28,0x69,0x54,0x65,0x78,0x43,0x68, + 0x61,0x6e,0x6e,0x65,0x6c,0x31,0x5f,0x69,0x53,0x6d,0x70,0x43,0x68,0x61,0x6e,0x6e, + 0x65,0x6c,0x31,0x2c,0x20,0x70,0x29,0x2e,0x78,0x3b,0x0a,0x7d,0x0a,0x0a,0x76,0x6f, + 0x69,0x64,0x20,0x6d,0x61,0x69,0x6e,0x28,0x29,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20, + 0x68,0x69,0x67,0x68,0x70,0x20,0x76,0x65,0x63,0x32,0x20,0x5f,0x36,0x30,0x20,0x3d, + 0x20,0x28,0x74,0x65,0x78,0x55,0x56,0x20,0x2a,0x20,0x76,0x65,0x63,0x32,0x28,0x66, + 0x73,0x5f,0x75,0x6e,0x69,0x66,0x6f,0x72,0x6d,0x73,0x5b,0x31,0x5d,0x2e,0x79,0x2c, + 0x20,0x31,0x2e,0x30,0x29,0x29,0x20,0x2a,0x20,0x66,0x73,0x5f,0x75,0x6e,0x69,0x66, + 0x6f,0x72,0x6d,0x73,0x5b,0x31,0x5d,0x2e,0x7a,0x3b,0x0a,0x20,0x20,0x20,0x20,0x68, + 0x69,0x67,0x68,0x70,0x20,0x76,0x65,0x63,0x32,0x20,0x70,0x61,0x72,0x61,0x6d,0x20, + 0x3d,0x20,0x5f,0x36,0x30,0x20,0x2d,0x20,0x28,0x66,0x73,0x5f,0x75,0x6e,0x69,0x66, + 0x6f,0x72,0x6d,0x73,0x5b,0x30,0x5d,0x2e,0x78,0x79,0x20,0x2a,0x20,0x66,0x73,0x5f, + 0x75,0x6e,0x69,0x66,0x6f,0x72,0x6d,0x73,0x5b,0x30,0x5d,0x2e,0x77,0x29,0x3b,0x0a, + 0x20,0x20,0x20,0x20,0x68,0x69,0x67,0x68,0x70,0x20,0x76,0x65,0x63,0x32,0x20,0x70, + 0x61,0x72,0x61,0x6d,0x5f,0x31,0x20,0x3d,0x20,0x5f,0x36,0x30,0x20,0x2b,0x20,0x76, + 0x65,0x63,0x32,0x28,0x5f,0x6e,0x6f,0x69,0x73,0x65,0x28,0x70,0x61,0x72,0x61,0x6d, + 0x29,0x20,0x2a,0x20,0x66,0x73,0x5f,0x75,0x6e,0x69,0x66,0x6f,0x72,0x6d,0x73,0x5b, + 0x31,0x5d,0x2e,0x78,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,0x72,0x61,0x67,0x43, + 0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20,0x76,0x65,0x63,0x34,0x28,0x6d,0x69,0x78,0x28, + 0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x28,0x69,0x54,0x65,0x78,0x43,0x68,0x61,0x6e, + 0x6e,0x65,0x6c,0x30,0x5f,0x69,0x53,0x6d,0x70,0x43,0x68,0x61,0x6e,0x6e,0x65,0x6c, + 0x30,0x2c,0x20,0x74,0x65,0x78,0x55,0x56,0x29,0x2e,0x78,0x79,0x7a,0x2c,0x20,0x76, + 0x65,0x63,0x33,0x28,0x5f,0x6e,0x6f,0x69,0x73,0x65,0x28,0x70,0x61,0x72,0x61,0x6d, + 0x5f,0x31,0x29,0x29,0x20,0x2a,0x20,0x69,0x43,0x6f,0x6c,0x6f,0x72,0x2e,0x78,0x79, + 0x7a,0x2c,0x20,0x76,0x65,0x63,0x33,0x28,0x66,0x73,0x5f,0x75,0x6e,0x69,0x66,0x6f, + 0x72,0x6d,0x73,0x5b,0x30,0x5d,0x2e,0x7a,0x29,0x29,0x2c,0x20,0x31,0x2e,0x30,0x29, + 0x3b,0x0a,0x7d,0x0a,0x0a,0x00, +}; +/* + static float4 gl_Position; + static float4 coord; + static float2 texUV; + static float4 iColor; + static float4 color; + + struct SPIRV_Cross_Input + { + float4 coord : TEXCOORD0; + float4 color : TEXCOORD1; + }; + + struct SPIRV_Cross_Output + { + float2 texUV : TEXCOORD0; + float4 iColor : TEXCOORD1; + float4 gl_Position : SV_Position; + }; + + void vert_main() + { + gl_Position = float4(coord.xy, 0.0f, 1.0f); + texUV = coord.zw; + iColor = color; + } + + SPIRV_Cross_Output main(SPIRV_Cross_Input stage_input) + { + coord = stage_input.coord; + color = stage_input.color; + vert_main(); + SPIRV_Cross_Output stage_output; + stage_output.gl_Position = gl_Position; + stage_output.texUV = texUV; + stage_output.iColor = iColor; + return stage_output; + } +*/ +static const uint8_t effect_vs_source_hlsl4[758] = { + 0x73,0x74,0x61,0x74,0x69,0x63,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20,0x67,0x6c, + 0x5f,0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x3b,0x0a,0x73,0x74,0x61,0x74,0x69, + 0x63,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20,0x63,0x6f,0x6f,0x72,0x64,0x3b,0x0a, + 0x73,0x74,0x61,0x74,0x69,0x63,0x20,0x66,0x6c,0x6f,0x61,0x74,0x32,0x20,0x74,0x65, + 0x78,0x55,0x56,0x3b,0x0a,0x73,0x74,0x61,0x74,0x69,0x63,0x20,0x66,0x6c,0x6f,0x61, + 0x74,0x34,0x20,0x69,0x43,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x73,0x74,0x61,0x74,0x69, + 0x63,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x3b,0x0a, + 0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x53,0x50,0x49,0x52,0x56,0x5f,0x43,0x72, + 0x6f,0x73,0x73,0x5f,0x49,0x6e,0x70,0x75,0x74,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20, + 0x66,0x6c,0x6f,0x61,0x74,0x34,0x20,0x63,0x6f,0x6f,0x72,0x64,0x20,0x3a,0x20,0x54, + 0x45,0x58,0x43,0x4f,0x4f,0x52,0x44,0x30,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c, + 0x6f,0x61,0x74,0x34,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x20,0x3a,0x20,0x54,0x45,0x58, + 0x43,0x4f,0x4f,0x52,0x44,0x31,0x3b,0x0a,0x7d,0x3b,0x0a,0x0a,0x73,0x74,0x72,0x75, + 0x63,0x74,0x20,0x53,0x50,0x49,0x52,0x56,0x5f,0x43,0x72,0x6f,0x73,0x73,0x5f,0x4f, + 0x75,0x74,0x70,0x75,0x74,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61, + 0x74,0x32,0x20,0x74,0x65,0x78,0x55,0x56,0x20,0x3a,0x20,0x54,0x45,0x58,0x43,0x4f, + 0x4f,0x52,0x44,0x30,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34, + 0x20,0x69,0x43,0x6f,0x6c,0x6f,0x72,0x20,0x3a,0x20,0x54,0x45,0x58,0x43,0x4f,0x4f, + 0x52,0x44,0x31,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,0x3a,0x20,0x53,0x56, + 0x5f,0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x3b,0x0a,0x7d,0x3b,0x0a,0x0a,0x76, + 0x6f,0x69,0x64,0x20,0x76,0x65,0x72,0x74,0x5f,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,0x66,0x6c,0x6f,0x61,0x74,0x34,0x28,0x63,0x6f,0x6f,0x72,0x64, + 0x2e,0x78,0x79,0x2c,0x20,0x30,0x2e,0x30,0x66,0x2c,0x20,0x31,0x2e,0x30,0x66,0x29, + 0x3b,0x0a,0x20,0x20,0x20,0x20,0x74,0x65,0x78,0x55,0x56,0x20,0x3d,0x20,0x63,0x6f, + 0x6f,0x72,0x64,0x2e,0x7a,0x77,0x3b,0x0a,0x20,0x20,0x20,0x20,0x69,0x43,0x6f,0x6c, + 0x6f,0x72,0x20,0x3d,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x7d,0x0a,0x0a,0x53, + 0x50,0x49,0x52,0x56,0x5f,0x43,0x72,0x6f,0x73,0x73,0x5f,0x4f,0x75,0x74,0x70,0x75, + 0x74,0x20,0x6d,0x61,0x69,0x6e,0x28,0x53,0x50,0x49,0x52,0x56,0x5f,0x43,0x72,0x6f, + 0x73,0x73,0x5f,0x49,0x6e,0x70,0x75,0x74,0x20,0x73,0x74,0x61,0x67,0x65,0x5f,0x69, + 0x6e,0x70,0x75,0x74,0x29,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x63,0x6f,0x6f,0x72, + 0x64,0x20,0x3d,0x20,0x73,0x74,0x61,0x67,0x65,0x5f,0x69,0x6e,0x70,0x75,0x74,0x2e, + 0x63,0x6f,0x6f,0x72,0x64,0x3b,0x0a,0x20,0x20,0x20,0x20,0x63,0x6f,0x6c,0x6f,0x72, + 0x20,0x3d,0x20,0x73,0x74,0x61,0x67,0x65,0x5f,0x69,0x6e,0x70,0x75,0x74,0x2e,0x63, + 0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x20,0x20,0x20,0x20,0x76,0x65,0x72,0x74,0x5f,0x6d, + 0x61,0x69,0x6e,0x28,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x53,0x50,0x49,0x52,0x56, + 0x5f,0x43,0x72,0x6f,0x73,0x73,0x5f,0x4f,0x75,0x74,0x70,0x75,0x74,0x20,0x73,0x74, + 0x61,0x67,0x65,0x5f,0x6f,0x75,0x74,0x70,0x75,0x74,0x3b,0x0a,0x20,0x20,0x20,0x20, + 0x73,0x74,0x61,0x67,0x65,0x5f,0x6f,0x75,0x74,0x70,0x75,0x74,0x2e,0x67,0x6c,0x5f, + 0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x67,0x6c,0x5f,0x50,0x6f, + 0x73,0x69,0x74,0x69,0x6f,0x6e,0x3b,0x0a,0x20,0x20,0x20,0x20,0x73,0x74,0x61,0x67, + 0x65,0x5f,0x6f,0x75,0x74,0x70,0x75,0x74,0x2e,0x74,0x65,0x78,0x55,0x56,0x20,0x3d, + 0x20,0x74,0x65,0x78,0x55,0x56,0x3b,0x0a,0x20,0x20,0x20,0x20,0x73,0x74,0x61,0x67, + 0x65,0x5f,0x6f,0x75,0x74,0x70,0x75,0x74,0x2e,0x69,0x43,0x6f,0x6c,0x6f,0x72,0x20, + 0x3d,0x20,0x69,0x43,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x20,0x20,0x20,0x20,0x72,0x65, + 0x74,0x75,0x72,0x6e,0x20,0x73,0x74,0x61,0x67,0x65,0x5f,0x6f,0x75,0x74,0x70,0x75, + 0x74,0x3b,0x0a,0x7d,0x0a,0x00, +}; +/* + cbuffer fs_uniforms : register(b0) + { + float2 _48_iVelocity : packoffset(c0); + float _48_iPressure : packoffset(c0.z); + float _48_iTime : packoffset(c0.w); + float _48_iWarpiness : packoffset(c1); + float _48_iRatio : packoffset(c1.y); + float _48_iZoom : packoffset(c1.z); + float _48_iLevel : packoffset(c1.w); + }; + + Texture2D iTexChannel1 : register(t0); + SamplerState iSmpChannel1 : register(s0); + Texture2D iTexChannel0 : register(t1); + SamplerState iSmpChannel0 : register(s1); + + static float2 texUV; + static float4 iColor; + static float4 fragColor; + + struct SPIRV_Cross_Input + { + float2 texUV : TEXCOORD0; + float4 iColor : TEXCOORD1; + }; + + struct SPIRV_Cross_Output + { + float4 fragColor : SV_Target0; + }; + + float _noise(float2 p) + { + return iTexChannel1.Sample(iSmpChannel1, p).x; + } + + void frag_main() + { + float2 _60 = (texUV * float2(_48_iRatio, 1.0f)) * _48_iZoom; + float2 param = _60 - (_48_iVelocity * _48_iTime); + float2 param_1 = _60 + (_noise(param) * _48_iWarpiness).xx; + fragColor = float4(lerp(iTexChannel0.Sample(iSmpChannel0, texUV).xyz, _noise(param_1).xxx * iColor.xyz, _48_iPressure.xxx), 1.0f); + } + + SPIRV_Cross_Output main(SPIRV_Cross_Input stage_input) + { + texUV = stage_input.texUV; + iColor = stage_input.iColor; + frag_main(); + SPIRV_Cross_Output stage_output; + stage_output.fragColor = fragColor; + return stage_output; + } +*/ +static const uint8_t effect_fs_source_hlsl4[1402] = { + 0x63,0x62,0x75,0x66,0x66,0x65,0x72,0x20,0x66,0x73,0x5f,0x75,0x6e,0x69,0x66,0x6f, + 0x72,0x6d,0x73,0x20,0x3a,0x20,0x72,0x65,0x67,0x69,0x73,0x74,0x65,0x72,0x28,0x62, + 0x30,0x29,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x32,0x20, + 0x5f,0x34,0x38,0x5f,0x69,0x56,0x65,0x6c,0x6f,0x63,0x69,0x74,0x79,0x20,0x3a,0x20, + 0x70,0x61,0x63,0x6b,0x6f,0x66,0x66,0x73,0x65,0x74,0x28,0x63,0x30,0x29,0x3b,0x0a, + 0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x20,0x5f,0x34,0x38,0x5f,0x69,0x50, + 0x72,0x65,0x73,0x73,0x75,0x72,0x65,0x20,0x3a,0x20,0x70,0x61,0x63,0x6b,0x6f,0x66, + 0x66,0x73,0x65,0x74,0x28,0x63,0x30,0x2e,0x7a,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20, + 0x66,0x6c,0x6f,0x61,0x74,0x20,0x5f,0x34,0x38,0x5f,0x69,0x54,0x69,0x6d,0x65,0x20, + 0x3a,0x20,0x70,0x61,0x63,0x6b,0x6f,0x66,0x66,0x73,0x65,0x74,0x28,0x63,0x30,0x2e, + 0x77,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x20,0x5f,0x34, + 0x38,0x5f,0x69,0x57,0x61,0x72,0x70,0x69,0x6e,0x65,0x73,0x73,0x20,0x3a,0x20,0x70, + 0x61,0x63,0x6b,0x6f,0x66,0x66,0x73,0x65,0x74,0x28,0x63,0x31,0x29,0x3b,0x0a,0x20, + 0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x20,0x5f,0x34,0x38,0x5f,0x69,0x52,0x61, + 0x74,0x69,0x6f,0x20,0x3a,0x20,0x70,0x61,0x63,0x6b,0x6f,0x66,0x66,0x73,0x65,0x74, + 0x28,0x63,0x31,0x2e,0x79,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61, + 0x74,0x20,0x5f,0x34,0x38,0x5f,0x69,0x5a,0x6f,0x6f,0x6d,0x20,0x3a,0x20,0x70,0x61, + 0x63,0x6b,0x6f,0x66,0x66,0x73,0x65,0x74,0x28,0x63,0x31,0x2e,0x7a,0x29,0x3b,0x0a, + 0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x20,0x5f,0x34,0x38,0x5f,0x69,0x4c, + 0x65,0x76,0x65,0x6c,0x20,0x3a,0x20,0x70,0x61,0x63,0x6b,0x6f,0x66,0x66,0x73,0x65, + 0x74,0x28,0x63,0x31,0x2e,0x77,0x29,0x3b,0x0a,0x7d,0x3b,0x0a,0x0a,0x54,0x65,0x78, + 0x74,0x75,0x72,0x65,0x32,0x44,0x3c,0x66,0x6c,0x6f,0x61,0x74,0x34,0x3e,0x20,0x69, + 0x54,0x65,0x78,0x43,0x68,0x61,0x6e,0x6e,0x65,0x6c,0x31,0x20,0x3a,0x20,0x72,0x65, + 0x67,0x69,0x73,0x74,0x65,0x72,0x28,0x74,0x30,0x29,0x3b,0x0a,0x53,0x61,0x6d,0x70, + 0x6c,0x65,0x72,0x53,0x74,0x61,0x74,0x65,0x20,0x69,0x53,0x6d,0x70,0x43,0x68,0x61, + 0x6e,0x6e,0x65,0x6c,0x31,0x20,0x3a,0x20,0x72,0x65,0x67,0x69,0x73,0x74,0x65,0x72, + 0x28,0x73,0x30,0x29,0x3b,0x0a,0x54,0x65,0x78,0x74,0x75,0x72,0x65,0x32,0x44,0x3c, + 0x66,0x6c,0x6f,0x61,0x74,0x34,0x3e,0x20,0x69,0x54,0x65,0x78,0x43,0x68,0x61,0x6e, + 0x6e,0x65,0x6c,0x30,0x20,0x3a,0x20,0x72,0x65,0x67,0x69,0x73,0x74,0x65,0x72,0x28, + 0x74,0x31,0x29,0x3b,0x0a,0x53,0x61,0x6d,0x70,0x6c,0x65,0x72,0x53,0x74,0x61,0x74, + 0x65,0x20,0x69,0x53,0x6d,0x70,0x43,0x68,0x61,0x6e,0x6e,0x65,0x6c,0x30,0x20,0x3a, + 0x20,0x72,0x65,0x67,0x69,0x73,0x74,0x65,0x72,0x28,0x73,0x31,0x29,0x3b,0x0a,0x0a, + 0x73,0x74,0x61,0x74,0x69,0x63,0x20,0x66,0x6c,0x6f,0x61,0x74,0x32,0x20,0x74,0x65, + 0x78,0x55,0x56,0x3b,0x0a,0x73,0x74,0x61,0x74,0x69,0x63,0x20,0x66,0x6c,0x6f,0x61, + 0x74,0x34,0x20,0x69,0x43,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x73,0x74,0x61,0x74,0x69, + 0x63,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20,0x66,0x72,0x61,0x67,0x43,0x6f,0x6c, + 0x6f,0x72,0x3b,0x0a,0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x53,0x50,0x49,0x52, + 0x56,0x5f,0x43,0x72,0x6f,0x73,0x73,0x5f,0x49,0x6e,0x70,0x75,0x74,0x0a,0x7b,0x0a, + 0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x32,0x20,0x74,0x65,0x78,0x55,0x56, + 0x20,0x3a,0x20,0x54,0x45,0x58,0x43,0x4f,0x4f,0x52,0x44,0x30,0x3b,0x0a,0x20,0x20, + 0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20,0x69,0x43,0x6f,0x6c,0x6f,0x72,0x20, + 0x3a,0x20,0x54,0x45,0x58,0x43,0x4f,0x4f,0x52,0x44,0x31,0x3b,0x0a,0x7d,0x3b,0x0a, + 0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x53,0x50,0x49,0x52,0x56,0x5f,0x43,0x72, + 0x6f,0x73,0x73,0x5f,0x4f,0x75,0x74,0x70,0x75,0x74,0x0a,0x7b,0x0a,0x20,0x20,0x20, + 0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20,0x66,0x72,0x61,0x67,0x43,0x6f,0x6c,0x6f, + 0x72,0x20,0x3a,0x20,0x53,0x56,0x5f,0x54,0x61,0x72,0x67,0x65,0x74,0x30,0x3b,0x0a, + 0x7d,0x3b,0x0a,0x0a,0x66,0x6c,0x6f,0x61,0x74,0x20,0x5f,0x6e,0x6f,0x69,0x73,0x65, + 0x28,0x66,0x6c,0x6f,0x61,0x74,0x32,0x20,0x70,0x29,0x0a,0x7b,0x0a,0x20,0x20,0x20, + 0x20,0x72,0x65,0x74,0x75,0x72,0x6e,0x20,0x69,0x54,0x65,0x78,0x43,0x68,0x61,0x6e, + 0x6e,0x65,0x6c,0x31,0x2e,0x53,0x61,0x6d,0x70,0x6c,0x65,0x28,0x69,0x53,0x6d,0x70, + 0x43,0x68,0x61,0x6e,0x6e,0x65,0x6c,0x31,0x2c,0x20,0x70,0x29,0x2e,0x78,0x3b,0x0a, + 0x7d,0x0a,0x0a,0x76,0x6f,0x69,0x64,0x20,0x66,0x72,0x61,0x67,0x5f,0x6d,0x61,0x69, + 0x6e,0x28,0x29,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x32, + 0x20,0x5f,0x36,0x30,0x20,0x3d,0x20,0x28,0x74,0x65,0x78,0x55,0x56,0x20,0x2a,0x20, + 0x66,0x6c,0x6f,0x61,0x74,0x32,0x28,0x5f,0x34,0x38,0x5f,0x69,0x52,0x61,0x74,0x69, + 0x6f,0x2c,0x20,0x31,0x2e,0x30,0x66,0x29,0x29,0x20,0x2a,0x20,0x5f,0x34,0x38,0x5f, + 0x69,0x5a,0x6f,0x6f,0x6d,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74, + 0x32,0x20,0x70,0x61,0x72,0x61,0x6d,0x20,0x3d,0x20,0x5f,0x36,0x30,0x20,0x2d,0x20, + 0x28,0x5f,0x34,0x38,0x5f,0x69,0x56,0x65,0x6c,0x6f,0x63,0x69,0x74,0x79,0x20,0x2a, + 0x20,0x5f,0x34,0x38,0x5f,0x69,0x54,0x69,0x6d,0x65,0x29,0x3b,0x0a,0x20,0x20,0x20, + 0x20,0x66,0x6c,0x6f,0x61,0x74,0x32,0x20,0x70,0x61,0x72,0x61,0x6d,0x5f,0x31,0x20, + 0x3d,0x20,0x5f,0x36,0x30,0x20,0x2b,0x20,0x28,0x5f,0x6e,0x6f,0x69,0x73,0x65,0x28, + 0x70,0x61,0x72,0x61,0x6d,0x29,0x20,0x2a,0x20,0x5f,0x34,0x38,0x5f,0x69,0x57,0x61, + 0x72,0x70,0x69,0x6e,0x65,0x73,0x73,0x29,0x2e,0x78,0x78,0x3b,0x0a,0x20,0x20,0x20, + 0x20,0x66,0x72,0x61,0x67,0x43,0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20,0x66,0x6c,0x6f, + 0x61,0x74,0x34,0x28,0x6c,0x65,0x72,0x70,0x28,0x69,0x54,0x65,0x78,0x43,0x68,0x61, + 0x6e,0x6e,0x65,0x6c,0x30,0x2e,0x53,0x61,0x6d,0x70,0x6c,0x65,0x28,0x69,0x53,0x6d, + 0x70,0x43,0x68,0x61,0x6e,0x6e,0x65,0x6c,0x30,0x2c,0x20,0x74,0x65,0x78,0x55,0x56, + 0x29,0x2e,0x78,0x79,0x7a,0x2c,0x20,0x5f,0x6e,0x6f,0x69,0x73,0x65,0x28,0x70,0x61, + 0x72,0x61,0x6d,0x5f,0x31,0x29,0x2e,0x78,0x78,0x78,0x20,0x2a,0x20,0x69,0x43,0x6f, + 0x6c,0x6f,0x72,0x2e,0x78,0x79,0x7a,0x2c,0x20,0x5f,0x34,0x38,0x5f,0x69,0x50,0x72, + 0x65,0x73,0x73,0x75,0x72,0x65,0x2e,0x78,0x78,0x78,0x29,0x2c,0x20,0x31,0x2e,0x30, + 0x66,0x29,0x3b,0x0a,0x7d,0x0a,0x0a,0x53,0x50,0x49,0x52,0x56,0x5f,0x43,0x72,0x6f, + 0x73,0x73,0x5f,0x4f,0x75,0x74,0x70,0x75,0x74,0x20,0x6d,0x61,0x69,0x6e,0x28,0x53, + 0x50,0x49,0x52,0x56,0x5f,0x43,0x72,0x6f,0x73,0x73,0x5f,0x49,0x6e,0x70,0x75,0x74, + 0x20,0x73,0x74,0x61,0x67,0x65,0x5f,0x69,0x6e,0x70,0x75,0x74,0x29,0x0a,0x7b,0x0a, + 0x20,0x20,0x20,0x20,0x74,0x65,0x78,0x55,0x56,0x20,0x3d,0x20,0x73,0x74,0x61,0x67, + 0x65,0x5f,0x69,0x6e,0x70,0x75,0x74,0x2e,0x74,0x65,0x78,0x55,0x56,0x3b,0x0a,0x20, + 0x20,0x20,0x20,0x69,0x43,0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20,0x73,0x74,0x61,0x67, + 0x65,0x5f,0x69,0x6e,0x70,0x75,0x74,0x2e,0x69,0x43,0x6f,0x6c,0x6f,0x72,0x3b,0x0a, + 0x20,0x20,0x20,0x20,0x66,0x72,0x61,0x67,0x5f,0x6d,0x61,0x69,0x6e,0x28,0x29,0x3b, + 0x0a,0x20,0x20,0x20,0x20,0x53,0x50,0x49,0x52,0x56,0x5f,0x43,0x72,0x6f,0x73,0x73, + 0x5f,0x4f,0x75,0x74,0x70,0x75,0x74,0x20,0x73,0x74,0x61,0x67,0x65,0x5f,0x6f,0x75, + 0x74,0x70,0x75,0x74,0x3b,0x0a,0x20,0x20,0x20,0x20,0x73,0x74,0x61,0x67,0x65,0x5f, + 0x6f,0x75,0x74,0x70,0x75,0x74,0x2e,0x66,0x72,0x61,0x67,0x43,0x6f,0x6c,0x6f,0x72, + 0x20,0x3d,0x20,0x66,0x72,0x61,0x67,0x43,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x20,0x20, + 0x20,0x20,0x72,0x65,0x74,0x75,0x72,0x6e,0x20,0x73,0x74,0x61,0x67,0x65,0x5f,0x6f, + 0x75,0x74,0x70,0x75,0x74,0x3b,0x0a,0x7d,0x0a,0x00, +}; +/* + #include + #include + + using namespace metal; + + struct main0_out + { + float2 texUV [[user(locn0)]]; + float4 iColor [[user(locn1)]]; + float4 gl_Position [[position]]; + }; + + struct main0_in + { + float4 coord [[attribute(0)]]; + float4 color [[attribute(1)]]; + }; + + vertex main0_out main0(main0_in in [[stage_in]]) + { + main0_out out = {}; + out.gl_Position = float4(in.coord.xy, 0.0, 1.0); + out.texUV = in.coord.zw; + out.iColor = in.color; + return out; + } + +*/ +static const uint8_t effect_vs_source_metal_macos[497] = { + 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,0x32,0x20,0x74,0x65,0x78,0x55,0x56,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,0x69,0x43,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,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,0x63,0x6f,0x6f,0x72,0x64,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,0x34,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x20,0x5b, + 0x5b,0x61,0x74,0x74,0x72,0x69,0x62,0x75,0x74,0x65,0x28,0x31,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,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,0x66,0x6c,0x6f,0x61,0x74,0x34,0x28,0x69,0x6e,0x2e, + 0x63,0x6f,0x6f,0x72,0x64,0x2e,0x78,0x79,0x2c,0x20,0x30,0x2e,0x30,0x2c,0x20,0x31, + 0x2e,0x30,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x6f,0x75,0x74,0x2e,0x74,0x65,0x78, + 0x55,0x56,0x20,0x3d,0x20,0x69,0x6e,0x2e,0x63,0x6f,0x6f,0x72,0x64,0x2e,0x7a,0x77, + 0x3b,0x0a,0x20,0x20,0x20,0x20,0x6f,0x75,0x74,0x2e,0x69,0x43,0x6f,0x6c,0x6f,0x72, + 0x20,0x3d,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, +}; +/* + #pragma clang diagnostic ignored "-Wmissing-prototypes" + + #include + #include + + using namespace metal; + + struct fs_uniforms + { + float2 iVelocity; + float iPressure; + float iTime; + float iWarpiness; + float iRatio; + float iZoom; + float iLevel; + }; + + struct main0_out + { + float4 fragColor [[color(0)]]; + }; + + struct main0_in + { + float2 texUV [[user(locn0)]]; + float4 iColor [[user(locn1)]]; + }; + + static inline __attribute__((always_inline)) + float _noise(thread const float2& p, texture2d iTexChannel1, sampler iSmpChannel1) + { + return iTexChannel1.sample(iSmpChannel1, p).x; + } + + fragment main0_out main0(main0_in in [[stage_in]], constant fs_uniforms& _48 [[buffer(0)]], texture2d iTexChannel1 [[texture(0)]], texture2d iTexChannel0 [[texture(1)]], sampler iSmpChannel1 [[sampler(0)]], sampler iSmpChannel0 [[sampler(1)]]) + { + main0_out out = {}; + float2 _60 = (in.texUV * float2(_48.iRatio, 1.0)) * _48.iZoom; + float2 param = _60 - (_48.iVelocity * _48.iTime); + float2 param_1 = _60 + float2(_noise(param, iTexChannel1, iSmpChannel1) * _48.iWarpiness); + out.fragColor = float4(mix(iTexChannel0.sample(iSmpChannel0, in.texUV).xyz, float3(_noise(param_1, iTexChannel1, iSmpChannel1)) * in.iColor.xyz, float3(_48.iPressure)), 1.0); + return out; + } + +*/ +static const uint8_t effect_fs_source_metal_macos[1328] = { + 0x23,0x70,0x72,0x61,0x67,0x6d,0x61,0x20,0x63,0x6c,0x61,0x6e,0x67,0x20,0x64,0x69, + 0x61,0x67,0x6e,0x6f,0x73,0x74,0x69,0x63,0x20,0x69,0x67,0x6e,0x6f,0x72,0x65,0x64, + 0x20,0x22,0x2d,0x57,0x6d,0x69,0x73,0x73,0x69,0x6e,0x67,0x2d,0x70,0x72,0x6f,0x74, + 0x6f,0x74,0x79,0x70,0x65,0x73,0x22,0x0a,0x0a,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,0x66,0x73,0x5f,0x75,0x6e,0x69,0x66,0x6f, + 0x72,0x6d,0x73,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x32, + 0x20,0x69,0x56,0x65,0x6c,0x6f,0x63,0x69,0x74,0x79,0x3b,0x0a,0x20,0x20,0x20,0x20, + 0x66,0x6c,0x6f,0x61,0x74,0x20,0x69,0x50,0x72,0x65,0x73,0x73,0x75,0x72,0x65,0x3b, + 0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x20,0x69,0x54,0x69,0x6d,0x65, + 0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x20,0x69,0x57,0x61,0x72, + 0x70,0x69,0x6e,0x65,0x73,0x73,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61, + 0x74,0x20,0x69,0x52,0x61,0x74,0x69,0x6f,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c, + 0x6f,0x61,0x74,0x20,0x69,0x5a,0x6f,0x6f,0x6d,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66, + 0x6c,0x6f,0x61,0x74,0x20,0x69,0x4c,0x65,0x76,0x65,0x6c,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,0x66, + 0x72,0x61,0x67,0x43,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,0x32,0x20,0x74,0x65,0x78,0x55,0x56,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,0x69,0x43,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,0x73,0x74,0x61,0x74,0x69,0x63,0x20,0x69,0x6e,0x6c, + 0x69,0x6e,0x65,0x20,0x5f,0x5f,0x61,0x74,0x74,0x72,0x69,0x62,0x75,0x74,0x65,0x5f, + 0x5f,0x28,0x28,0x61,0x6c,0x77,0x61,0x79,0x73,0x5f,0x69,0x6e,0x6c,0x69,0x6e,0x65, + 0x29,0x29,0x0a,0x66,0x6c,0x6f,0x61,0x74,0x20,0x5f,0x6e,0x6f,0x69,0x73,0x65,0x28, + 0x74,0x68,0x72,0x65,0x61,0x64,0x20,0x63,0x6f,0x6e,0x73,0x74,0x20,0x66,0x6c,0x6f, + 0x61,0x74,0x32,0x26,0x20,0x70,0x2c,0x20,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x32, + 0x64,0x3c,0x66,0x6c,0x6f,0x61,0x74,0x3e,0x20,0x69,0x54,0x65,0x78,0x43,0x68,0x61, + 0x6e,0x6e,0x65,0x6c,0x31,0x2c,0x20,0x73,0x61,0x6d,0x70,0x6c,0x65,0x72,0x20,0x69, + 0x53,0x6d,0x70,0x43,0x68,0x61,0x6e,0x6e,0x65,0x6c,0x31,0x29,0x0a,0x7b,0x0a,0x20, + 0x20,0x20,0x20,0x72,0x65,0x74,0x75,0x72,0x6e,0x20,0x69,0x54,0x65,0x78,0x43,0x68, + 0x61,0x6e,0x6e,0x65,0x6c,0x31,0x2e,0x73,0x61,0x6d,0x70,0x6c,0x65,0x28,0x69,0x53, + 0x6d,0x70,0x43,0x68,0x61,0x6e,0x6e,0x65,0x6c,0x31,0x2c,0x20,0x70,0x29,0x2e,0x78, + 0x3b,0x0a,0x7d,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,0x63,0x6f,0x6e,0x73,0x74,0x61,0x6e,0x74, + 0x20,0x66,0x73,0x5f,0x75,0x6e,0x69,0x66,0x6f,0x72,0x6d,0x73,0x26,0x20,0x5f,0x34, + 0x38,0x20,0x5b,0x5b,0x62,0x75,0x66,0x66,0x65,0x72,0x28,0x30,0x29,0x5d,0x5d,0x2c, + 0x20,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x32,0x64,0x3c,0x66,0x6c,0x6f,0x61,0x74, + 0x3e,0x20,0x69,0x54,0x65,0x78,0x43,0x68,0x61,0x6e,0x6e,0x65,0x6c,0x31,0x20,0x5b, + 0x5b,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x28,0x30,0x29,0x5d,0x5d,0x2c,0x20,0x74, + 0x65,0x78,0x74,0x75,0x72,0x65,0x32,0x64,0x3c,0x66,0x6c,0x6f,0x61,0x74,0x3e,0x20, + 0x69,0x54,0x65,0x78,0x43,0x68,0x61,0x6e,0x6e,0x65,0x6c,0x30,0x20,0x5b,0x5b,0x74, + 0x65,0x78,0x74,0x75,0x72,0x65,0x28,0x31,0x29,0x5d,0x5d,0x2c,0x20,0x73,0x61,0x6d, + 0x70,0x6c,0x65,0x72,0x20,0x69,0x53,0x6d,0x70,0x43,0x68,0x61,0x6e,0x6e,0x65,0x6c, + 0x31,0x20,0x5b,0x5b,0x73,0x61,0x6d,0x70,0x6c,0x65,0x72,0x28,0x30,0x29,0x5d,0x5d, + 0x2c,0x20,0x73,0x61,0x6d,0x70,0x6c,0x65,0x72,0x20,0x69,0x53,0x6d,0x70,0x43,0x68, + 0x61,0x6e,0x6e,0x65,0x6c,0x30,0x20,0x5b,0x5b,0x73,0x61,0x6d,0x70,0x6c,0x65,0x72, + 0x28,0x31,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,0x66,0x6c,0x6f,0x61,0x74,0x32,0x20,0x5f,0x36,0x30,0x20, + 0x3d,0x20,0x28,0x69,0x6e,0x2e,0x74,0x65,0x78,0x55,0x56,0x20,0x2a,0x20,0x66,0x6c, + 0x6f,0x61,0x74,0x32,0x28,0x5f,0x34,0x38,0x2e,0x69,0x52,0x61,0x74,0x69,0x6f,0x2c, + 0x20,0x31,0x2e,0x30,0x29,0x29,0x20,0x2a,0x20,0x5f,0x34,0x38,0x2e,0x69,0x5a,0x6f, + 0x6f,0x6d,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x32,0x20,0x70, + 0x61,0x72,0x61,0x6d,0x20,0x3d,0x20,0x5f,0x36,0x30,0x20,0x2d,0x20,0x28,0x5f,0x34, + 0x38,0x2e,0x69,0x56,0x65,0x6c,0x6f,0x63,0x69,0x74,0x79,0x20,0x2a,0x20,0x5f,0x34, + 0x38,0x2e,0x69,0x54,0x69,0x6d,0x65,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c, + 0x6f,0x61,0x74,0x32,0x20,0x70,0x61,0x72,0x61,0x6d,0x5f,0x31,0x20,0x3d,0x20,0x5f, + 0x36,0x30,0x20,0x2b,0x20,0x66,0x6c,0x6f,0x61,0x74,0x32,0x28,0x5f,0x6e,0x6f,0x69, + 0x73,0x65,0x28,0x70,0x61,0x72,0x61,0x6d,0x2c,0x20,0x69,0x54,0x65,0x78,0x43,0x68, + 0x61,0x6e,0x6e,0x65,0x6c,0x31,0x2c,0x20,0x69,0x53,0x6d,0x70,0x43,0x68,0x61,0x6e, + 0x6e,0x65,0x6c,0x31,0x29,0x20,0x2a,0x20,0x5f,0x34,0x38,0x2e,0x69,0x57,0x61,0x72, + 0x70,0x69,0x6e,0x65,0x73,0x73,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x6f,0x75,0x74, + 0x2e,0x66,0x72,0x61,0x67,0x43,0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20,0x66,0x6c,0x6f, + 0x61,0x74,0x34,0x28,0x6d,0x69,0x78,0x28,0x69,0x54,0x65,0x78,0x43,0x68,0x61,0x6e, + 0x6e,0x65,0x6c,0x30,0x2e,0x73,0x61,0x6d,0x70,0x6c,0x65,0x28,0x69,0x53,0x6d,0x70, + 0x43,0x68,0x61,0x6e,0x6e,0x65,0x6c,0x30,0x2c,0x20,0x69,0x6e,0x2e,0x74,0x65,0x78, + 0x55,0x56,0x29,0x2e,0x78,0x79,0x7a,0x2c,0x20,0x66,0x6c,0x6f,0x61,0x74,0x33,0x28, + 0x5f,0x6e,0x6f,0x69,0x73,0x65,0x28,0x70,0x61,0x72,0x61,0x6d,0x5f,0x31,0x2c,0x20, + 0x69,0x54,0x65,0x78,0x43,0x68,0x61,0x6e,0x6e,0x65,0x6c,0x31,0x2c,0x20,0x69,0x53, + 0x6d,0x70,0x43,0x68,0x61,0x6e,0x6e,0x65,0x6c,0x31,0x29,0x29,0x20,0x2a,0x20,0x69, + 0x6e,0x2e,0x69,0x43,0x6f,0x6c,0x6f,0x72,0x2e,0x78,0x79,0x7a,0x2c,0x20,0x66,0x6c, + 0x6f,0x61,0x74,0x33,0x28,0x5f,0x34,0x38,0x2e,0x69,0x50,0x72,0x65,0x73,0x73,0x75, + 0x72,0x65,0x29,0x29,0x2c,0x20,0x31,0x2e,0x30,0x29,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 + { + float2 texUV [[user(locn0)]]; + float4 iColor [[user(locn1)]]; + float4 gl_Position [[position]]; + }; + + struct main0_in + { + float4 coord [[attribute(0)]]; + float4 color [[attribute(1)]]; + }; + + vertex main0_out main0(main0_in in [[stage_in]]) + { + main0_out out = {}; + out.gl_Position = float4(in.coord.xy, 0.0, 1.0); + out.texUV = in.coord.zw; + out.iColor = in.color; + return out; + } + +*/ +static const uint8_t effect_vs_source_metal_ios[497] = { + 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,0x32,0x20,0x74,0x65,0x78,0x55,0x56,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,0x69,0x43,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,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,0x63,0x6f,0x6f,0x72,0x64,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,0x34,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x20,0x5b, + 0x5b,0x61,0x74,0x74,0x72,0x69,0x62,0x75,0x74,0x65,0x28,0x31,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,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,0x66,0x6c,0x6f,0x61,0x74,0x34,0x28,0x69,0x6e,0x2e, + 0x63,0x6f,0x6f,0x72,0x64,0x2e,0x78,0x79,0x2c,0x20,0x30,0x2e,0x30,0x2c,0x20,0x31, + 0x2e,0x30,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x6f,0x75,0x74,0x2e,0x74,0x65,0x78, + 0x55,0x56,0x20,0x3d,0x20,0x69,0x6e,0x2e,0x63,0x6f,0x6f,0x72,0x64,0x2e,0x7a,0x77, + 0x3b,0x0a,0x20,0x20,0x20,0x20,0x6f,0x75,0x74,0x2e,0x69,0x43,0x6f,0x6c,0x6f,0x72, + 0x20,0x3d,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, +}; +/* + #pragma clang diagnostic ignored "-Wmissing-prototypes" + + #include + #include + + using namespace metal; + + struct fs_uniforms + { + float2 iVelocity; + float iPressure; + float iTime; + float iWarpiness; + float iRatio; + float iZoom; + float iLevel; + }; + + struct main0_out + { + float4 fragColor [[color(0)]]; + }; + + struct main0_in + { + float2 texUV [[user(locn0)]]; + float4 iColor [[user(locn1)]]; + }; + + static inline __attribute__((always_inline)) + float _noise(thread const float2& p, texture2d iTexChannel1, sampler iSmpChannel1) + { + return iTexChannel1.sample(iSmpChannel1, p).x; + } + + fragment main0_out main0(main0_in in [[stage_in]], constant fs_uniforms& _48 [[buffer(0)]], texture2d iTexChannel1 [[texture(0)]], texture2d iTexChannel0 [[texture(1)]], sampler iSmpChannel1 [[sampler(0)]], sampler iSmpChannel0 [[sampler(1)]]) + { + main0_out out = {}; + float2 _60 = (in.texUV * float2(_48.iRatio, 1.0)) * _48.iZoom; + float2 param = _60 - (_48.iVelocity * _48.iTime); + float2 param_1 = _60 + float2(_noise(param, iTexChannel1, iSmpChannel1) * _48.iWarpiness); + out.fragColor = float4(mix(iTexChannel0.sample(iSmpChannel0, in.texUV).xyz, float3(_noise(param_1, iTexChannel1, iSmpChannel1)) * in.iColor.xyz, float3(_48.iPressure)), 1.0); + return out; + } + +*/ +static const uint8_t effect_fs_source_metal_ios[1328] = { + 0x23,0x70,0x72,0x61,0x67,0x6d,0x61,0x20,0x63,0x6c,0x61,0x6e,0x67,0x20,0x64,0x69, + 0x61,0x67,0x6e,0x6f,0x73,0x74,0x69,0x63,0x20,0x69,0x67,0x6e,0x6f,0x72,0x65,0x64, + 0x20,0x22,0x2d,0x57,0x6d,0x69,0x73,0x73,0x69,0x6e,0x67,0x2d,0x70,0x72,0x6f,0x74, + 0x6f,0x74,0x79,0x70,0x65,0x73,0x22,0x0a,0x0a,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,0x66,0x73,0x5f,0x75,0x6e,0x69,0x66,0x6f, + 0x72,0x6d,0x73,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x32, + 0x20,0x69,0x56,0x65,0x6c,0x6f,0x63,0x69,0x74,0x79,0x3b,0x0a,0x20,0x20,0x20,0x20, + 0x66,0x6c,0x6f,0x61,0x74,0x20,0x69,0x50,0x72,0x65,0x73,0x73,0x75,0x72,0x65,0x3b, + 0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x20,0x69,0x54,0x69,0x6d,0x65, + 0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x20,0x69,0x57,0x61,0x72, + 0x70,0x69,0x6e,0x65,0x73,0x73,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61, + 0x74,0x20,0x69,0x52,0x61,0x74,0x69,0x6f,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c, + 0x6f,0x61,0x74,0x20,0x69,0x5a,0x6f,0x6f,0x6d,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66, + 0x6c,0x6f,0x61,0x74,0x20,0x69,0x4c,0x65,0x76,0x65,0x6c,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,0x66, + 0x72,0x61,0x67,0x43,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,0x32,0x20,0x74,0x65,0x78,0x55,0x56,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,0x69,0x43,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,0x73,0x74,0x61,0x74,0x69,0x63,0x20,0x69,0x6e,0x6c, + 0x69,0x6e,0x65,0x20,0x5f,0x5f,0x61,0x74,0x74,0x72,0x69,0x62,0x75,0x74,0x65,0x5f, + 0x5f,0x28,0x28,0x61,0x6c,0x77,0x61,0x79,0x73,0x5f,0x69,0x6e,0x6c,0x69,0x6e,0x65, + 0x29,0x29,0x0a,0x66,0x6c,0x6f,0x61,0x74,0x20,0x5f,0x6e,0x6f,0x69,0x73,0x65,0x28, + 0x74,0x68,0x72,0x65,0x61,0x64,0x20,0x63,0x6f,0x6e,0x73,0x74,0x20,0x66,0x6c,0x6f, + 0x61,0x74,0x32,0x26,0x20,0x70,0x2c,0x20,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x32, + 0x64,0x3c,0x66,0x6c,0x6f,0x61,0x74,0x3e,0x20,0x69,0x54,0x65,0x78,0x43,0x68,0x61, + 0x6e,0x6e,0x65,0x6c,0x31,0x2c,0x20,0x73,0x61,0x6d,0x70,0x6c,0x65,0x72,0x20,0x69, + 0x53,0x6d,0x70,0x43,0x68,0x61,0x6e,0x6e,0x65,0x6c,0x31,0x29,0x0a,0x7b,0x0a,0x20, + 0x20,0x20,0x20,0x72,0x65,0x74,0x75,0x72,0x6e,0x20,0x69,0x54,0x65,0x78,0x43,0x68, + 0x61,0x6e,0x6e,0x65,0x6c,0x31,0x2e,0x73,0x61,0x6d,0x70,0x6c,0x65,0x28,0x69,0x53, + 0x6d,0x70,0x43,0x68,0x61,0x6e,0x6e,0x65,0x6c,0x31,0x2c,0x20,0x70,0x29,0x2e,0x78, + 0x3b,0x0a,0x7d,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,0x63,0x6f,0x6e,0x73,0x74,0x61,0x6e,0x74, + 0x20,0x66,0x73,0x5f,0x75,0x6e,0x69,0x66,0x6f,0x72,0x6d,0x73,0x26,0x20,0x5f,0x34, + 0x38,0x20,0x5b,0x5b,0x62,0x75,0x66,0x66,0x65,0x72,0x28,0x30,0x29,0x5d,0x5d,0x2c, + 0x20,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x32,0x64,0x3c,0x66,0x6c,0x6f,0x61,0x74, + 0x3e,0x20,0x69,0x54,0x65,0x78,0x43,0x68,0x61,0x6e,0x6e,0x65,0x6c,0x31,0x20,0x5b, + 0x5b,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x28,0x30,0x29,0x5d,0x5d,0x2c,0x20,0x74, + 0x65,0x78,0x74,0x75,0x72,0x65,0x32,0x64,0x3c,0x66,0x6c,0x6f,0x61,0x74,0x3e,0x20, + 0x69,0x54,0x65,0x78,0x43,0x68,0x61,0x6e,0x6e,0x65,0x6c,0x30,0x20,0x5b,0x5b,0x74, + 0x65,0x78,0x74,0x75,0x72,0x65,0x28,0x31,0x29,0x5d,0x5d,0x2c,0x20,0x73,0x61,0x6d, + 0x70,0x6c,0x65,0x72,0x20,0x69,0x53,0x6d,0x70,0x43,0x68,0x61,0x6e,0x6e,0x65,0x6c, + 0x31,0x20,0x5b,0x5b,0x73,0x61,0x6d,0x70,0x6c,0x65,0x72,0x28,0x30,0x29,0x5d,0x5d, + 0x2c,0x20,0x73,0x61,0x6d,0x70,0x6c,0x65,0x72,0x20,0x69,0x53,0x6d,0x70,0x43,0x68, + 0x61,0x6e,0x6e,0x65,0x6c,0x30,0x20,0x5b,0x5b,0x73,0x61,0x6d,0x70,0x6c,0x65,0x72, + 0x28,0x31,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,0x66,0x6c,0x6f,0x61,0x74,0x32,0x20,0x5f,0x36,0x30,0x20, + 0x3d,0x20,0x28,0x69,0x6e,0x2e,0x74,0x65,0x78,0x55,0x56,0x20,0x2a,0x20,0x66,0x6c, + 0x6f,0x61,0x74,0x32,0x28,0x5f,0x34,0x38,0x2e,0x69,0x52,0x61,0x74,0x69,0x6f,0x2c, + 0x20,0x31,0x2e,0x30,0x29,0x29,0x20,0x2a,0x20,0x5f,0x34,0x38,0x2e,0x69,0x5a,0x6f, + 0x6f,0x6d,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x32,0x20,0x70, + 0x61,0x72,0x61,0x6d,0x20,0x3d,0x20,0x5f,0x36,0x30,0x20,0x2d,0x20,0x28,0x5f,0x34, + 0x38,0x2e,0x69,0x56,0x65,0x6c,0x6f,0x63,0x69,0x74,0x79,0x20,0x2a,0x20,0x5f,0x34, + 0x38,0x2e,0x69,0x54,0x69,0x6d,0x65,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c, + 0x6f,0x61,0x74,0x32,0x20,0x70,0x61,0x72,0x61,0x6d,0x5f,0x31,0x20,0x3d,0x20,0x5f, + 0x36,0x30,0x20,0x2b,0x20,0x66,0x6c,0x6f,0x61,0x74,0x32,0x28,0x5f,0x6e,0x6f,0x69, + 0x73,0x65,0x28,0x70,0x61,0x72,0x61,0x6d,0x2c,0x20,0x69,0x54,0x65,0x78,0x43,0x68, + 0x61,0x6e,0x6e,0x65,0x6c,0x31,0x2c,0x20,0x69,0x53,0x6d,0x70,0x43,0x68,0x61,0x6e, + 0x6e,0x65,0x6c,0x31,0x29,0x20,0x2a,0x20,0x5f,0x34,0x38,0x2e,0x69,0x57,0x61,0x72, + 0x70,0x69,0x6e,0x65,0x73,0x73,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x6f,0x75,0x74, + 0x2e,0x66,0x72,0x61,0x67,0x43,0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20,0x66,0x6c,0x6f, + 0x61,0x74,0x34,0x28,0x6d,0x69,0x78,0x28,0x69,0x54,0x65,0x78,0x43,0x68,0x61,0x6e, + 0x6e,0x65,0x6c,0x30,0x2e,0x73,0x61,0x6d,0x70,0x6c,0x65,0x28,0x69,0x53,0x6d,0x70, + 0x43,0x68,0x61,0x6e,0x6e,0x65,0x6c,0x30,0x2c,0x20,0x69,0x6e,0x2e,0x74,0x65,0x78, + 0x55,0x56,0x29,0x2e,0x78,0x79,0x7a,0x2c,0x20,0x66,0x6c,0x6f,0x61,0x74,0x33,0x28, + 0x5f,0x6e,0x6f,0x69,0x73,0x65,0x28,0x70,0x61,0x72,0x61,0x6d,0x5f,0x31,0x2c,0x20, + 0x69,0x54,0x65,0x78,0x43,0x68,0x61,0x6e,0x6e,0x65,0x6c,0x31,0x2c,0x20,0x69,0x53, + 0x6d,0x70,0x43,0x68,0x61,0x6e,0x6e,0x65,0x6c,0x31,0x29,0x29,0x20,0x2a,0x20,0x69, + 0x6e,0x2e,0x69,0x43,0x6f,0x6c,0x6f,0x72,0x2e,0x78,0x79,0x7a,0x2c,0x20,0x66,0x6c, + 0x6f,0x61,0x74,0x33,0x28,0x5f,0x34,0x38,0x2e,0x69,0x50,0x72,0x65,0x73,0x73,0x75, + 0x72,0x65,0x29,0x29,0x2c,0x20,0x31,0x2e,0x30,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20, + 0x72,0x65,0x74,0x75,0x72,0x6e,0x20,0x6f,0x75,0x74,0x3b,0x0a,0x7d,0x0a,0x0a,0x00, + +}; +/* + diagnostic(off, derivative_uniformity); + + var coord : vec4f; + + var texUV : vec2f; + + var iColor : vec4f; + + var color : vec4f; + + var gl_Position : vec4f; + + fn main_1() { + let x_19 : vec4f = coord; + let x_20 : vec2f = vec2f(x_19.x, x_19.y); + gl_Position = vec4f(x_20.x, x_20.y, 0.0f, 1.0f); + let x_30 : vec4f = coord; + texUV = vec2f(x_30.z, x_30.w); + let x_34 : vec4f = color; + iColor = x_34; + return; + } + + struct main_out { + @builtin(position) + gl_Position : vec4f, + @location(0) + texUV_1 : vec2f, + @location(1) + iColor_1 : vec4f, + } + + @vertex + fn main(@location(0) coord_param : vec4f, @location(1) color_param : vec4f) -> main_out { + coord = coord_param; + color = color_param; + main_1(); + return main_out(gl_Position, texUV, iColor); + } + +*/ +static const uint8_t effect_vs_source_wgsl[790] = { + 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,0x63,0x6f,0x6f,0x72,0x64,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,0x55,0x56,0x20,0x3a,0x20,0x76,0x65,0x63,0x32,0x66, + 0x3b,0x0a,0x0a,0x76,0x61,0x72,0x3c,0x70,0x72,0x69,0x76,0x61,0x74,0x65,0x3e,0x20, + 0x69,0x43,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,0x20,0x3a,0x20,0x76,0x65,0x63,0x34,0x66,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,0x31,0x39,0x20,0x3a,0x20,0x76,0x65,0x63,0x34, + 0x66,0x20,0x3d,0x20,0x63,0x6f,0x6f,0x72,0x64,0x3b,0x0a,0x20,0x20,0x6c,0x65,0x74, + 0x20,0x78,0x5f,0x32,0x30,0x20,0x3a,0x20,0x76,0x65,0x63,0x32,0x66,0x20,0x3d,0x20, + 0x76,0x65,0x63,0x32,0x66,0x28,0x78,0x5f,0x31,0x39,0x2e,0x78,0x2c,0x20,0x78,0x5f, + 0x31,0x39,0x2e,0x79,0x29,0x3b,0x0a,0x20,0x20,0x67,0x6c,0x5f,0x50,0x6f,0x73,0x69, + 0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x76,0x65,0x63,0x34,0x66,0x28,0x78,0x5f,0x32, + 0x30,0x2e,0x78,0x2c,0x20,0x78,0x5f,0x32,0x30,0x2e,0x79,0x2c,0x20,0x30,0x2e,0x30, + 0x66,0x2c,0x20,0x31,0x2e,0x30,0x66,0x29,0x3b,0x0a,0x20,0x20,0x6c,0x65,0x74,0x20, + 0x78,0x5f,0x33,0x30,0x20,0x3a,0x20,0x76,0x65,0x63,0x34,0x66,0x20,0x3d,0x20,0x63, + 0x6f,0x6f,0x72,0x64,0x3b,0x0a,0x20,0x20,0x74,0x65,0x78,0x55,0x56,0x20,0x3d,0x20, + 0x76,0x65,0x63,0x32,0x66,0x28,0x78,0x5f,0x33,0x30,0x2e,0x7a,0x2c,0x20,0x78,0x5f, + 0x33,0x30,0x2e,0x77,0x29,0x3b,0x0a,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x33, + 0x34,0x20,0x3a,0x20,0x76,0x65,0x63,0x34,0x66,0x20,0x3d,0x20,0x63,0x6f,0x6c,0x6f, + 0x72,0x3b,0x0a,0x20,0x20,0x69,0x43,0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20,0x78,0x5f, + 0x33,0x34,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, + 0x74,0x65,0x78,0x55,0x56,0x5f,0x31,0x20,0x3a,0x20,0x76,0x65,0x63,0x32,0x66,0x2c, + 0x0a,0x20,0x20,0x40,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x28,0x31,0x29,0x0a, + 0x20,0x20,0x69,0x43,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,0x63,0x6f,0x6f,0x72,0x64,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,0x63,0x6f,0x6f,0x72,0x64, + 0x20,0x3d,0x20,0x63,0x6f,0x6f,0x72,0x64,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,0x67,0x6c,0x5f,0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e, + 0x2c,0x20,0x74,0x65,0x78,0x55,0x56,0x2c,0x20,0x69,0x43,0x6f,0x6c,0x6f,0x72,0x29, + 0x3b,0x0a,0x7d,0x0a,0x0a,0x00, +}; +/* + diagnostic(off, derivative_uniformity); + + struct fs_uniforms { + /_ @offset(0) _/ + iVelocity : vec2f, + /_ @offset(8) _/ + iPressure : f32, + /_ @offset(12) _/ + iTime : f32, + /_ @offset(16) _/ + iWarpiness : f32, + /_ @offset(20) _/ + iRatio : f32, + /_ @offset(24) _/ + iZoom : f32, + /_ @offset(28) _/ + iLevel : f32, + } + + @group(1) @binding(64) var iTexChannel1 : texture_2d; + + @group(1) @binding(80) var iSmpChannel1 : sampler; + + @group(1) @binding(65) var iTexChannel0 : texture_2d; + + @group(1) @binding(81) var iSmpChannel0 : sampler; + + var texUV : vec2f; + + @group(0) @binding(8) var x_48 : fs_uniforms; + + var iColor : vec4f; + + var fragColor : vec4f; + + fn noise_vf2_(p : ptr) -> f32 { + let x_23 : vec2f = *(p); + let x_25 : vec4f = textureSample(iTexChannel1, iSmpChannel1, x_23); + return x_25.x; + } + + fn main_1() { + var tex_col : vec3f; + var fog_uv : vec2f; + var f : f32; + var param : vec2f; + var param_1 : vec2f; + var col : vec3f; + let x_41 : vec2f = texUV; + let x_42 : vec4f = textureSample(iTexChannel0, iSmpChannel0, x_41); + tex_col = vec3f(x_42.x, x_42.y, x_42.z); + let x_45 : vec2f = texUV; + let x_53 : f32 = x_48.iRatio; + let x_59 : f32 = x_48.iZoom; + fog_uv = ((x_45 * vec2f(x_53, 1.0f)) * x_59); + let x_63 : vec2f = fog_uv; + let x_67 : vec2f = x_48.iVelocity; + let x_70 : f32 = x_48.iTime; + param = (x_63 - (x_67 * x_70)); + let x_74 : f32 = noise_vf2_(&(param)); + f = x_74; + let x_75 : vec2f = fog_uv; + let x_76 : f32 = f; + let x_79 : f32 = x_48.iWarpiness; + let x_80 : f32 = (x_76 * x_79); + param_1 = (x_75 + vec2f(x_80, x_80)); + let x_84 : f32 = noise_vf2_(&(param_1)); + f = x_84; + let x_86 : vec3f = tex_col; + let x_87 : f32 = f; + let x_91 : vec4f = iColor; + let x_96 : f32 = x_48.iPressure; + col = mix(x_86, (vec3f(x_87, x_87, x_87) * vec3f(x_91.x, x_91.y, x_91.z)), vec3f(x_96, x_96, x_96)); + let x_101 : vec3f = col; + fragColor = vec4f(x_101.x, x_101.y, x_101.z, 1.0f); + return; + } + + struct main_out { + @location(0) + fragColor_1 : vec4f, + } + + @fragment + fn main(@location(0) texUV_param : vec2f, @location(1) iColor_param : vec4f) -> main_out { + texUV = texUV_param; + iColor = iColor_param; + main_1(); + return main_out(fragColor); + } + +*/ +static const uint8_t effect_fs_source_wgsl[2247] = { + 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, + 0x66,0x73,0x5f,0x75,0x6e,0x69,0x66,0x6f,0x72,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,0x69,0x56,0x65,0x6c,0x6f,0x63,0x69,0x74,0x79,0x20,0x3a,0x20,0x76, + 0x65,0x63,0x32,0x66,0x2c,0x0a,0x20,0x20,0x2f,0x2a,0x20,0x40,0x6f,0x66,0x66,0x73, + 0x65,0x74,0x28,0x38,0x29,0x20,0x2a,0x2f,0x0a,0x20,0x20,0x69,0x50,0x72,0x65,0x73, + 0x73,0x75,0x72,0x65,0x20,0x3a,0x20,0x66,0x33,0x32,0x2c,0x0a,0x20,0x20,0x2f,0x2a, + 0x20,0x40,0x6f,0x66,0x66,0x73,0x65,0x74,0x28,0x31,0x32,0x29,0x20,0x2a,0x2f,0x0a, + 0x20,0x20,0x69,0x54,0x69,0x6d,0x65,0x20,0x3a,0x20,0x66,0x33,0x32,0x2c,0x0a,0x20, + 0x20,0x2f,0x2a,0x20,0x40,0x6f,0x66,0x66,0x73,0x65,0x74,0x28,0x31,0x36,0x29,0x20, + 0x2a,0x2f,0x0a,0x20,0x20,0x69,0x57,0x61,0x72,0x70,0x69,0x6e,0x65,0x73,0x73,0x20, + 0x3a,0x20,0x66,0x33,0x32,0x2c,0x0a,0x20,0x20,0x2f,0x2a,0x20,0x40,0x6f,0x66,0x66, + 0x73,0x65,0x74,0x28,0x32,0x30,0x29,0x20,0x2a,0x2f,0x0a,0x20,0x20,0x69,0x52,0x61, + 0x74,0x69,0x6f,0x20,0x3a,0x20,0x66,0x33,0x32,0x2c,0x0a,0x20,0x20,0x2f,0x2a,0x20, + 0x40,0x6f,0x66,0x66,0x73,0x65,0x74,0x28,0x32,0x34,0x29,0x20,0x2a,0x2f,0x0a,0x20, + 0x20,0x69,0x5a,0x6f,0x6f,0x6d,0x20,0x3a,0x20,0x66,0x33,0x32,0x2c,0x0a,0x20,0x20, + 0x2f,0x2a,0x20,0x40,0x6f,0x66,0x66,0x73,0x65,0x74,0x28,0x32,0x38,0x29,0x20,0x2a, + 0x2f,0x0a,0x20,0x20,0x69,0x4c,0x65,0x76,0x65,0x6c,0x20,0x3a,0x20,0x66,0x33,0x32, + 0x2c,0x0a,0x7d,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, + 0x69,0x54,0x65,0x78,0x43,0x68,0x61,0x6e,0x6e,0x65,0x6c,0x31,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,0x69,0x53,0x6d,0x70, + 0x43,0x68,0x61,0x6e,0x6e,0x65,0x6c,0x31,0x20,0x3a,0x20,0x73,0x61,0x6d,0x70,0x6c, + 0x65,0x72,0x3b,0x0a,0x0a,0x40,0x67,0x72,0x6f,0x75,0x70,0x28,0x31,0x29,0x20,0x40, + 0x62,0x69,0x6e,0x64,0x69,0x6e,0x67,0x28,0x36,0x35,0x29,0x20,0x76,0x61,0x72,0x20, + 0x69,0x54,0x65,0x78,0x43,0x68,0x61,0x6e,0x6e,0x65,0x6c,0x30,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,0x31,0x29,0x20,0x76,0x61,0x72,0x20,0x69,0x53,0x6d,0x70, + 0x43,0x68,0x61,0x6e,0x6e,0x65,0x6c,0x30,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,0x74,0x65,0x78,0x55,0x56,0x20,0x3a,0x20,0x76,0x65,0x63,0x32,0x66,0x3b, + 0x0a,0x0a,0x40,0x67,0x72,0x6f,0x75,0x70,0x28,0x30,0x29,0x20,0x40,0x62,0x69,0x6e, + 0x64,0x69,0x6e,0x67,0x28,0x38,0x29,0x20,0x76,0x61,0x72,0x3c,0x75,0x6e,0x69,0x66, + 0x6f,0x72,0x6d,0x3e,0x20,0x78,0x5f,0x34,0x38,0x20,0x3a,0x20,0x66,0x73,0x5f,0x75, + 0x6e,0x69,0x66,0x6f,0x72,0x6d,0x73,0x3b,0x0a,0x0a,0x76,0x61,0x72,0x3c,0x70,0x72, + 0x69,0x76,0x61,0x74,0x65,0x3e,0x20,0x69,0x43,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,0x66,0x72,0x61,0x67,0x43,0x6f,0x6c,0x6f,0x72,0x20,0x3a, + 0x20,0x76,0x65,0x63,0x34,0x66,0x3b,0x0a,0x0a,0x66,0x6e,0x20,0x6e,0x6f,0x69,0x73, + 0x65,0x5f,0x76,0x66,0x32,0x5f,0x28,0x70,0x20,0x3a,0x20,0x70,0x74,0x72,0x3c,0x66, + 0x75,0x6e,0x63,0x74,0x69,0x6f,0x6e,0x2c,0x20,0x76,0x65,0x63,0x32,0x66,0x3e,0x29, + 0x20,0x2d,0x3e,0x20,0x66,0x33,0x32,0x20,0x7b,0x0a,0x20,0x20,0x6c,0x65,0x74,0x20, + 0x78,0x5f,0x32,0x33,0x20,0x3a,0x20,0x76,0x65,0x63,0x32,0x66,0x20,0x3d,0x20,0x2a, + 0x28,0x70,0x29,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,0x69,0x54,0x65,0x78,0x43,0x68,0x61,0x6e, + 0x6e,0x65,0x6c,0x31,0x2c,0x20,0x69,0x53,0x6d,0x70,0x43,0x68,0x61,0x6e,0x6e,0x65, + 0x6c,0x31,0x2c,0x20,0x78,0x5f,0x32,0x33,0x29,0x3b,0x0a,0x20,0x20,0x72,0x65,0x74, + 0x75,0x72,0x6e,0x20,0x78,0x5f,0x32,0x35,0x2e,0x78,0x3b,0x0a,0x7d,0x0a,0x0a,0x66, + 0x6e,0x20,0x6d,0x61,0x69,0x6e,0x5f,0x31,0x28,0x29,0x20,0x7b,0x0a,0x20,0x20,0x76, + 0x61,0x72,0x20,0x74,0x65,0x78,0x5f,0x63,0x6f,0x6c,0x20,0x3a,0x20,0x76,0x65,0x63, + 0x33,0x66,0x3b,0x0a,0x20,0x20,0x76,0x61,0x72,0x20,0x66,0x6f,0x67,0x5f,0x75,0x76, + 0x20,0x3a,0x20,0x76,0x65,0x63,0x32,0x66,0x3b,0x0a,0x20,0x20,0x76,0x61,0x72,0x20, + 0x66,0x20,0x3a,0x20,0x66,0x33,0x32,0x3b,0x0a,0x20,0x20,0x76,0x61,0x72,0x20,0x70, + 0x61,0x72,0x61,0x6d,0x20,0x3a,0x20,0x76,0x65,0x63,0x32,0x66,0x3b,0x0a,0x20,0x20, + 0x76,0x61,0x72,0x20,0x70,0x61,0x72,0x61,0x6d,0x5f,0x31,0x20,0x3a,0x20,0x76,0x65, + 0x63,0x32,0x66,0x3b,0x0a,0x20,0x20,0x76,0x61,0x72,0x20,0x63,0x6f,0x6c,0x20,0x3a, + 0x20,0x76,0x65,0x63,0x33,0x66,0x3b,0x0a,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f, + 0x34,0x31,0x20,0x3a,0x20,0x76,0x65,0x63,0x32,0x66,0x20,0x3d,0x20,0x74,0x65,0x78, + 0x55,0x56,0x3b,0x0a,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x34,0x32,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,0x69,0x54,0x65,0x78,0x43,0x68,0x61,0x6e,0x6e, + 0x65,0x6c,0x30,0x2c,0x20,0x69,0x53,0x6d,0x70,0x43,0x68,0x61,0x6e,0x6e,0x65,0x6c, + 0x30,0x2c,0x20,0x78,0x5f,0x34,0x31,0x29,0x3b,0x0a,0x20,0x20,0x74,0x65,0x78,0x5f, + 0x63,0x6f,0x6c,0x20,0x3d,0x20,0x76,0x65,0x63,0x33,0x66,0x28,0x78,0x5f,0x34,0x32, + 0x2e,0x78,0x2c,0x20,0x78,0x5f,0x34,0x32,0x2e,0x79,0x2c,0x20,0x78,0x5f,0x34,0x32, + 0x2e,0x7a,0x29,0x3b,0x0a,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x34,0x35,0x20, + 0x3a,0x20,0x76,0x65,0x63,0x32,0x66,0x20,0x3d,0x20,0x74,0x65,0x78,0x55,0x56,0x3b, + 0x0a,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x35,0x33,0x20,0x3a,0x20,0x66,0x33, + 0x32,0x20,0x3d,0x20,0x78,0x5f,0x34,0x38,0x2e,0x69,0x52,0x61,0x74,0x69,0x6f,0x3b, + 0x0a,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x35,0x39,0x20,0x3a,0x20,0x66,0x33, + 0x32,0x20,0x3d,0x20,0x78,0x5f,0x34,0x38,0x2e,0x69,0x5a,0x6f,0x6f,0x6d,0x3b,0x0a, + 0x20,0x20,0x66,0x6f,0x67,0x5f,0x75,0x76,0x20,0x3d,0x20,0x28,0x28,0x78,0x5f,0x34, + 0x35,0x20,0x2a,0x20,0x76,0x65,0x63,0x32,0x66,0x28,0x78,0x5f,0x35,0x33,0x2c,0x20, + 0x31,0x2e,0x30,0x66,0x29,0x29,0x20,0x2a,0x20,0x78,0x5f,0x35,0x39,0x29,0x3b,0x0a, + 0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x36,0x33,0x20,0x3a,0x20,0x76,0x65,0x63, + 0x32,0x66,0x20,0x3d,0x20,0x66,0x6f,0x67,0x5f,0x75,0x76,0x3b,0x0a,0x20,0x20,0x6c, + 0x65,0x74,0x20,0x78,0x5f,0x36,0x37,0x20,0x3a,0x20,0x76,0x65,0x63,0x32,0x66,0x20, + 0x3d,0x20,0x78,0x5f,0x34,0x38,0x2e,0x69,0x56,0x65,0x6c,0x6f,0x63,0x69,0x74,0x79, + 0x3b,0x0a,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x37,0x30,0x20,0x3a,0x20,0x66, + 0x33,0x32,0x20,0x3d,0x20,0x78,0x5f,0x34,0x38,0x2e,0x69,0x54,0x69,0x6d,0x65,0x3b, + 0x0a,0x20,0x20,0x70,0x61,0x72,0x61,0x6d,0x20,0x3d,0x20,0x28,0x78,0x5f,0x36,0x33, + 0x20,0x2d,0x20,0x28,0x78,0x5f,0x36,0x37,0x20,0x2a,0x20,0x78,0x5f,0x37,0x30,0x29, + 0x29,0x3b,0x0a,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x37,0x34,0x20,0x3a,0x20, + 0x66,0x33,0x32,0x20,0x3d,0x20,0x6e,0x6f,0x69,0x73,0x65,0x5f,0x76,0x66,0x32,0x5f, + 0x28,0x26,0x28,0x70,0x61,0x72,0x61,0x6d,0x29,0x29,0x3b,0x0a,0x20,0x20,0x66,0x20, + 0x3d,0x20,0x78,0x5f,0x37,0x34,0x3b,0x0a,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f, + 0x37,0x35,0x20,0x3a,0x20,0x76,0x65,0x63,0x32,0x66,0x20,0x3d,0x20,0x66,0x6f,0x67, + 0x5f,0x75,0x76,0x3b,0x0a,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x37,0x36,0x20, + 0x3a,0x20,0x66,0x33,0x32,0x20,0x3d,0x20,0x66,0x3b,0x0a,0x20,0x20,0x6c,0x65,0x74, + 0x20,0x78,0x5f,0x37,0x39,0x20,0x3a,0x20,0x66,0x33,0x32,0x20,0x3d,0x20,0x78,0x5f, + 0x34,0x38,0x2e,0x69,0x57,0x61,0x72,0x70,0x69,0x6e,0x65,0x73,0x73,0x3b,0x0a,0x20, + 0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x38,0x30,0x20,0x3a,0x20,0x66,0x33,0x32,0x20, + 0x3d,0x20,0x28,0x78,0x5f,0x37,0x36,0x20,0x2a,0x20,0x78,0x5f,0x37,0x39,0x29,0x3b, + 0x0a,0x20,0x20,0x70,0x61,0x72,0x61,0x6d,0x5f,0x31,0x20,0x3d,0x20,0x28,0x78,0x5f, + 0x37,0x35,0x20,0x2b,0x20,0x76,0x65,0x63,0x32,0x66,0x28,0x78,0x5f,0x38,0x30,0x2c, + 0x20,0x78,0x5f,0x38,0x30,0x29,0x29,0x3b,0x0a,0x20,0x20,0x6c,0x65,0x74,0x20,0x78, + 0x5f,0x38,0x34,0x20,0x3a,0x20,0x66,0x33,0x32,0x20,0x3d,0x20,0x6e,0x6f,0x69,0x73, + 0x65,0x5f,0x76,0x66,0x32,0x5f,0x28,0x26,0x28,0x70,0x61,0x72,0x61,0x6d,0x5f,0x31, + 0x29,0x29,0x3b,0x0a,0x20,0x20,0x66,0x20,0x3d,0x20,0x78,0x5f,0x38,0x34,0x3b,0x0a, + 0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x38,0x36,0x20,0x3a,0x20,0x76,0x65,0x63, + 0x33,0x66,0x20,0x3d,0x20,0x74,0x65,0x78,0x5f,0x63,0x6f,0x6c,0x3b,0x0a,0x20,0x20, + 0x6c,0x65,0x74,0x20,0x78,0x5f,0x38,0x37,0x20,0x3a,0x20,0x66,0x33,0x32,0x20,0x3d, + 0x20,0x66,0x3b,0x0a,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x39,0x31,0x20,0x3a, + 0x20,0x76,0x65,0x63,0x34,0x66,0x20,0x3d,0x20,0x69,0x43,0x6f,0x6c,0x6f,0x72,0x3b, + 0x0a,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x39,0x36,0x20,0x3a,0x20,0x66,0x33, + 0x32,0x20,0x3d,0x20,0x78,0x5f,0x34,0x38,0x2e,0x69,0x50,0x72,0x65,0x73,0x73,0x75, + 0x72,0x65,0x3b,0x0a,0x20,0x20,0x63,0x6f,0x6c,0x20,0x3d,0x20,0x6d,0x69,0x78,0x28, + 0x78,0x5f,0x38,0x36,0x2c,0x20,0x28,0x76,0x65,0x63,0x33,0x66,0x28,0x78,0x5f,0x38, + 0x37,0x2c,0x20,0x78,0x5f,0x38,0x37,0x2c,0x20,0x78,0x5f,0x38,0x37,0x29,0x20,0x2a, + 0x20,0x76,0x65,0x63,0x33,0x66,0x28,0x78,0x5f,0x39,0x31,0x2e,0x78,0x2c,0x20,0x78, + 0x5f,0x39,0x31,0x2e,0x79,0x2c,0x20,0x78,0x5f,0x39,0x31,0x2e,0x7a,0x29,0x29,0x2c, + 0x20,0x76,0x65,0x63,0x33,0x66,0x28,0x78,0x5f,0x39,0x36,0x2c,0x20,0x78,0x5f,0x39, + 0x36,0x2c,0x20,0x78,0x5f,0x39,0x36,0x29,0x29,0x3b,0x0a,0x20,0x20,0x6c,0x65,0x74, + 0x20,0x78,0x5f,0x31,0x30,0x31,0x20,0x3a,0x20,0x76,0x65,0x63,0x33,0x66,0x20,0x3d, + 0x20,0x63,0x6f,0x6c,0x3b,0x0a,0x20,0x20,0x66,0x72,0x61,0x67,0x43,0x6f,0x6c,0x6f, + 0x72,0x20,0x3d,0x20,0x76,0x65,0x63,0x34,0x66,0x28,0x78,0x5f,0x31,0x30,0x31,0x2e, + 0x78,0x2c,0x20,0x78,0x5f,0x31,0x30,0x31,0x2e,0x79,0x2c,0x20,0x78,0x5f,0x31,0x30, + 0x31,0x2e,0x7a,0x2c,0x20,0x31,0x2e,0x30,0x66,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, + 0x43,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,0x74,0x65,0x78,0x55,0x56,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,0x31,0x29,0x20,0x69,0x43,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,0x74,0x65,0x78,0x55,0x56, + 0x20,0x3d,0x20,0x74,0x65,0x78,0x55,0x56,0x5f,0x70,0x61,0x72,0x61,0x6d,0x3b,0x0a, + 0x20,0x20,0x69,0x43,0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20,0x69,0x43,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,0x43,0x6f,0x6c,0x6f,0x72, + 0x29,0x3b,0x0a,0x7d,0x0a,0x0a,0x00, +}; +const sg_shader_desc* effect_program_shader_desc(sg_backend backend) { + if (backend == SG_BACKEND_GLCORE) { + static sg_shader_desc desc; + static bool valid; + if (!valid) { + valid = true; + desc.vertex_func.source = (const char*)effect_vs_source_glsl410; + desc.vertex_func.entry = "main"; + desc.fragment_func.source = (const char*)effect_fs_source_glsl410; + desc.fragment_func.entry = "main"; + desc.attrs[0].glsl_name = "coord"; + desc.attrs[1].glsl_name = "color"; + desc.uniform_blocks[1].stage = SG_SHADERSTAGE_FRAGMENT; + desc.uniform_blocks[1].layout = SG_UNIFORMLAYOUT_STD140; + desc.uniform_blocks[1].size = 32; + desc.uniform_blocks[1].glsl_uniforms[0].type = SG_UNIFORMTYPE_FLOAT4; + desc.uniform_blocks[1].glsl_uniforms[0].array_count = 2; + desc.uniform_blocks[1].glsl_uniforms[0].glsl_name = "fs_uniforms"; + desc.images[0].stage = SG_SHADERSTAGE_FRAGMENT; + desc.images[0].image_type = SG_IMAGETYPE_2D; + desc.images[0].sample_type = SG_IMAGESAMPLETYPE_FLOAT; + desc.images[0].multisampled = false; + desc.images[1].stage = SG_SHADERSTAGE_FRAGMENT; + desc.images[1].image_type = SG_IMAGETYPE_2D; + desc.images[1].sample_type = SG_IMAGESAMPLETYPE_FLOAT; + desc.images[1].multisampled = false; + desc.samplers[0].stage = SG_SHADERSTAGE_FRAGMENT; + desc.samplers[0].sampler_type = SG_SAMPLERTYPE_FILTERING; + desc.samplers[1].stage = SG_SHADERSTAGE_FRAGMENT; + desc.samplers[1].sampler_type = SG_SAMPLERTYPE_FILTERING; + desc.image_sampler_pairs[0].stage = SG_SHADERSTAGE_FRAGMENT; + desc.image_sampler_pairs[0].image_slot = 0; + desc.image_sampler_pairs[0].sampler_slot = 0; + desc.image_sampler_pairs[0].glsl_name = "iTexChannel0_iSmpChannel0"; + desc.image_sampler_pairs[1].stage = SG_SHADERSTAGE_FRAGMENT; + desc.image_sampler_pairs[1].image_slot = 1; + desc.image_sampler_pairs[1].sampler_slot = 1; + desc.image_sampler_pairs[1].glsl_name = "iTexChannel1_iSmpChannel1"; + desc.label = "effect_program_shader"; + } + return &desc; + } + if (backend == SG_BACKEND_GLES3) { + static sg_shader_desc desc; + static bool valid; + if (!valid) { + valid = true; + desc.vertex_func.source = (const char*)effect_vs_source_glsl300es; + desc.vertex_func.entry = "main"; + desc.fragment_func.source = (const char*)effect_fs_source_glsl300es; + desc.fragment_func.entry = "main"; + desc.attrs[0].glsl_name = "coord"; + desc.attrs[1].glsl_name = "color"; + desc.uniform_blocks[1].stage = SG_SHADERSTAGE_FRAGMENT; + desc.uniform_blocks[1].layout = SG_UNIFORMLAYOUT_STD140; + desc.uniform_blocks[1].size = 32; + desc.uniform_blocks[1].glsl_uniforms[0].type = SG_UNIFORMTYPE_FLOAT4; + desc.uniform_blocks[1].glsl_uniforms[0].array_count = 2; + desc.uniform_blocks[1].glsl_uniforms[0].glsl_name = "fs_uniforms"; + desc.images[0].stage = SG_SHADERSTAGE_FRAGMENT; + desc.images[0].image_type = SG_IMAGETYPE_2D; + desc.images[0].sample_type = SG_IMAGESAMPLETYPE_FLOAT; + desc.images[0].multisampled = false; + desc.images[1].stage = SG_SHADERSTAGE_FRAGMENT; + desc.images[1].image_type = SG_IMAGETYPE_2D; + desc.images[1].sample_type = SG_IMAGESAMPLETYPE_FLOAT; + desc.images[1].multisampled = false; + desc.samplers[0].stage = SG_SHADERSTAGE_FRAGMENT; + desc.samplers[0].sampler_type = SG_SAMPLERTYPE_FILTERING; + desc.samplers[1].stage = SG_SHADERSTAGE_FRAGMENT; + desc.samplers[1].sampler_type = SG_SAMPLERTYPE_FILTERING; + desc.image_sampler_pairs[0].stage = SG_SHADERSTAGE_FRAGMENT; + desc.image_sampler_pairs[0].image_slot = 0; + desc.image_sampler_pairs[0].sampler_slot = 0; + desc.image_sampler_pairs[0].glsl_name = "iTexChannel0_iSmpChannel0"; + desc.image_sampler_pairs[1].stage = SG_SHADERSTAGE_FRAGMENT; + desc.image_sampler_pairs[1].image_slot = 1; + desc.image_sampler_pairs[1].sampler_slot = 1; + desc.image_sampler_pairs[1].glsl_name = "iTexChannel1_iSmpChannel1"; + desc.label = "effect_program_shader"; + } + return &desc; + } + if (backend == SG_BACKEND_D3D11) { + static sg_shader_desc desc; + static bool valid; + if (!valid) { + valid = true; + desc.vertex_func.source = (const char*)effect_vs_source_hlsl4; + desc.vertex_func.d3d11_target = "vs_4_0"; + desc.vertex_func.entry = "main"; + desc.fragment_func.source = (const char*)effect_fs_source_hlsl4; + desc.fragment_func.d3d11_target = "ps_4_0"; + desc.fragment_func.entry = "main"; + desc.attrs[0].hlsl_sem_name = "TEXCOORD"; + desc.attrs[0].hlsl_sem_index = 0; + desc.attrs[1].hlsl_sem_name = "TEXCOORD"; + desc.attrs[1].hlsl_sem_index = 1; + desc.uniform_blocks[1].stage = SG_SHADERSTAGE_FRAGMENT; + desc.uniform_blocks[1].layout = SG_UNIFORMLAYOUT_STD140; + desc.uniform_blocks[1].size = 32; + desc.uniform_blocks[1].hlsl_register_b_n = 0; + desc.images[0].stage = SG_SHADERSTAGE_FRAGMENT; + desc.images[0].image_type = SG_IMAGETYPE_2D; + desc.images[0].sample_type = SG_IMAGESAMPLETYPE_FLOAT; + desc.images[0].multisampled = false; + desc.images[0].hlsl_register_t_n = 1; + desc.images[1].stage = SG_SHADERSTAGE_FRAGMENT; + desc.images[1].image_type = SG_IMAGETYPE_2D; + desc.images[1].sample_type = SG_IMAGESAMPLETYPE_FLOAT; + desc.images[1].multisampled = false; + desc.images[1].hlsl_register_t_n = 0; + desc.samplers[0].stage = SG_SHADERSTAGE_FRAGMENT; + desc.samplers[0].sampler_type = SG_SAMPLERTYPE_FILTERING; + desc.samplers[0].hlsl_register_s_n = 1; + desc.samplers[1].stage = SG_SHADERSTAGE_FRAGMENT; + desc.samplers[1].sampler_type = SG_SAMPLERTYPE_FILTERING; + desc.samplers[1].hlsl_register_s_n = 0; + desc.image_sampler_pairs[0].stage = SG_SHADERSTAGE_FRAGMENT; + desc.image_sampler_pairs[0].image_slot = 0; + desc.image_sampler_pairs[0].sampler_slot = 0; + desc.image_sampler_pairs[1].stage = SG_SHADERSTAGE_FRAGMENT; + desc.image_sampler_pairs[1].image_slot = 1; + desc.image_sampler_pairs[1].sampler_slot = 1; + desc.label = "effect_program_shader"; + } + return &desc; + } + if (backend == SG_BACKEND_METAL_MACOS) { + static sg_shader_desc desc; + static bool valid; + if (!valid) { + valid = true; + desc.vertex_func.source = (const char*)effect_vs_source_metal_macos; + desc.vertex_func.entry = "main0"; + desc.fragment_func.source = (const char*)effect_fs_source_metal_macos; + desc.fragment_func.entry = "main0"; + desc.uniform_blocks[1].stage = SG_SHADERSTAGE_FRAGMENT; + desc.uniform_blocks[1].layout = SG_UNIFORMLAYOUT_STD140; + desc.uniform_blocks[1].size = 32; + desc.uniform_blocks[1].msl_buffer_n = 0; + desc.images[0].stage = SG_SHADERSTAGE_FRAGMENT; + desc.images[0].image_type = SG_IMAGETYPE_2D; + desc.images[0].sample_type = SG_IMAGESAMPLETYPE_FLOAT; + desc.images[0].multisampled = false; + desc.images[0].msl_texture_n = 1; + desc.images[1].stage = SG_SHADERSTAGE_FRAGMENT; + desc.images[1].image_type = SG_IMAGETYPE_2D; + desc.images[1].sample_type = SG_IMAGESAMPLETYPE_FLOAT; + desc.images[1].multisampled = false; + desc.images[1].msl_texture_n = 0; + desc.samplers[0].stage = SG_SHADERSTAGE_FRAGMENT; + desc.samplers[0].sampler_type = SG_SAMPLERTYPE_FILTERING; + desc.samplers[0].msl_sampler_n = 1; + desc.samplers[1].stage = SG_SHADERSTAGE_FRAGMENT; + desc.samplers[1].sampler_type = SG_SAMPLERTYPE_FILTERING; + desc.samplers[1].msl_sampler_n = 0; + desc.image_sampler_pairs[0].stage = SG_SHADERSTAGE_FRAGMENT; + desc.image_sampler_pairs[0].image_slot = 0; + desc.image_sampler_pairs[0].sampler_slot = 0; + desc.image_sampler_pairs[1].stage = SG_SHADERSTAGE_FRAGMENT; + desc.image_sampler_pairs[1].image_slot = 1; + desc.image_sampler_pairs[1].sampler_slot = 1; + desc.label = "effect_program_shader"; + } + return &desc; + } + if (backend == SG_BACKEND_METAL_IOS) { + static sg_shader_desc desc; + static bool valid; + if (!valid) { + valid = true; + desc.vertex_func.source = (const char*)effect_vs_source_metal_ios; + desc.vertex_func.entry = "main0"; + desc.fragment_func.source = (const char*)effect_fs_source_metal_ios; + desc.fragment_func.entry = "main0"; + desc.uniform_blocks[1].stage = SG_SHADERSTAGE_FRAGMENT; + desc.uniform_blocks[1].layout = SG_UNIFORMLAYOUT_STD140; + desc.uniform_blocks[1].size = 32; + desc.uniform_blocks[1].msl_buffer_n = 0; + desc.images[0].stage = SG_SHADERSTAGE_FRAGMENT; + desc.images[0].image_type = SG_IMAGETYPE_2D; + desc.images[0].sample_type = SG_IMAGESAMPLETYPE_FLOAT; + desc.images[0].multisampled = false; + desc.images[0].msl_texture_n = 1; + desc.images[1].stage = SG_SHADERSTAGE_FRAGMENT; + desc.images[1].image_type = SG_IMAGETYPE_2D; + desc.images[1].sample_type = SG_IMAGESAMPLETYPE_FLOAT; + desc.images[1].multisampled = false; + desc.images[1].msl_texture_n = 0; + desc.samplers[0].stage = SG_SHADERSTAGE_FRAGMENT; + desc.samplers[0].sampler_type = SG_SAMPLERTYPE_FILTERING; + desc.samplers[0].msl_sampler_n = 1; + desc.samplers[1].stage = SG_SHADERSTAGE_FRAGMENT; + desc.samplers[1].sampler_type = SG_SAMPLERTYPE_FILTERING; + desc.samplers[1].msl_sampler_n = 0; + desc.image_sampler_pairs[0].stage = SG_SHADERSTAGE_FRAGMENT; + desc.image_sampler_pairs[0].image_slot = 0; + desc.image_sampler_pairs[0].sampler_slot = 0; + desc.image_sampler_pairs[1].stage = SG_SHADERSTAGE_FRAGMENT; + desc.image_sampler_pairs[1].image_slot = 1; + desc.image_sampler_pairs[1].sampler_slot = 1; + desc.label = "effect_program_shader"; + } + return &desc; + } + if (backend == SG_BACKEND_WGPU) { + static sg_shader_desc desc; + static bool valid; + if (!valid) { + valid = true; + desc.vertex_func.source = (const char*)effect_vs_source_wgsl; + desc.vertex_func.entry = "main"; + desc.fragment_func.source = (const char*)effect_fs_source_wgsl; + desc.fragment_func.entry = "main"; + desc.uniform_blocks[1].stage = SG_SHADERSTAGE_FRAGMENT; + desc.uniform_blocks[1].layout = SG_UNIFORMLAYOUT_STD140; + desc.uniform_blocks[1].size = 32; + desc.uniform_blocks[1].wgsl_group0_binding_n = 8; + desc.images[0].stage = SG_SHADERSTAGE_FRAGMENT; + desc.images[0].image_type = SG_IMAGETYPE_2D; + desc.images[0].sample_type = SG_IMAGESAMPLETYPE_FLOAT; + desc.images[0].multisampled = false; + desc.images[0].wgsl_group1_binding_n = 65; + desc.images[1].stage = SG_SHADERSTAGE_FRAGMENT; + desc.images[1].image_type = SG_IMAGETYPE_2D; + desc.images[1].sample_type = SG_IMAGESAMPLETYPE_FLOAT; + desc.images[1].multisampled = false; + desc.images[1].wgsl_group1_binding_n = 64; + desc.samplers[0].stage = SG_SHADERSTAGE_FRAGMENT; + desc.samplers[0].sampler_type = SG_SAMPLERTYPE_FILTERING; + desc.samplers[0].wgsl_group1_binding_n = 81; + desc.samplers[1].stage = SG_SHADERSTAGE_FRAGMENT; + desc.samplers[1].sampler_type = SG_SAMPLERTYPE_FILTERING; + desc.samplers[1].wgsl_group1_binding_n = 80; + desc.image_sampler_pairs[0].stage = SG_SHADERSTAGE_FRAGMENT; + desc.image_sampler_pairs[0].image_slot = 0; + desc.image_sampler_pairs[0].sampler_slot = 0; + desc.image_sampler_pairs[1].stage = SG_SHADERSTAGE_FRAGMENT; + desc.image_sampler_pairs[1].image_slot = 1; + desc.image_sampler_pairs[1].sampler_slot = 1; + desc.label = "effect_program_shader"; + } + return &desc; + } + return 0; +} +#endif // SOKOL_SHDC_IMPL \ No newline at end of file diff --git a/inc/sokol/sokol_app.h b/inc/sokol/sokol_app.h new file mode 100644 index 0000000..790e9de --- /dev/null +++ b/inc/sokol/sokol_app.h @@ -0,0 +1,12097 @@ +#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 use a main() entry point instead of WinMain + 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 _DEBUG is 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. + + 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 with Metal: Cocoa, QuartzCore, Metal, MetalKit + - on macOS with GL: Cocoa, QuartzCore, OpenGL + - on iOS with Metal: Foundation, UIKit, Metal, MetalKit + - on iOS with GL: Foundation, UIKit, OpenGLES, GLKit + - on Linux with EGL: X11, Xi, Xcursor, EGL, GL (or GLESv2), dl, pthread, m(?) + - on Linux with GLX: X11, Xi, Xcursor, GL, dl, pthread, m(?) + - on Android: GLESv3, EGL, 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 + - 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. + + 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 'default framebuffer' + - 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, WebGL, WebGL2, NOAPI + + FEATURE/PLATFORM MATRIX + ======================= + | Windows | macOS | Linux | iOS | Android | HTML5 + --------------------+---------+-------+-------+-------+---------+-------- + gl 3.x | YES | YES | YES | --- | --- | --- + gles3/webgl2 | --- | --- | YES(2)| YES | YES | YES + metal | --- | YES | --- | YES | --- | --- + d3d11 | 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 | --- + 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 | TODO | --- | --- | 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) + + 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 tunnel through C code. Also note that the returned + pointers to the renderpass-descriptor and drawable 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_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) + Returns the major and minor version of the GL context + (only for SOKOL_GLCORE, all other backends return zero here, including SOKOL_GLES3) + + 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 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()) { ... } + + On native platforms, the sapp_lock_mouse() and sapp_mouse_locked() + functions work as expected (mouse lock is activated or deactivated + immediately when sapp_lock_mouse() is called, and sapp_mouse_locked() + also immediately returns the new state after sapp_lock_mouse() + is called. + + On the web platform, sapp_lock_mouse() and sapp_mouse_locked() behave + differently, as dictated 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; + } + } + + 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(). + + 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 + + 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 no 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 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. + + 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 (at first, window + icons, later maybe cursor images). + + Note that the actual image pixel format depends on the use case: + + - window icon pixels are RGBA8 +*/ +typedef struct sapp_image_desc { + int width; + int height; + 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(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(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_SWAPCHAIN_CREATE_SURFACE_FAILED, "wgpu: failed to create surface for swapchain") \ + _SAPP_LOGITEM_XMACRO(WGPU_SWAPCHAIN_CREATE_SWAPCHAIN_FAILED, "wgpu: failed to create swapchain object") \ + _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_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; + +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 major and minor version (the default GL version is 4.1 on macOS, 4.3 elsewhere) + 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_name; // the name (id) 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_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_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); +/* 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 (only valid for desktop GL) */ +SOKOL_APP_API_DECL int sapp_gl_get_major_version(void); +/* GL: get minor version (only valid for desktop GL) */ +SOKOL_APP_API_DECL int sapp_gl_get_minor_version(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 + +// this WinRT specific hack is required when wWinMain is in a static library +#if defined(_MSC_VER) && defined(UNICODE) +#include +#if defined(WINAPI_FAMILY_PARTITION) && !WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) +#pragma comment(linker, "/include:wWinMain") +#endif +#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) + #error("sokol_app.h: unknown 3D API selected for MacOS, must be SOKOL_METAL or SOKOL_GLCORE") + #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 + #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_NOAPI) + #error("sokol_app.h: unknown 3D API selected for Win32, must be SOKOL_D3D11, SOKOL_GLCORE 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) + #if !defined(SOKOL_FORCE_EGL) + #define _SAPP_GLX (1) + #endif + #define GL_GLEXT_PROTOTYPES + #include + #elif defined(SOKOL_GLES3) + #include + #include + #else + #error("sokol_app.h: unknown 3D API selected for Linux, must be SOKOL_GLCORE, SOKOL_GLES3") + #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(_SAPP_APPLE) + #if defined(SOKOL_METAL) + #import + #import + #endif + #if defined(_SAPP_MACOS) + #if defined(_SAPP_ANY_GL) + #ifndef GL_SILENCE_DEPRECATION + #define GL_SILENCE_DEPRECATION + #endif + #include + #include + #endif + #elif defined(_SAPP_IOS) + #import + #if defined(_SAPP_ANY_GL) + #import + #include + #endif + #endif + #include + #include +#elif defined(_SAPP_EMSCRIPTEN) + #if defined(SOKOL_WGPU) + #include + #endif + #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(SOKOL_NO_ENTRY) // if SOKOL_NO_ENTRY is defined, it's the applications' responsibility to use the right subsystem + #if 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(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_GLX) + #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 +#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(_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 +#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* cursors[_SAPP_MOUSECURSOR_NUM]; + #if defined(SOKOL_METAL) + id mtl_device; + #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) + +#if defined(SOKOL_WGPU) +typedef struct { + WGPUInstance instance; + WGPUAdapter adapter; + WGPUDevice device; + WGPUTextureFormat render_format; + WGPUSurface surface; + WGPUSwapChain swapchain; + WGPUTexture msaa_tex; + WGPUTextureView msaa_view; + WGPUTexture depth_stencil_tex; + WGPUTextureView depth_stencil_view; + WGPUTextureView swapchain_view; + bool async_init_done; +} _sapp_wgpu_t; +#endif + +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 cursors[_SAPP_MOUSECURSOR_NUM]; + UINT orig_codepage; + LONG mouse_locked_x, mouse_locked_y; + 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; + bool mouse_tracked; + uint8_t mouse_capture_mask; + _sapp_win32_dpi_t dpi; + bool raw_input_mousepos_valid; + LONG raw_input_mousepos_x; + LONG raw_input_mousepos_y; + uint8_t raw_input_data[256]; +} _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 cursors[_SAPP_MOUSECURSOR_NUM]; + int window_state; + float dpi; + unsigned char error_code; + Atom UTF8_STRING; + 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; + +#else + +typedef struct { + EGLDisplay display; + EGLContext context; + EGLSurface surface; +} _sapp_egl_t; + +#endif // _SAPP_GLX +#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(_SAPP_MACOS) + _sapp_macos_t macos; + #elif defined(_SAPP_IOS) + _sapp_ios_t ios; + #elif defined(_SAPP_EMSCRIPTEN) + _sapp_emsc_t emsc; + #if defined(SOKOL_WGPU) + _sapp_wgpu_t wgpu; + #endif + #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; + #else + _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]; +} _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__) + +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, 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 +_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 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(const char* src, char* dst, int max_len) { + SOKOL_ASSERT(src && dst && (max_len > 0)); + char* const end = &(dst[max_len-1]); + char c = 0; + for (int i = 0; i < max_len; i++) { + c = *src; + if (c != 0) { + src++; + } + *dst++ = c; + } + /* truncated? */ + if (c != 0) { + *end = 0; + return false; + } + else { + return true; + } +} + +_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); + // NOTE: can't patch the default for gl_major_version and gl_minor_version + // independently, because a desired version 4.0 would be patched to 4.2 + // (or expressed differently: zero is a valid value for gl_minor_version + // and can't be used to indicate 'default') + if (0 == res.gl_major_version) { + #if defined(_SAPP_APPLE) + res.gl_major_version = 4; + res.gl_minor_version = 1; + #else + res.gl_major_version = 4; + res.gl_minor_version = 3; + #endif + } + res.html5_canvas_name = _sapp_def(res.html5_canvas_name, "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_app"); + 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.html5_canvas_selector[0] = '#'; + _sapp_strcpy(_sapp.desc.html5_canvas_name, &_sapp.html5_canvas_selector[1], sizeof(_sapp.html5_canvas_selector) - 1); + _sapp.desc.html5_canvas_name = &_sapp.html5_canvas_selector[1]; + _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); + } + _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); +} + +// █████ ██████ ██████ ██ ███████ +// ██ ██ ██ ██ ██ ██ ██ ██ +// ███████ ██████ ██████ ██ █████ +// ██ ██ ██ ██ ██ ██ +// ██ ██ ██ ██ ███████ ███████ +// +// >>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) + +_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_OBJC_RELEASE(_sapp.macos.mtl_device); + #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) { + _sapp.macos.cursors[SAPP_MOUSECURSOR_DEFAULT] = nil; // not a bug + _sapp.macos.cursors[SAPP_MOUSECURSOR_ARROW] = [NSCursor arrowCursor]; + _sapp.macos.cursors[SAPP_MOUSECURSOR_IBEAM] = [NSCursor IBeamCursor]; + _sapp.macos.cursors[SAPP_MOUSECURSOR_CROSSHAIR] = [NSCursor crosshairCursor]; + _sapp.macos.cursors[SAPP_MOUSECURSOR_POINTING_HAND] = [NSCursor pointingHandCursor]; + _sapp.macos.cursors[SAPP_MOUSECURSOR_RESIZE_EW] = [NSCursor respondsToSelector:@selector(_windowResizeEastWestCursor)] ? [NSCursor _windowResizeEastWestCursor] : [NSCursor resizeLeftRightCursor]; + _sapp.macos.cursors[SAPP_MOUSECURSOR_RESIZE_NS] = [NSCursor respondsToSelector:@selector(_windowResizeNorthSouthCursor)] ? [NSCursor _windowResizeNorthSouthCursor] : [NSCursor resizeUpDownCursor]; + _sapp.macos.cursors[SAPP_MOUSECURSOR_RESIZE_NWSE] = [NSCursor respondsToSelector:@selector(_windowResizeNorthWestSouthEastCursor)] ? [NSCursor _windowResizeNorthWestSouthEastCursor] : [NSCursor closedHandCursor]; + _sapp.macos.cursors[SAPP_MOUSECURSOR_RESIZE_NESW] = [NSCursor respondsToSelector:@selector(_windowResizeNorthEastSouthWestCursor)] ? [NSCursor _windowResizeNorthEastSouthWestCursor] : [NSCursor closedHandCursor]; + _sapp.macos.cursors[SAPP_MOUSECURSOR_RESIZE_ALL] = [NSCursor closedHandCursor]; + _sapp.macos.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; + } + _sapp.macos.view.layer.contentsScale = _sapp.dpi_scale; // NOTE: needed because we set layerContentsPlacement to a non-scaling value in windowWillStartLiveResize. + const NSRect bounds = [_sapp.macos.view bounds]; + _sapp.window_width = (int)roundf(bounds.size.width); + _sapp.window_height = (int)roundf(bounds.size.height); + #if defined(SOKOL_METAL) + _sapp.framebuffer_width = (int)roundf(bounds.size.width * _sapp.dpi_scale); + _sapp.framebuffer_height = (int)roundf(bounds.size.height * _sapp.dpi_scale); + const CGSize fb_size = _sapp.macos.view.drawableSize; + const int cur_fb_width = (int)roundf(fb_size.width); + const int cur_fb_height = (int)roundf(fb_size.height); + const bool dim_changed = (_sapp.framebuffer_width != cur_fb_width) || + (_sapp.framebuffer_height != cur_fb_height); + #elif defined(SOKOL_GLCORE) + const int cur_fb_width = (int)roundf(bounds.size.width * _sapp.dpi_scale); + const int cur_fb_height = (int)roundf(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; + #endif + if (_sapp.framebuffer_width == 0) { + _sapp.framebuffer_width = 1; + } + if (_sapp.framebuffer_height == 0) { + _sapp.framebuffer_height = 1; + } + if (_sapp.window_width == 0) { + _sapp.window_width = 1; + } + if (_sapp.window_height == 0) { + _sapp.window_height = 1; + } + if (dim_changed) { + #if defined(SOKOL_METAL) + CGSize drawable_size = { (CGFloat) _sapp.framebuffer_width, (CGFloat) _sapp.framebuffer_height }; + _sapp.macos.view.drawableSize = drawable_size; + #else + // nothing to do for GL? + #endif + if (!_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, _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 type + SOKOL_ASSERT((cursor >= 0) && (cursor < _SAPP_MOUSECURSOR_NUM)); + if (_sapp.macos.cursors[cursor]) { + [_sapp.macos.cursors[cursor] set]; + } + else { + [[NSCursor arrowCursor] set]; + } +} + +_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) { + _sapp_frame(); + 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 = (int)roundf((screen_rect.size.width * 4.0f) / 5.0f); + } + if (_sapp.window_height == 0) { + _sapp.window_height = (int)roundf((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) + NSInteger max_fps = 60; + #if (__MAC_OS_X_VERSION_MAX_ALLOWED >= 120000) + if (@available(macOS 12.0, *)) { + max_fps = [NSScreen.mainScreen maximumFramesPerSecond]; + } + #endif + _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.window.contentView = _sapp.macos.view; + [_sapp.macos.window makeFirstResponder:_sapp.macos.view]; + _sapp.macos.view.layer.magnificationFilter = kCAFilterNearest; + #elif defined(SOKOL_GLCORE) + 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]; + } + + _sapp.macos.window.contentView = _sapp.macos.view; + [_sapp.macos.window makeFirstResponder:_sapp.macos.view]; + + 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; + #endif + [_sapp.macos.window center]; + _sapp.valid = true; + if (_sapp.fullscreen) { + /* ^^^ on GL, this already toggles a rendered frame, so set the valid flag before */ + [_sapp.macos.window toggleFullScreen:self]; + } + NSApp.activationPolicy = NSApplicationActivationPolicyRegular; + [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; + } +} + +#if defined(SOKOL_METAL) +- (void)windowWillStartLiveResize:(NSNotification *)notification { + // Work around the MTKView 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), _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 + +_SOKOL_PRIVATE void _sapp_macos_poll_input_events(void) { + /* + + NOTE: late event polling temporarily out-commented to check if this + causes infrequent and almost impossible to reproduce problems with the + window close events, see: + https://github.com/floooh/sokol/pull/483#issuecomment-805148815 + + + const NSEventMask mask = NSEventMaskLeftMouseDown | + NSEventMaskLeftMouseUp| + NSEventMaskRightMouseDown | + NSEventMaskRightMouseUp | + NSEventMaskMouseMoved | + NSEventMaskLeftMouseDragged | + NSEventMaskRightMouseDragged | + NSEventMaskMouseEntered | + NSEventMaskMouseExited | + NSEventMaskKeyDown | + NSEventMaskKeyUp | + NSEventMaskCursorUpdate | + NSEventMaskScrollWheel | + NSEventMaskTabletPoint | + NSEventMaskTabletProximity | + NSEventMaskOtherMouseDown | + NSEventMaskOtherMouseUp | + NSEventMaskOtherMouseDragged | + NSEventMaskPressure | + NSEventMaskDirectTouch; + @autoreleasepool { + for (;;) { + // NOTE: using NSDefaultRunLoopMode here causes stuttering in the GL backend, + // see: https://github.com/floooh/sokol/issues/486 + NSEvent* event = [NSApp nextEventMatchingMask:mask untilDate:nil inMode:NSEventTrackingRunLoopMode dequeue:YES]; + if (event == nil) { + break; + } + [NSApp sendEvent:event]; + } + } + */ +} + +- (void)drawRect:(NSRect)rect { + _SOKOL_UNUSED(rect); + #if defined(_SAPP_ANY_GL) + glGetIntegerv(GL_FRAMEBUFFER_BINDING, (GLint*)&_sapp.gl.framebuffer); + #endif + _sapp_timing_measure(&_sapp.timing); + /* Catch any last-moment input events */ + _sapp_macos_poll_input_events(); + @autoreleasepool { + _sapp_macos_frame(); + } + #if defined(_SAPP_ANY_GL) + [[_sapp.macos.view openGLContext] flushBuffer]; + #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); + } + } +} +- (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)); + } +} +@end + +#endif // macOS + +// ██ ██████ ███████ +// ██ ██ ██ ██ +// ██ ██ ██ ███████ +// ██ ██ ██ ██ +// ██ ██████ ███████ +// +// >>ios +#if defined(_SAPP_IOS) + +_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_OBJC_RELEASE(_sapp.ios.view_ctrl); + _SAPP_OBJC_RELEASE(_sapp.ios.mtl_device); + #else + _SAPP_OBJC_RELEASE(_sapp.ios.view_ctrl); + _SAPP_OBJC_RELEASE(_sapp.ios.eagl_ctx); + #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_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.framebuffer_width = (int)roundf(screen_rect.size.width * _sapp.dpi_scale); + _sapp.framebuffer_height = (int)roundf(screen_rect.size.height * _sapp.dpi_scale); + _sapp.window_width = (int)roundf(screen_rect.size.width); + _sapp.window_height = (int)roundf(screen_rect.size.height); + int cur_fb_width, cur_fb_height; + #if defined(SOKOL_METAL) + const CGSize fb_size = _sapp.ios.view.drawableSize; + cur_fb_width = (int)roundf(fb_size.width); + cur_fb_height = (int)roundf(fb_size.height); + #else + cur_fb_width = (int)roundf(_sapp.ios.view.drawableWidth); + cur_fb_height = (int)roundf(_sapp.ios.view.drawableHeight); + #endif + const bool dim_changed = (_sapp.framebuffer_width != cur_fb_width) || + (_sapp.framebuffer_height != cur_fb_height); + if (dim_changed) { + #if defined(SOKOL_METAL) + const CGSize drawable_size = { (CGFloat) _sapp.framebuffer_width, (CGFloat) _sapp.framebuffer_height }; + _sapp.ios.view.drawableSize = drawable_size; + #else + // nothing to do here, GLKView correctly respects the view's contentScaleFactor + #endif + if (!_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]; + + [[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]; + } + 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 = (int)roundf(screen_rect.size.width); + _sapp.window_height = (int)roundf(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 = (int)roundf(_sapp.window_width * _sapp.dpi_scale); + _sapp.framebuffer_height = (int)roundf(_sapp.window_height * _sapp.dpi_scale); + NSInteger max_fps = UIScreen.mainScreen.maximumFramesPerSecond; + #if defined(SOKOL_METAL) + _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; + _sapp.ios.view.multipleTouchEnabled = YES; + _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; + #else + _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; + #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 (_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; + } +} +- (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 (_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; + } +} +- (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)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"); +#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, _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), _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); +} + +#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, (const char* canvas_name_cstr), { + Module.sokol_drop_files = []; + const canvas_name = UTF8ToString(canvas_name_cstr); + const canvas = document.getElementById(canvas_name); + 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); + }; + 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, (const char* canvas_name_cstr), { + const canvas_name = UTF8ToString(canvas_name_cstr); + const canvas = document.getElementById(canvas_name); + 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), { + // lookup and store canvas object by name + const target_str = UTF8ToString(c_str_target); + Module.sapp_emsc_target = document.getElementById(target_str); + if (!Module.sapp_emsc_target) { + console.log("sokol_app.h: invalid target:" + target_str); + } + if (!Module.sapp_emsc_target.requestPointerLock) { + console.log("sokol_app.h: target doesn't support requestPointerLock:" + target_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), { + if (Module.sapp_emsc_target) { + let cursor; + if (shown === 0) { + cursor = "none"; + } + 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)); + sapp_js_set_cursor((int)cursor, shown ? 1 : 0); +} + +/* 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; +} + +#if defined(SOKOL_WGPU) +_SOKOL_PRIVATE void _sapp_emsc_wgpu_size_changed(void); +#endif + +_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 = (int)roundf(w); + } + if (h < 1.0) { + h = ui_event->windowInnerHeight; + } + else { + _sapp.window_height = (int)roundf(h); + } + if (_sapp.desc.high_dpi) { + _sapp.dpi_scale = emscripten_get_device_pixel_ratio(); + } + _sapp.framebuffer_width = (int)roundf(w * _sapp.dpi_scale); + _sapp.framebuffer_height = (int)roundf(h * _sapp.dpi_scale); + SOKOL_ASSERT((_sapp.framebuffer_width > 0) && (_sapp.framebuffer_height > 0)); + 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_emsc_wgpu_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); + + // FIXME: remove PVRTC support here and in sokol-gfx at some point + // some WebGL extension are not enabled automatically by emscripten + emscripten_webgl_enable_extension(ctx, "WEBKIT_WEBGL_compressed_texture_pvrtc"); +} +#endif + +#if defined(SOKOL_WGPU) + +_SOKOL_PRIVATE void _sapp_emsc_wgpu_create_swapchain(void) { + SOKOL_ASSERT(_sapp.wgpu.instance); + SOKOL_ASSERT(_sapp.wgpu.device); + SOKOL_ASSERT(0 == _sapp.wgpu.surface); + SOKOL_ASSERT(0 == _sapp.wgpu.swapchain); + 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); + SOKOL_ASSERT(0 == _sapp.wgpu.swapchain_view); + + WGPUSurfaceDescriptorFromCanvasHTMLSelector canvas_desc; + _sapp_clear(&canvas_desc, sizeof(canvas_desc)); + canvas_desc.chain.sType = WGPUSType_SurfaceDescriptorFromCanvasHTMLSelector; + canvas_desc.selector = _sapp.html5_canvas_selector; + WGPUSurfaceDescriptor surf_desc; + _sapp_clear(&surf_desc, sizeof(surf_desc)); + surf_desc.nextInChain = &canvas_desc.chain; + _sapp.wgpu.surface = wgpuInstanceCreateSurface(_sapp.wgpu.instance, &surf_desc); + if (0 == _sapp.wgpu.surface) { + _SAPP_PANIC(WGPU_SWAPCHAIN_CREATE_SURFACE_FAILED); + } + _sapp.wgpu.render_format = wgpuSurfaceGetPreferredFormat(_sapp.wgpu.surface, _sapp.wgpu.adapter); + + WGPUSwapChainDescriptor sc_desc; + _sapp_clear(&sc_desc, sizeof(sc_desc)); + sc_desc.usage = WGPUTextureUsage_RenderAttachment; + sc_desc.format = _sapp.wgpu.render_format; + sc_desc.width = (uint32_t)_sapp.framebuffer_width; + sc_desc.height = (uint32_t)_sapp.framebuffer_height; + sc_desc.presentMode = WGPUPresentMode_Fifo; + _sapp.wgpu.swapchain = wgpuDeviceCreateSwapChain(_sapp.wgpu.device, _sapp.wgpu.surface, &sc_desc); + if (0 == _sapp.wgpu.swapchain) { + _SAPP_PANIC(WGPU_SWAPCHAIN_CREATE_SWAPCHAIN_FAILED); + } + + 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_emsc_wgpu_discard_swapchain(void) { + 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 (_sapp.wgpu.swapchain) { + wgpuSwapChainRelease(_sapp.wgpu.swapchain); + _sapp.wgpu.swapchain = 0; + } + if (_sapp.wgpu.surface) { + wgpuSurfaceRelease(_sapp.wgpu.surface); + _sapp.wgpu.surface = 0; + } +} + +_SOKOL_PRIVATE void _sapp_emsc_wgpu_size_changed(void) { + _sapp_emsc_wgpu_discard_swapchain(); + _sapp_emsc_wgpu_create_swapchain(); +} + +_SOKOL_PRIVATE void _sapp_emsc_wgpu_request_device_cb(WGPURequestDeviceStatus status, WGPUDevice device, const char* msg, void* userdata) { + _SOKOL_UNUSED(msg); + _SOKOL_UNUSED(userdata); + SOKOL_ASSERT(!_sapp.wgpu.async_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; + _sapp_emsc_wgpu_create_swapchain(); + _sapp.wgpu.async_init_done = true; +} + +_SOKOL_PRIVATE void _sapp_emsc_wgpu_request_adapter_cb(WGPURequestAdapterStatus status, WGPUAdapter adapter, const char* msg, void* userdata) { + _SOKOL_UNUSED(msg); + _SOKOL_UNUSED(userdata); + 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; + 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(adapter, WGPUFeatureName_TextureCompressionBC)) { + SOKOL_ASSERT(cur_feature_index < _SAPP_WGPU_MAX_REQUESTED_FEATURES); + requiredFeatures[cur_feature_index++] = WGPUFeatureName_TextureCompressionBC; + } + if (wgpuAdapterHasFeature(adapter, WGPUFeatureName_TextureCompressionETC2)) { + SOKOL_ASSERT(cur_feature_index < _SAPP_WGPU_MAX_REQUESTED_FEATURES); + requiredFeatures[cur_feature_index++] = WGPUFeatureName_TextureCompressionETC2; + } + if (wgpuAdapterHasFeature(adapter, WGPUFeatureName_TextureCompressionASTC)) { + SOKOL_ASSERT(cur_feature_index < _SAPP_WGPU_MAX_REQUESTED_FEATURES); + requiredFeatures[cur_feature_index++] = WGPUFeatureName_TextureCompressionASTC; + } + if (wgpuAdapterHasFeature(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 + + WGPUDeviceDescriptor dev_desc; + _sapp_clear(&dev_desc, sizeof(dev_desc)); + dev_desc.requiredFeatureCount = cur_feature_index; + dev_desc.requiredFeatures = requiredFeatures, + wgpuAdapterRequestDevice(adapter, &dev_desc, _sapp_emsc_wgpu_request_device_cb, 0); +} + +_SOKOL_PRIVATE void _sapp_emsc_wgpu_init(void) { + SOKOL_ASSERT(0 == _sapp.wgpu.instance); + SOKOL_ASSERT(!_sapp.wgpu.async_init_done); + _sapp.wgpu.instance = wgpuCreateInstance(0); + if (0 == _sapp.wgpu.instance) { + _SAPP_PANIC(WGPU_CREATE_INSTANCE_FAILED); + } + // FIXME: power preference? + wgpuInstanceRequestAdapter(_sapp.wgpu.instance, 0, _sapp_emsc_wgpu_request_adapter_cb, 0); +} + +_SOKOL_PRIVATE void _sapp_emsc_wgpu_frame(void) { + if (_sapp.wgpu.async_init_done) { + _sapp.wgpu.swapchain_view = wgpuSwapChainGetCurrentTextureView(_sapp.wgpu.swapchain); + _sapp_frame(); + wgpuTextureViewRelease(_sapp.wgpu.swapchain_view); + _sapp.wgpu.swapchain_view = 0; + } +} +#endif // SOKOL_WGPU + +_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); + sapp_js_add_beforeunload_listener(); + if (_sapp.clipboard.enabled) { + sapp_js_add_clipboard_listener(); + } + if (_sapp.drop.enabled) { + sapp_js_add_dragndrop_listeners(&_sapp.html5_canvas_selector[1]); + } + #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); + 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(&_sapp.html5_canvas_selector[1]); + } + #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_emsc_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(); + _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_js_init(&_sapp.html5_canvas_selector[1]); + 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 = (int)roundf(w); + _sapp.window_height = (int)roundf(h); + _sapp.framebuffer_width = (int)roundf(w * _sapp.dpi_scale); + _sapp.framebuffer_height = (int)roundf(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_emsc_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 feature_level; + HRESULT hr = D3D11CreateDeviceAndSwapChain( + NULL, /* pAdapter (use default) */ + D3D_DRIVER_TYPE_HARDWARE, /* DriverType */ + NULL, /* Software */ + create_flags, /* Flags */ + NULL, /* pFeatureLevels */ + 0, /* FeatureLevels */ + D3D11_SDK_VERSION, /* SDKVersion */ + sc_desc, /* pSwapChainDesc */ + &_sapp.d3d11.swap_chain, /* ppSwapChain */ + &_sapp.d3d11.device, /* ppDevice */ + &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 &= ~D3D11_CREATE_DEVICE_DEBUG; + hr = D3D11CreateDeviceAndSwapChain( + NULL, /* pAdapter (use default) */ + D3D_DRIVER_TYPE_HARDWARE, /* DriverType */ + NULL, /* Software */ + create_flags, /* Flags */ + NULL, /* pFeatureLevels */ + 0, /* FeatureLevels */ + D3D11_SDK_VERSION, /* SDKVersion */ + sc_desc, /* pSwapChainDesc */ + &_sapp.d3d11.swap_chain, /* ppSwapChain */ + &_sapp.d3d11.device, /* ppDevice */ + &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; + + /* 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 = (int)roundf(window_width); + _sapp.window_height = (int)roundf(window_height); + int fb_width = (int)roundf(window_width * _sapp.win32.dpi.content_scale); + int fb_height = (int)roundf(window_height * _sapp.win32.dpi.content_scale); + /* prevent a framebuffer size of 0 when window is minimized */ + if (0 == fb_width) { + fb_width = 1; + } + if (0 == fb_height) { + fb_height = 1; + } + 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.cursors[cursor] = (HCURSOR)LoadImageW(NULL, MAKEINTRESOURCEW(id), IMAGE_CURSOR, 0, 0, LR_DEFAULTSIZE|LR_SHARED); + } + // fallback: default cursor + if (0 == _sapp.win32.cursors[cursor]) { + // 32512 => IDC_ARROW + _sapp.win32.cursors[cursor] = LoadCursorW(NULL, MAKEINTRESOURCEW(32512)); + } + SOKOL_ASSERT(0 != _sapp.win32.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) { + // NOTE: when called from WM_SETCURSOR, the area test would be redundant + if (!skip_area_test) { + if (!_sapp_win32_cursor_in_content_area()) { + return; + } + } + if (!shown) { + SetCursor(NULL); + } + else { + SOKOL_ASSERT((cursor >= 0) && (cursor < _SAPP_MOUSECURSOR_NUM)); + SOKOL_ASSERT(0 != _sapp.win32.cursors[cursor]); + SetCursor(_sapp.win32.cursors[cursor]); + } +} + +_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 void _sapp_win32_lock_mouse(bool lock) { + if (lock == _sapp.mouse.locked) { + return; + } + _sapp.mouse.dx = 0.0f; + _sapp.mouse.dy = 0.0f; + _sapp.mouse.locked = lock; + _sapp_win32_release_mouse(0xFF); + if (_sapp.mouse.locked) { + /* store the current mouse position, so it can be restored when unlocked */ + POINT pos; + BOOL res = GetCursorPos(&pos); + SOKOL_ASSERT(res); _SOKOL_UNUSED(res); + _sapp.win32.mouse_locked_x = pos.x; + _sapp.win32.mouse_locked_y = pos.y; + + /* while the mouse is locked, make the mouse cursor invisible and + confine the mouse movement to a small rectangle inside our window + (so that we don't miss any mouse up events) + */ + RECT client_rect = { + _sapp.win32.mouse_locked_x, + _sapp.win32.mouse_locked_y, + _sapp.win32.mouse_locked_x, + _sapp.win32.mouse_locked_y + }; + ClipCursor(&client_rect); + + /* make the mouse cursor invisible, this will stack with sapp_show_mouse() */ + ShowCursor(FALSE); + + /* 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.raw_input_mousepos_valid = false; + } + else { + /* 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); + } + + /* let the mouse roam freely again */ + ClipCursor(NULL); + ShowCursor(TRUE); + + /* restore the 'pre-locked' mouse position */ + BOOL res = SetCursorPos(_sapp.win32.mouse_locked_x, _sapp.win32.mouse_locked_y); + SOKOL_ASSERT(res); _SOKOL_UNUSED(res); + } +} + +_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)) { + _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) { + if ((_sapp.d3d11.sync_refresh_count + 1) != dxgi_stats.SyncRefreshCount) { + _sapp_timing_discontinuity(&_sapp.timing); + } + _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 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: + /* if focus is lost for any reason, and we're in mouse locked mode, disable mouse lock */ + if (_sapp.mouse.locked) { + _sapp_win32_lock_mouse(false); + } + _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.raw_input_mousepos_valid) { + _sapp.mouse.dx = (float) (new_x - _sapp.win32.raw_input_mousepos_x); + _sapp.mouse.dy = (float) (new_y - _sapp.win32.raw_input_mousepos_y); + } + _sapp.win32.raw_input_mousepos_x = new_x; + _sapp.win32.raw_input_mousepos_y = new_y; + _sapp.win32.raw_input_mousepos_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_frame(); + #if defined(SOKOL_D3D11) + // present with DXGI_PRESENT_DO_NOT_WAIT + _sapp_d3d11_present(true); + #endif + #if defined(SOKOL_GLCORE) + _sapp_wgl_swap_buffers(); + #endif + /* 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; + 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.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) { + 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 = true; + icon_info.xHotspot = 0; + icon_info.yHotspot = 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]); + HICON sml_icon = _sapp_win32_create_icon_from_image(&icon_desc->images[sml_img_index]); + + // 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(); + #endif + #if defined(SOKOL_GLCORE) + _sapp_wgl_init(); + _sapp_wgl_load_extensions(); + _sapp_wgl_create_context(); + #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_frame(); + #if defined(SOKOL_D3D11) + _sapp_d3d11_present(false); + if (IsIconic(_sapp.win32.hwnd)) { + Sleep((DWORD)(16 * _sapp.swap_interval)); + } + #endif + #if defined(SOKOL_GLCORE) + _sapp_wgl_swap_buffers(); + #endif + /* 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(); + #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); + } + } + _sapp_call_cleanup(); + + #if defined(SOKOL_D3D11) + _sapp_d3d11_destroy_default_render_target(); + _sapp_d3d11_destroy_device_and_swapchain(); + #else + _sapp_wgl_destroy_context(); + _sapp_wgl_shutdown(); + #endif + _sapp_win32_destroy_window(); + _sapp_win32_destroy_icons(); + _sapp_win32_restore_console(); + _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; +} + +#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; +} +#else +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_MAIN */ +#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_CLIENT_VERSION, 3, + 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); + 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 void _sapp_x11_query_window_size(void) { + XWindowAttributes attribs; + XGetWindowAttributes(_sapp.x11.display, _sapp.x11.window, &attribs); + _sapp.window_width = attribs.width; + _sapp.window_height = attribs.height; + _sapp.framebuffer_width = _sapp.window_width; + _sapp.framebuffer_height = _sapp.window_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.cursors[cursor] = XcursorImageLoadCursor(_sapp.x11.display, img); + XcursorImageDestroy(img); + } + } + if (0 == _sapp.x11.cursors[cursor]) { + _sapp.x11.cursors[cursor] = XCreateFontCursor(_sapp.x11.display, fallback_native); + } +} + +_SOKOL_PRIVATE void _sapp_x11_create_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_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.cursors[i]) { + XFreeCursor(_sapp.x11.display, _sapp.x11.cursors[i]); + _sapp.x11.cursors[i] = 0; + } + } +} + +_SOKOL_PRIVATE void _sapp_x11_toggle_fullscreen(void) { + _sapp.fullscreen = !_sapp.fullscreen; + _sapp_x11_set_fullscreen(_sapp.fullscreen); + _sapp_x11_query_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.x11.cursors[cursor]) { + XDefineCursor(_sapp.x11.display, _sapp.x11.window, _sapp.x11.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_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, int depth) { + _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); + int window_width = _sapp.window_width; + int window_height = _sapp.window_height; + if (0 == window_width) { + window_width = (display_width * 4) / 5; + } + if (0 == window_height) { + window_height = (display_height * 4) / 5; + } + int window_xpos = (display_width - window_width) / 2; + int window_ypos = (display_height - window_height) / 2; + if (window_xpos < 0) { + window_xpos = 0; + } + if (window_ypos < 0) { + window_ypos = 0; + } + _sapp_x11_grab_error_handler(); + _sapp.x11.window = XCreateWindow(_sapp.x11.display, + _sapp.x11.root, + window_xpos, + window_ypos, + (uint32_t)window_width, + (uint32_t)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); + + XSizeHints* hints = XAllocSizeHints(); + hints->flags = (PWinGravity | PPosition | PSize); + hints->win_gravity = StaticGravity; + hints->x = window_xpos; + hints->y = window_ypos; + hints->width = window_width; + hints->height = window_height; + 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_query_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); + 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 void _sapp_x11_app_event(sapp_event_type type) { + if (_sapp_events_enabled()) { + _sapp_init_event(type); + _sapp_call_event(&_sapp.event); + } +} + +_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_configurenotify(XEvent* event) { + if ((event->xconfigure.width != _sapp.window_width) || (event->xconfigure.height != _sapp.window_height)) { + _sapp.window_width = event->xconfigure.width; + _sapp.window_height = event->xconfigure.height; + _sapp.framebuffer_width = _sapp.window_width; + _sapp.framebuffer_height = _sapp.window_height; + _sapp_x11_app_event(SAPP_EVENTTYPE_RESIZED); + } +} + +_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); + } + } +} + +_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_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 ConfigureNotify: + _sapp_x11_on_configurenotify(event); + break; + case PropertyNotify: + _sapp_x11_on_propertynotify(event); + break; + case SelectionNotify: + _sapp_x11_on_selectionnotify(event); + break; + case DestroyNotify: + // not a bug + break; + case ClientMessage: + _sapp_x11_on_clientmessage(event); + break; + } +} + +#if !defined(_SAPP_GLX) + +_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[] = { + #if defined(SOKOL_GLCORE) + EGL_CONTEXT_MAJOR_VERSION, _sapp.desc.gl_major_version, + EGL_CONTEXT_MINOR_VERSION, _sapp.desc.gl_minor_version, + EGL_CONTEXT_OPENGL_PROFILE_MASK, EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT, + #elif defined(SOKOL_GLES3) + EGL_CONTEXT_CLIENT_VERSION, 3, + #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_GLX */ + +_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(); + _sapp.dpi_scale = _sapp.x11.dpi / 96.0f; + _sapp_x11_init_extensions(); + _sapp_x11_create_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); +#else + _sapp_egl_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_frame(); +#if defined(_SAPP_GLX) + _sapp_glx_swap_buffers(); +#else + eglSwapBuffers(_sapp.egl.display, _sapp.egl.surface); +#endif + 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(); +#else + _sapp_egl_destroy(); +#endif + _sapp_x11_destroy_window(); + _sapp_x11_destroy_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(_SAPP_EMSCRIPTEN) && 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_GLX) + 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_GLX) + 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(); + #endif +} + +/* 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) { + #if defined(_SAPP_MACOS) + _sapp_macos_update_cursor(_sapp.mouse.current_cursor, show); + #elif defined(_SAPP_WIN32) + _sapp_win32_update_cursor(_sapp.mouse.current_cursor, show, false); + #elif defined(_SAPP_LINUX) + _sapp_x11_update_cursor(_sapp.mouse.current_cursor, show); + #elif defined(_SAPP_EMSCRIPTEN) + _sapp_emsc_update_cursor(_sapp.mouse.current_cursor, show); + #endif + _sapp.mouse.shown = 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) { + #if defined(_SAPP_MACOS) + _sapp_macos_update_cursor(cursor, _sapp.mouse.shown); + #elif defined(_SAPP_WIN32) + _sapp_win32_update_cursor(cursor, _sapp.mouse.shown, false); + #elif defined(_SAPP_LINUX) + _sapp_x11_update_cursor(cursor, _sapp.mouse.shown); + #elif defined(_SAPP_EMSCRIPTEN) + _sapp_emsc_update_cursor(cursor, _sapp.mouse.shown); + #endif + _sapp.mouse.current_cursor = cursor; + } +} + +SOKOL_API_IMPL sapp_mouse_cursor sapp_get_mouse_cursor(void) { + return _sapp.mouse.current_cursor; +} + +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); + #else + /* not implemented */ + #endif + _sapp_strcpy(str, _sapp.clipboard.buffer, _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(); + #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(_SAPP_EMSCRIPTEN) && 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(_SAPP_EMSCRIPTEN) && 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(_SAPP_EMSCRIPTEN) && 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(_SAPP_EMSCRIPTEN) && 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(SOKOL_GLCORE) + 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(SOKOL_GLCORE) + return _sapp.desc.gl_minor_version; + #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/inc/sokol/sokol_gfx.h b/inc/sokol/sokol_gfx.h new file mode 100644 index 0000000..0e5ac49 --- /dev/null +++ b/inc/sokol/sokol_gfx.h @@ -0,0 +1,22314 @@ +#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. + + If you want to compile without deprecated structs and functions, + define: + + SOKOL_NO_DEPRECATED + + Optionally define the following to force debug checks and validations + even in release mode: + + SOKOL_DEBUG - by default this is defined if _DEBUG is 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 (at least buffers, shaders and pipelines, + and optionally images, samplers and render/compute-pass-attachments): + + sg_buffer sg_make_buffer(const sg_buffer_desc*) + sg_image sg_make_image(const sg_image_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*) + sg_attachments sg_make_attachments(const sg_attachments_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, an sg_attachment + object handle is required instead of an sg_swapchain struct. An offscreen + pass is started like this (assuming attachments is an sg_attachments handle): + + sg_begin_pass(&(sg_pass){ .action = { ... }, .attachments = attachments }); + + To start a compute-pass, just set the .compute item to true: + + sg_begin_pass(&(sg_pass){ .compute = true }); + + If the compute pass writes into storage images, provide those as + 'storage attachments' via an sg_attachments object: + + sg_begin_pass(&(sg_pass){ .compute = true, .attachments = attattachments }); + + --- 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 images, + samplers and storage-buffers), 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 are allowed and will be rejected by the 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. + + --- ...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_attachments(sg_attachments atts) + + --- 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_default_pass() and 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_attachments_desc sg_query_attachments_desc(sg_attachments atts) + + ...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_attachments_desc sg_query_attachments_defaults(const sg_attachments_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_attachments_info sg_query_attachments_info(sg_attachments atts) + + ...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 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 '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 + are baked into an immutable sg_attachments object. + + For a simple offscreen scenario with one color-, one depth-stencil-render + target and without multisampling, creating an attachment object 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: + + const sg_image color_img = sg_make_image(&(sg_image_desc){ + .render_target = true, + .width = 256, + .height = 256, + .pixel_format = SG_PIXELFORMAT_RGBA8, + .sample_count = 1, + }); + const sg_image depth_img = sg_make_image(&(sg_image_desc){ + .render_target = 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 an attachments object: + + const sg_attachments atts = sg_make_attachments(&(sg_attachments_desc){ + .colors[0].image = color_img, + .depth_stencil.image = depth_img, + }); + + This attachments object is then passed into the sg_begin_pass() function + in place of the swapchain struct: + + sg_begin_pass(&(sg_pass){ .attachments = atts }); + + Swapchain and offscreen passes form dependency trees each with a swapchain + pass at the root, offscreen passes as nodes, and render target images as + dependencies between passes. + + sg_pass_action structs are used to define actions that should happen at the + start and end of rendering 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 = attachments, + }); + ... + 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 happens at attachments creation time, for instance + to render into mipmap 2 of slice 3 of an array texture: + + const sg_attachments atts = sg_make_attachments(&(sg_attachments_desc){ + .colors[0] = { + .image = color_img, + .mip_level = 2, + .slice = 3, + }, + .depth_stencil.image = depth_img, + }); + + 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. + + Creating a simple attachments object for multisampled rendering requires + 3 attachment images: the color attachment image which has a sample + count > 1, a resolve attachment image of the same size and pixel format + but a sample count == 1, and a depth/stencil attachment image with + the same size and sample count as the color attachment image: + + const sg_image color_img = sg_make_image(&(sg_image_desc){ + .render_target = true, + .width = 256, + .height = 256, + .pixel_format = SG_PIXELFORMAT_RGBA8, + .sample_count = 4, + }); + const sg_image resolve_img = sg_make_image(&(sg_image_desc){ + .render_target = true, + .width = 256, + .height = 256, + .pixel_format = SG_PIXELFORMAT_RGBA8, + .sample_count = 1, + }); + const sg_image depth_img = sg_make_image(&(sg_image_desc){ + .render_target = true, + .width = 256, + .height = 256, + .pixel_format = SG_PIXELFORMAT_DEPTH, + .sample_count = 4, + }); + + ...create the attachments object: + + const sg_attachments atts = sg_make_attachments(&(sg_attachments_desc){ + .colors[0].image = color_img, + .resolves[0].image = resolve_img, + .depth_stencil.image = depth_img, + }); + + If an attachments object defines a resolve image in a specific resolve attachment slot, + an 'msaa resolve operation' will happen in sg_end_pass(). + + In this scenario, the content of the MSAA color attachment doesn't need to be + preserved (since it's only needed inside sg_end_pass for the msaa-resolve), so + the .store_action should be set to "don't care": + + const sg_pass_action = { + .colors[0] = { + .load_action = SG_LOADACTION_CLEAR, + .store_action = SG_STOREACTION_DONTCARE, + .clear_value = { 0.0f, 0.0f, 0.0f, 1.0f } + } + }; + + The actual render pass looks as usual: + + sg_begin_pass(&(sg_pass){ .action = pass_action, .attachments = atts }); + ... + sg_end_pass(); + + ...after sg_end_pass() the only difference to the non-msaa scenario is that the + rendering result which is going to be used as texture in a followup pass is + in 'resolve_img', not in 'color_img' (in fact, trying to bind color_img as a + texture would result in a validation error). + + + 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 which only updates storage buffers is started with: + + sg_begin_pass(&(sg_pass){ .compute = true }); + + ...if the compute pass updates storage images, the images must be 'bound' + via an sg_attachments object: + + sg_begin_pass(&(sg_pass){ .compute = true, .attachments = attachments }); + + Image objects in such a compute pass attachments object must be created with + `storage_attachment` usage: + + sg_image storage_image = sg_make_image(&(sg_image_desc){ + .usage = { + .storage_attachment = true, + }, + // ... + }); + + ...a compute pass is 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 input, only storage-buffers, textures, non-filtering + samplers and images via storage attachments (more details on compute shaders in + the following section). + + 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 support, 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 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 storage buffer binding used in the shader: + - 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..23 (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..11 (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..15 + - 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..7 + - note that storage buffer bindings are not supported on all backends + and platforms + + - A description of each storage image binding used in the shader (only supported + in compute shaders): + - 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..11, the + bind slot must not collide with UAV storage buffer bindings + - Metal/MSL: the texture bind slot N (`[[texture(N)]])` where N is 0..19, + the bind slot must not collide with other texture bindings on the same + stage + - WebGPU/WGSL: the binding N in `@group(2) @binding(N)` where N is 0..3 + - GL/GLSL: the buffer binding N in `layout(binding=N)` where N is 0..3 + - note that storage image bindings are not supported on all backends and platforms + + - A description of each texture binding used in the shader: + - 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..23 + (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..19 + (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 + + - 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..15 + - Metal/MSL: the sampler bind slot N (`[[sampler(N)]]`) where N is 0..15 + - WebGPU/WGSL: the binding N in `@group(0) @binding(N)` where N is 0..127 + + - An array of 'image-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..t23)` + - read/write storage buffer and storage image bindings: `register(u0..u11)` + - samplers: `register(s0..s15)` + - Metal/MSL: + - separate bindslot space per shader stage + - uniform blocks: `[[buffer(0..7)]]` + - storage buffers: `[[buffer(8..15)]]` + - textures and storage image bindings: `[[texture(0..19)]]` + - samplers: `[[sampler(0..15)]]` + - WebGPU/WGSL: + - common bindslot space across shader stages + - uniform blocks: `@group(0) @binding(0..15)` + - textures, samplers and storage buffers: `@group(1) @binding(0..127)` + - storage image bindings: `@group(2) @binding(0..3)` + - GL/GLSL: + - uniforms and image-samplers are bound by name + - storage buffer bindings: `layout(std430, binding=0..7)` (common + bindslot space across shader stages) + - storage image bindings: `layout(binding=0..3, [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..7 in the vertex shader, and 8..15 in the fragment shader + + 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_attachment usage`: + + sg_image storage_image = sg_make_image(&(sg_image_desc){ + .usage = { + .storage_attachment = true, + }, + .width = ..., + .height = ..., + .pixel_format = ..., + }); + + ...next the image object must be wrapped in an attachment object, this allows + to pick a specific mipmap level or slice to be accessed by the compute shader: + + sg_attachments storage_attachment = sg_make_attachment(&(sg_attachments_desc){ + .storages[0] = { + .image = storage_image, + .mip_level = ..., + .slice = ..., + }, + }); + + Finally 'bind' the storage image as pass attachment in the `sg_begin_pass` + call of a compute pass: + + sg_begin_pass(&(sg_pass){ .compute = true, .attachments = storage_attachments }); + ... + sg_end_pass(); + + Storage attachments should only be accessed as `readwrite` or `writeonly` mode + in compute shaders because if the limited bind space of up to 4 slots. For + readonly access, just bind the storage image as regular texture via + `sg_apply_bindings()`. + + 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 + + NOTE: in the (hopefully not-too-distant) future, working with storage + images will change by moving the resource binding from pass attachments to + regular bindings via `sg_apply_bindings()`, but this requires the + introduction of resource view objects into sokol-gfx (see planning + ticket: https://github.com/floooh/sokol/issues/1252) + + 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_attachments sg_make_attachments(const sg_attachments_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_attachments sg_alloc_attachments(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_attachments(sg_attachments atts, const sg_attachments_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_attachments(sg_attachments pass) + + 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_attachments(sg_attachments atts) + + 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_attachments(sg_attachments atts) + + 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_attachments(sg_attachments atts) + + 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 + and images, but not for samplers, shaders, pipelines or attachments. 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. Same when trying to create + an attachments object with invalid image objects. + + + 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 and storage buffers must use `@group(1)` and + bindings must be in the range 0..127: + + @group(1) @binding(0..127) + + All storage image attachments must use `@group(2)` and bindings + must be in the range 0..3: + + @group(2) @binding(0..3) + + Note that the number of texture, sampler and storage buffer 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 + + If you use sokol-shdc to generate WGSL shader code, you don't need to worry + about the above binding conventions since sokol-shdc. + + - 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_attachments: a baked collection of render pass attachment images + + 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_attachments { uint32_t id; } sg_attachments; + +/* + 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 = 4, + SG_MAX_STORAGE_ATTACHMENTS = 4, + SG_MAX_UNIFORMBLOCK_MEMBERS = 16, + SG_MAX_VERTEX_ATTRIBUTES = 16, + SG_MAX_MIPMAPS = 16, + SG_MAX_TEXTUREARRAY_LAYERS = 128, + SG_MAX_UNIFORMBLOCK_BINDSLOTS = 8, + SG_MAX_VERTEXBUFFER_BINDSLOTS = 8, + SG_MAX_IMAGE_BINDSLOTS = 16, + SG_MAX_SAMPLER_BINDSLOTS = 16, + SG_MAX_STORAGEBUFFER_BINDSLOTS = 8, + SG_MAX_IMAGE_SAMPLER_PAIRS = 16, +}; + +/* + 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_image_bindings; // if true, multisampled images can be bound as texture resources + bool separate_buffer_types; // cannot use the same buffer for vertex and indices (onlu WebGL2) +} 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 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) +} 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_cube_face + + The cubemap faces. Use these as indices in the sg_image_desc.content + array. +*/ +typedef enum sg_cube_face { + SG_CUBEFACE_POS_X, + SG_CUBEFACE_NEG_X, + SG_CUBEFACE_POS_Y, + SG_CUBEFACE_NEG_Y, + SG_CUBEFACE_POS_Z, + SG_CUBEFACE_NEG_Z, + SG_CUBEFACE_NUM, + _SG_CUBEFACE_FORCE_U32 = 0x7FFFFFFF +} sg_cube_face; + +/* + 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 structs, 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_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 and + an sg_attachments handle: + + sg_begin_pass(&(sg_pass){ + .action = { ... }, + .attachments = attachments, + }); + + 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 buffers, images and + samplers 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 + - 0..N vertex buffer offsets + - 0..1 index buffers + - 0..1 index buffer offsets + - 0..N images + - 0..N samplers + - 0..N storage buffers + + Where 'N' is defined in the following constants: + + - SG_MAX_VERTEXBUFFER_BINDSLOTS + - SG_MAX_IMAGE_BINDLOTS + - SG_MAX_SAMPLER_BINDSLOTS + - SG_MAX_STORAGEBUFFER_BINDGLOTS + + Note that inside compute passes vertex- and index-buffer-bindings are + disallowed. + + When using sokol-shdc for shader authoring, the `layout(binding=N)` + annotation in the shader code directly maps to the slot index for that + resource type in the bindings struct, 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=0) uniform texture2D vs_tex; + layout(binding=0) uniform sampler vs_smp; + ... + @end + + @fs fs + layout(binding=1) uniform fs_params { ... }; + layout(binding=1) 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] = ..., + .images[0] = vs_tex, + .images[1] = fs_tex, + .samplers[0] = vs_smp, + .samplers[1] = fs_smp, + .storage_buffers[0] = ssbo, + }; + + ...alternatively you can use code-generated slot indices: + + const sg_bindings bnd = { + .vertex_buffers[0] = ..., + .images[IMG_vs_tex] = vs_tex, + .images[IMG_fs_tex] = fs_tex, + .samplers[SMP_vs_smp] = vs_smp, + .samplers[SMP_fs_smp] = fs_smp, + .storage_buffers[SBUF_ssbo] = ssbo, + }; + + 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-resource 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_image images[SG_MAX_IMAGE_BINDSLOTS]; + sg_sampler samplers[SG_MAX_SAMPLER_BINDSLOTS]; + sg_buffer storage_buffers[SG_MAX_STORAGEBUFFER_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 bound as vertex buffer via sg_bindings.vertex_buffers[] + .index_buffer (default: false) + the buffer will bound as index buffer via sg_bindings.index_buffer + .storage_buffer (default: false) + the buffer will bound as storage buffer via sg_bindings.storage_buffers[] + .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 how the image object is going to be used: + + .render_attachment (default: false) + the image object is used as color-, resolve- or depth-stencil- + attachment in a render pass + .storage_attachment (default: false) + the image object is used as storage-attachment in a + compute pass (to be written to by compute shaders) + .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 the usage as texture binding is implicit and always allowed. +*/ +typedef struct sg_image_usage { + bool render_attachment; + bool storage_attachment; + bool immutable; + bool dynamic_update; + bool stream_update; +} sg_image_usage; + +/* + sg_image_data + + Defines the content of an image through a 2D array of sg_range structs. + The first array dimension is the cubemap face, and the second array + dimension the mipmap level. +*/ +typedef struct sg_image_data { + sg_range subimage[SG_CUBEFACE_NUM][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 (non-attachment) images with usage.immutable must be fully initialized by + providing a valid .data member which points to initialization data. + + Images with usage.render_attachment or usage.storage_attachment must + *not* be created with initial content. Be aware that the initial + content of render- and storage-attachment images is undefined. + + 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 + .d3d11_shader_resource_view + .wgpu_texture + .wgpu_texture_view + + 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) + + For D3D11 and WebGPU, either only provide a texture, or both a texture and + shader-resource-view / texture-view object. If you want to use access the + injected texture in a shader you *must* provide a shader-resource-view. + + 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* d3d11_shader_resource_view; + const void* wgpu_texture; + const void* wgpu_texture_view; + 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 binding used by the shader: + - the shader stage the texture 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..23)` + - MSL: the texture attribute `[[texture(0..19)]]` + - WGSL: the binding in `@group(1) @binding(0..127)` + + - 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..15)` + - MSL: the sampler attribute `[[sampler(0..15)]]` + - WGSL: the binding in `@group(0) @binding(0..127)` + + - reflection information for each storage buffer binding used by the shader: + - the shader stage the storage buffer appears in (SG_SHADERSTAGE_*) + - whether the storage buffer is readonly + - backend specific bindslots: + - HLSL: + - for readonly storage buffer bindings: `register(t0..23)` + - for read/write storage buffer bindings: `register(u0..11)` + - MSL: the buffer attribute `[[buffer(8..15)]]` + - WGSL: the binding in `@group(1) @binding(0..127)` + - GL: the binding in `layout(binding=0..7)` + + - reflection information for each storage image binding used by the shader: + - 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..u11)` + - MSL: the texture attribute `[[texture(0..19)]]` + - WGSL: the binding in `@group(2) @binding(0..3)` + - GLSL: the binding in `layout(binding=0..3, [access_format])` + + - reflection information for each combined image-sampler object + used by the shader: + - the shader stage (SG_SHADERSTAGE_*) + - the texture's array index in the sg_shader_desc.images[] 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 shader_desc images, samplers and storage_buffers + arrays correspond to the same array items in the sg_bindings struct: + + - sg_shader_desc.images[N] => sg_bindings.images[N] + - sg_shader_desc.samplers[N] => sg_bindings.samplers[N] + - sg_shader_desc.storage_buffers[N] => sg_bindings.storage_buffers[N] + + 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, image, sampler, storage_buffer and + storage_image arrays may have gaps. This allows to use the same sg_bindings + struct for different related shader variants. + + 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. +*/ +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" +} 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_image { + 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_image; + +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_storage_buffer { + 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; + +typedef struct sg_shader_storage_image { + sg_shader_stage stage; // must be NONE or COMPUTE + 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_group2_binding_n; // WGSL @group(2) @binding(n) bind slot + uint8_t glsl_binding_n; // GLSL layout(binding=n) +} sg_shader_storage_image; + +typedef struct sg_shader_image_sampler_pair { + sg_shader_stage stage; + uint8_t image_slot; + uint8_t sampler_slot; + const char* glsl_name; // glsl name binding required because of GL 4.1 and WebGL2 +} sg_shader_image_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_storage_buffer storage_buffers[SG_MAX_STORAGEBUFFER_BINDSLOTS]; + sg_shader_image images[SG_MAX_IMAGE_BINDSLOTS]; + sg_shader_sampler samplers[SG_MAX_SAMPLER_BINDSLOTS]; + sg_shader_image_sampler_pair image_sampler_pairs[SG_MAX_IMAGE_SAMPLER_PAIRS]; + sg_shader_storage_image storage_images[SG_MAX_STORAGE_ATTACHMENTS]; + 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_attachments_desc + + Creation parameters for an sg_attachments object, used as argument to the + sg_make_attachments() function. + + An attachments object bundles either bundles 'render attachments' for + a render pass, or 'storage attachments' for a compute pass which writes + to storage images. + + Render attachments are: + + - 0..4 color attachment images + - 0..4 msaa-resolve attachment images + - 0 or one depth-stencil attachment image + + Note that all types of render attachment images must be created with + `sg_image_desc.usage.render_attachment = true`. At least one color-attachment + or depth-stencil-attachment image must be provided in a render pass + (only providing a depth-stencil-attachment is useful for depth-only passes). + + Alternatively provide 1..4 storage attachment images which must be created + with `sg_image_desc.usage.storage_attachment = true`. + + An sg_attachments object cannot have both render- and storage-attachments. + + Each attachment definition consists of an image object, and two additional indices + describing which subimage the pass will render into: one mipmap index, and if the image + is a cubemap, array-texture or 3D-texture, the face-index, array-layer or + depth-slice. + + All attachments must have the same width and height. + + All color attachments and the depth-stencil attachment must have the + same sample count. + + If a resolve attachment is set, an MSAA-resolve operation from the + associated color attachment image into the resolve attachment image will take + place in the sg_end_pass() function. In this case, the color attachment + must have a (sample_count>1), and the resolve attachment a + (sample_count==1). The resolve attachment also must have the same pixel + format as the color attachment. + + NOTE that MSAA depth-stencil attachments cannot be msaa-resolved! +*/ +typedef struct sg_attachment_desc { + sg_image image; + int mip_level; + int slice; // cube texture: face; array texture: layer; 3D texture: slice +} sg_attachment_desc; + +typedef struct sg_attachments_desc { + uint32_t _start_canary; + sg_attachment_desc colors[SG_MAX_COLOR_ATTACHMENTS]; + sg_attachment_desc resolves[SG_MAX_COLOR_ATTACHMENTS]; + sg_attachment_desc depth_stencil; + sg_attachment_desc storages[SG_MAX_STORAGE_ATTACHMENTS]; + const char* label; + uint32_t _end_canary; +} sg_attachments_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_attachments)(const sg_attachments_desc* desc, sg_attachments 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_attachments)(sg_attachments atts, 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 (*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_attachments)(sg_attachments 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_attachments)(sg_attachments atts_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_attachments)(sg_attachments atts_id, const sg_attachments_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_attachments)(sg_attachments atts_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_attachments)(sg_attachments atts_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_attachments_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_attachments_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_attachments_info { + sg_slot_info slot; // resource pool slot info +} sg_attachments_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_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_texture; + uint32_t num_set_vertex_sampler_state; + uint32_t num_set_fragment_buffer; + uint32_t num_set_fragment_texture; + uint32_t num_set_fragment_sampler_state; + uint32_t num_set_compute_buffer; + uint32_t num_set_compute_texture; + uint32_t num_set_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_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_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_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 (must be 0..7) (gl)") \ + _SG_LOGITEM_XMACRO(GL_STORAGEIMAGE_GLSL_BINDING_OUT_OF_RANGE, "GLSL storage image bindslot is out of range (must be 0..3) (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_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, "uniform block 'hlsl_register_b_n' is out of range (must be 0..7)") \ + _SG_LOGITEM_XMACRO(D3D11_STORAGEBUFFER_HLSL_REGISTER_T_OUT_OF_RANGE, "storage buffer 'hlsl_register_t_n' is out of range (must be 0..23)") \ + _SG_LOGITEM_XMACRO(D3D11_STORAGEBUFFER_HLSL_REGISTER_U_OUT_OF_RANGE, "storage buffer 'hlsl_register_u_n' is out of range (must be 0..11)") \ + _SG_LOGITEM_XMACRO(D3D11_IMAGE_HLSL_REGISTER_T_OUT_OF_RANGE, "image 'hlsl_register_t_n' is out of range (must be 0..23)") \ + _SG_LOGITEM_XMACRO(D3D11_SAMPLER_HLSL_REGISTER_S_OUT_OF_RANGE, "sampler 'hlsl_register_s_n' is out of rang (must be 0..15)") \ + _SG_LOGITEM_XMACRO(D3D11_STORAGEIMAGE_HLSL_REGISTER_U_OUT_OF_RANGE, "storage image 'hlsl_register_u_n' is out of range (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..15)") \ + _SG_LOGITEM_XMACRO(METAL_STORAGEIMAGE_MSL_TEXTURE_SLOT_OUT_OF_RANGE, "storage image 'msl_texture_n' is out of range (must be 0..19)") \ + _SG_LOGITEM_XMACRO(METAL_IMAGE_MSL_TEXTURE_SLOT_OUT_OF_RANGE, "image 'msl_texture_n' is out of range (must be 0..19)") \ + _SG_LOGITEM_XMACRO(METAL_SAMPLER_MSL_SAMPLER_SLOT_OUT_OF_RANGE, "sampler 'msl_sampler_n' is out of range (must be 0..15)") \ + _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_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_IMAGE_WGSL_GROUP1_BINDING_OUT_OF_RANGE, "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_STORAGEIMAGE_WGSL_GROUP2_BINDING_OUT_OF_RANGE, "storage image 'wgsl_group2_binding_n' is out of range (must be 0..3)") \ + _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(WGPU_ATTACHMENTS_CREATE_TEXTURE_VIEW_FAILED, "wgpuTextureCreateView() failed in create attachments") \ + _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_ATTACHMENTS_INVALID_STATE, "sg_dealloc_attachments(): attachments 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_ATTACHMENTS_INVALID_STATE, "sg_init_attachments(): pass must be in ALLOC state") \ + _SG_LOGITEM_XMACRO(UNINIT_BUFFER_INVALID_STATE, "sg_uninit_buffer(): buffer must be in VALID or FAILED state") \ + _SG_LOGITEM_XMACRO(UNINIT_IMAGE_INVALID_STATE, "sg_uninit_image(): image must be in VALID or FAILED state") \ + _SG_LOGITEM_XMACRO(UNINIT_SAMPLER_INVALID_STATE, "sg_uninit_sampler(): sampler must be in VALID or FAILED state") \ + _SG_LOGITEM_XMACRO(UNINIT_SHADER_INVALID_STATE, "sg_uninit_shader(): shader must be in VALID or FAILED state") \ + _SG_LOGITEM_XMACRO(UNINIT_PIPELINE_INVALID_STATE, "sg_uninit_pipeline(): pipeline must be in VALID or FAILED state") \ + _SG_LOGITEM_XMACRO(UNINIT_ATTACHMENTS_INVALID_STATE, "sg_uninit_attachments(): attachments must be in VALID or FAILED 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_ATTACHMENTS_INVALID_STATE, "sg_fail_attachments(): attachments 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(PASS_POOL_EXHAUSTED, "pass pool exhausted") \ + _SG_LOGITEM_XMACRO(BEGINPASS_ATTACHMENT_INVALID, "sg_begin_pass: an attachment was provided that no longer exists") \ + _SG_LOGITEM_XMACRO(APPLY_BINDINGS_STORAGE_BUFFER_TRACKER_EXHAUSTED, "sg_apply_bindings: too many read/write storage buffers in pass (bump sg_desc.max_dispatch_calls_per_pass") \ + _SG_LOGITEM_XMACRO(DRAW_WITHOUT_BINDINGS, "attempting to draw without resource bindings") \ + _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_RENDER_VS_STORAGE_ATTACHMENT, "sg_image_desc.usage: only one of .render_attachment or .storage_attachment can be true") \ + _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, "render/storage attachment 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_RENDERATTACHMENT_NO_MSAA_SUPPORT, "multisampling not supported for this pixel format") \ + _SG_LOGITEM_XMACRO(VALIDATE_IMAGEDESC_RENDERATTACHMENT_MSAA_NUM_MIPMAPS, "multisample images must have num_mipmaps == 1") \ + _SG_LOGITEM_XMACRO(VALIDATE_IMAGEDESC_RENDERATTACHMENT_MSAA_3D_IMAGE, "3D images cannot have a sample_count > 1") \ + _SG_LOGITEM_XMACRO(VALIDATE_IMAGEDESC_RENDERATTACHMENT_MSAA_CUBE_IMAGE, "cube images cannot have sample_count > 1") \ + _SG_LOGITEM_XMACRO(VALIDATE_IMAGEDESC_RENDERATTACHMENT_MSAA_ARRAY_IMAGE, "array images cannot have sample_count > 1") \ + _SG_LOGITEM_XMACRO(VALIDATE_IMAGEDESC_RENDERATTACHMENT_PIXELFORMAT, "invalid pixel format for render attachment image") \ + _SG_LOGITEM_XMACRO(VALIDATE_IMAGEDESC_STORAGEATTACHMENT_PIXELFORMAT, "invalid pixel format for storage attachment image") \ + _SG_LOGITEM_XMACRO(VALIDATE_IMAGEDESC_STORAGEATTACHMENT_EXPECT_NO_MSAA, "storage attachment 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, "sg_shader_desc.mtl_threads_per_threadgroup must be initialized for compute shaders (metal)") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_UNIFORMBLOCK_NO_CONT_MEMBERS, "uniform block members must occupy continuous slots") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_UNIFORMBLOCK_SIZE_IS_ZERO, "bound uniform block size cannot be zero") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_UNIFORMBLOCK_METAL_BUFFER_SLOT_OUT_OF_RANGE, "uniform block 'msl_buffer_n' is out of range (must be 0..7)") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_UNIFORMBLOCK_METAL_BUFFER_SLOT_COLLISION, "uniform block '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_OUT_OF_RANGE, "uniform block 'hlsl_register_b_n' is out of range (must be 0..7)") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_UNIFORMBLOCK_HLSL_REGISTER_B_COLLISION, "uniform block 'hlsl_register_b_n' must be unique across uniform blocks in same shader stage") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_UNIFORMBLOCK_WGSL_GROUP0_BINDING_OUT_OF_RANGE, "uniform block 'wgsl_group0_binding_n' is out of range (must be 0..15)") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_UNIFORMBLOCK_WGSL_GROUP0_BINDING_COLLISION, "uniform block 'wgsl_group0_binding_n' must be unique across all uniform blocks") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_UNIFORMBLOCK_NO_MEMBERS, "GL backend requires uniform block member declarations") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_UNIFORMBLOCK_UNIFORM_GLSL_NAME, "uniform block member 'glsl_name' missing") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_UNIFORMBLOCK_SIZE_MISMATCH, "size of uniform block members doesn't match uniform block size") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_UNIFORMBLOCK_ARRAY_COUNT, "uniform array count must be >= 1") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_UNIFORMBLOCK_STD140_ARRAY_TYPE, "uniform arrays only allowed for FLOAT4, INT4, MAT4 in std140 layout") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_STORAGEBUFFER_METAL_BUFFER_SLOT_OUT_OF_RANGE, "storage buffer 'msl_buffer_n' is out of range (must be 8..15)") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_STORAGEBUFFER_METAL_BUFFER_SLOT_COLLISION, "storage buffer 'msl_buffer_n' must be unique across uniform blocks and storage buffer in same shader stage") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_STORAGEBUFFER_HLSL_REGISTER_T_OUT_OF_RANGE, "storage buffer 'hlsl_register_t_n' is out of range (must be 0..23)") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_STORAGEBUFFER_HLSL_REGISTER_T_COLLISION, "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_STORAGEBUFFER_HLSL_REGISTER_U_OUT_OF_RANGE, "storage buffer 'hlsl_register_u_n' is out of range (must be 0..11)") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_STORAGEBUFFER_HLSL_REGISTER_U_COLLISION, "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_STORAGEBUFFER_GLSL_BINDING_OUT_OF_RANGE, "storage buffer 'glsl_binding_n' is out of range (must be 0..7)") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_STORAGEBUFFER_GLSL_BINDING_COLLISION, "storage buffer 'glsl_binding_n' must be unique across shader stages") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_STORAGEBUFFER_WGSL_GROUP1_BINDING_OUT_OF_RANGE, "storage buffer 'wgsl_group1_binding_n' is out of range (must be 0..127)") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_STORAGEBUFFER_WGSL_GROUP1_BINDING_COLLISION, "storage buffer 'wgsl_group1_binding_n' must be unique across all images, samplers and storage buffers") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_STORAGEIMAGE_EXPECT_COMPUTE_STAGE, "storage images are only allowed on the compute stage") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_STORAGEIMAGE_METAL_TEXTURE_SLOT_OUT_OF_RANGE, "storage image 'msl_texture_n' is out of range (must be 0..19") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_STORAGEIMAGE_METAL_TEXTURE_SLOT_COLLISION, "storage image 'msl_texture_n' must be unique across images and storage images in same shader stage") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_STORAGEIMAGE_HLSL_REGISTER_U_OUT_OF_RANGE, "storage image 'hlsl_register_u_n' is out of range (must be 0..11)") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_STORAGEIMAGE_HLSL_REGISTER_U_COLLISION, "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_STORAGEIMAGE_GLSL_BINDING_OUT_OF_RANGE, "storage image 'glsl_binding_n' is out of range (must be 0..4)") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_STORAGEIMAGE_GLSL_BINDING_COLLISION, "storage image 'glsl_binding_n' must be unique across shader stages") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_STORAGEIMAGE_WGSL_GROUP2_BINDING_OUT_OF_RANGE, "storage image 'wgsl_group2_binding_n' is out of range (must be 0..7)") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_STORAGEIMAGE_WGSL_GROUP2_BINDING_COLLISION, "storage image 'wgsl_group2_binding_n' must be unique in same shader stage") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_IMAGE_METAL_TEXTURE_SLOT_OUT_OF_RANGE, "image 'msl_texture_n' is out of range (must be 0..19)") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_IMAGE_METAL_TEXTURE_SLOT_COLLISION, "image 'msl_texture_n' must be unique across images and storage images in same shader stage") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_IMAGE_HLSL_REGISTER_T_OUT_OF_RANGE, "image 'hlsl_register_t_n' is out of range (must be 0..23)") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_IMAGE_HLSL_REGISTER_T_COLLISION, "image 'hlsl_register_t_n' must be unique across images and storage buffers in same shader stage") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_IMAGE_WGSL_GROUP1_BINDING_OUT_OF_RANGE, "image 'wgsl_group1_binding_n' is out of range (must be 0..127)") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_IMAGE_WGSL_GROUP1_BINDING_COLLISION, "image 'wgsl_group1_binding_n' must be unique across all images, samplers and storage buffers") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_SAMPLER_METAL_SAMPLER_SLOT_OUT_OF_RANGE, "sampler 'msl_sampler_n' is out of range (must be 0..15)") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_SAMPLER_METAL_SAMPLER_SLOT_COLLISION, "sampler 'msl_sampler_n' must be unique in same shader stage") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_SAMPLER_HLSL_REGISTER_S_OUT_OF_RANGE, "sampler 'hlsl_register_s_n' is out of rang (must be 0..15)") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_SAMPLER_HLSL_REGISTER_S_COLLISION, "sampler 'hlsl_register_s_n' must be unique in same shader stage") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_SAMPLER_WGSL_GROUP1_BINDING_OUT_OF_RANGE, "sampler 'wgsl_group1_binding_n' is out of range (must be 0..127)") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_SAMPLER_WGSL_GROUP1_BINDING_COLLISION, "sampler 'wgsl_group1_binding_n' must be unique across all images, samplers and storage buffers") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_IMAGE_SAMPLER_PAIR_IMAGE_SLOT_OUT_OF_RANGE, "image-sampler-pair image slot index is out of range (sg_shader_desc.image_sampler_pairs[].image_slot)") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_IMAGE_SAMPLER_PAIR_SAMPLER_SLOT_OUT_OF_RANGE, "image-sampler-pair sampler slot index is out of range (sg_shader_desc.image_sampler_pairs[].sampler_slot)") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_IMAGE_SAMPLER_PAIR_IMAGE_STAGE_MISMATCH, "image-sampler-pair stage doesn't match referenced image stage") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_IMAGE_SAMPLER_PAIR_SAMPLER_STAGE_MISMATCH, "image-sampler-pair stage doesn't match referenced sampler stage") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_IMAGE_SAMPLER_PAIR_GLSL_NAME, "image-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_IMAGE_NOT_REFERENCED_BY_IMAGE_SAMPLER_PAIRS, "one or more images are not referenced by by image-sampler-pairs (sg_shader_desc.image_sampler_pairs[].image_slot)") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_SAMPLER_NOT_REFERENCED_BY_IMAGE_SAMPLER_PAIRS, "one or more samplers are not referenced by image-sampler-pairs (sg_shader_desc.image_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_ATTACHMENTSDESC_CANARY, "sg_attachments_desc not initialized") \ + _SG_LOGITEM_XMACRO(VALIDATE_ATTACHMENTSDESC_NO_ATTACHMENTS, "sg_attachments_desc no color, depth-stencil or storage attachments") \ + _SG_LOGITEM_XMACRO(VALIDATE_ATTACHMENTSDESC_NO_CONT_COLOR_ATTS, "color attachments must occupy continuous slots") \ + _SG_LOGITEM_XMACRO(VALIDATE_ATTACHMENTSDESC_COLOR_IMAGE, "color attachment image is not valid") \ + _SG_LOGITEM_XMACRO(VALIDATE_ATTACHMENTSDESC_COLOR_MIPLEVEL, "color attachment mip level is higher than number of mipmaps in image") \ + _SG_LOGITEM_XMACRO(VALIDATE_ATTACHMENTSDESC_COLOR_FACE, "color attachment image is cubemap, but face index is too big") \ + _SG_LOGITEM_XMACRO(VALIDATE_ATTACHMENTSDESC_COLOR_LAYER, "color attachment image is array texture, but layer index is too big") \ + _SG_LOGITEM_XMACRO(VALIDATE_ATTACHMENTSDESC_COLOR_SLICE, "color attachment image is 3d texture, but slice value is too big") \ + _SG_LOGITEM_XMACRO(VALIDATE_ATTACHMENTSDESC_COLOR_IMAGE_NO_RENDERATTACHMENT, "color attachment images must be sg_image_desc.usage.render_attachment=true") \ + _SG_LOGITEM_XMACRO(VALIDATE_ATTACHMENTSDESC_COLOR_INV_PIXELFORMAT, "color attachment images must be renderable color pixel format") \ + _SG_LOGITEM_XMACRO(VALIDATE_ATTACHMENTSDESC_IMAGE_SIZES, "all color and depth attachment images must have the same size") \ + _SG_LOGITEM_XMACRO(VALIDATE_ATTACHMENTSDESC_IMAGE_SAMPLE_COUNTS, "all color and depth attachment images must have the same sample count") \ + _SG_LOGITEM_XMACRO(VALIDATE_ATTACHMENTSDESC_RESOLVE_COLOR_IMAGE_MSAA, "resolve attachments must have a color attachment image with sample count > 1") \ + _SG_LOGITEM_XMACRO(VALIDATE_ATTACHMENTSDESC_RESOLVE_IMAGE, "resolve attachment image not valid") \ + _SG_LOGITEM_XMACRO(VALIDATE_ATTACHMENTSDESC_RESOLVE_SAMPLE_COUNT, "pass resolve attachment image sample count must be 1") \ + _SG_LOGITEM_XMACRO(VALIDATE_ATTACHMENTSDESC_RESOLVE_MIPLEVEL, "resolve attachment mip level is higher than number of mipmaps in image") \ + _SG_LOGITEM_XMACRO(VALIDATE_ATTACHMENTSDESC_RESOLVE_FACE, "resolve attachment is cubemap, but face index is too big") \ + _SG_LOGITEM_XMACRO(VALIDATE_ATTACHMENTSDESC_RESOLVE_LAYER, "resolve attachment is array texture, but layer index is too big") \ + _SG_LOGITEM_XMACRO(VALIDATE_ATTACHMENTSDESC_RESOLVE_SLICE, "resolve attachment is 3d texture, but slice value is too big") \ + _SG_LOGITEM_XMACRO(VALIDATE_ATTACHMENTSDESC_RESOLVE_IMAGE_NO_RT, "resolve attachment image must have render_target=true") \ + _SG_LOGITEM_XMACRO(VALIDATE_ATTACHMENTSDESC_RESOLVE_IMAGE_SIZES, "resolve attachment size must match color attachment image size") \ + _SG_LOGITEM_XMACRO(VALIDATE_ATTACHMENTSDESC_RESOLVE_IMAGE_FORMAT, "resolve attachment pixel format must match color attachment pixel format") \ + _SG_LOGITEM_XMACRO(VALIDATE_ATTACHMENTSDESC_DEPTH_INV_PIXELFORMAT, "depth attachment image must be depth or depth-stencil pixel format") \ + _SG_LOGITEM_XMACRO(VALIDATE_ATTACHMENTSDESC_DEPTH_IMAGE, "depth attachment image is not valid") \ + _SG_LOGITEM_XMACRO(VALIDATE_ATTACHMENTSDESC_DEPTH_MIPLEVEL, "depth attachment mip level is higher than number of mipmaps in image") \ + _SG_LOGITEM_XMACRO(VALIDATE_ATTACHMENTSDESC_DEPTH_FACE, "depth attachment image is cubemap, but face index is too big") \ + _SG_LOGITEM_XMACRO(VALIDATE_ATTACHMENTSDESC_DEPTH_LAYER, "depth attachment image is array texture, but layer index is too big") \ + _SG_LOGITEM_XMACRO(VALIDATE_ATTACHMENTSDESC_DEPTH_SLICE, "depth attachment image is 3d texture, but slice value is too big") \ + _SG_LOGITEM_XMACRO(VALIDATE_ATTACHMENTSDESC_DEPTH_IMAGE_NO_RENDERATTACHMENT, "depth attachment image must be sg_image_desc.usage.render_attachment=true") \ + _SG_LOGITEM_XMACRO(VALIDATE_ATTACHMENTSDESC_DEPTH_IMAGE_SIZES, "depth attachment image size must match color attachment image size") \ + _SG_LOGITEM_XMACRO(VALIDATE_ATTACHMENTSDESC_DEPTH_IMAGE_SAMPLE_COUNT, "depth attachment sample count must match color attachment sample count") \ + _SG_LOGITEM_XMACRO(VALIDATE_ATTACHMENTSDESC_STORAGE_IMAGE, "storage attachment image is not valid") \ + _SG_LOGITEM_XMACRO(VALIDATE_ATTACHMENTSDESC_STORAGE_MIPLEVEL, "storage attachment mip level is higher than number of mipmaps in image") \ + _SG_LOGITEM_XMACRO(VALIDATE_ATTACHMENTSDESC_STORAGE_FACE, "storage attachment image is cubemap, but face index is too big") \ + _SG_LOGITEM_XMACRO(VALIDATE_ATTACHMENTSDESC_STORAGE_LAYER, "storage attachment image is array texture, but layer index is too big") \ + _SG_LOGITEM_XMACRO(VALIDATE_ATTACHMENTSDESC_STORAGE_SLICE, "storage attachment image is 3d texture, but slice value is too big") \ + _SG_LOGITEM_XMACRO(VALIDATE_ATTACHMENTSDESC_STORAGE_IMAGE_NO_STORAGEATTACHMENT, "storage attachment images must be sg_image_desc.usage.storage_attachment=true") \ + _SG_LOGITEM_XMACRO(VALIDATE_ATTACHMENTSDESC_STORAGE_INV_PIXELFORMAT, "storage attachment pixel format must have .compute_readwrite or .compute_writeonly capabilities") \ + _SG_LOGITEM_XMACRO(VALIDATE_ATTACHMENTSDESC_RENDER_VS_STORAGE_ATTACHMENTS, "cannot use color/depth and storage attachment images on the same sg_attachments object") \ + _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_CANARY, "sg_begin_pass: pass struct not initialized") \ + _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_ATTACHMENTS_EXISTS, "sg_begin_pass: attachments object no longer alive") \ + _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_ATTACHMENTS_VALID, "sg_begin_pass: attachments object not in resource state VALID") \ + _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_COMPUTEPASS_STORAGE_ATTACHMENTS_ONLY, "sg_begin_pass: only storage attachments allowed on compute pass") \ + _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_RENDERPASS_RENDER_ATTACHMENTS_ONLY, "sg_begin_pass: a render pass cannot have storage attachments") \ + _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_COLOR_ATTACHMENT_IMAGE_ALIVE, "sg_begin_pass: one or more color attachment images are no longer alive") \ + _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_COLOR_ATTACHMENT_IMAGE_VALID, "sg_begin_pass: one or more color attachment images are not in valid state") \ + _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_RESOLVE_ATTACHMENT_IMAGE_ALIVE, "sg_begin_pass: one or more resolve attachment images are no longer alive") \ + _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_RESOLVE_ATTACHMENT_IMAGE_VALID, "sg_begin_pass: one or more resolve attachment images are not in valid state") \ + _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_DEPTHSTENCIL_ATTACHMENT_IMAGE_ALIVE, "sg_begin_pass: one or more depth-stencil attachment images are no longer alive") \ + _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_DEPTHSTENCIL_ATTACHMENT_IMAGE_VALID, "sg_begin_pass: one or more depth-stencil attachment images are not in valid state") \ + _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_STORAGE_ATTACHMENT_IMAGE_ALIVE, "sg_begin_pass: one or more storage attachment images is no longer alive") \ + _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_STORAGE_ATTACHMENT_IMAGE_VALID, "sg_begin_pass: one or more storage attachment images is not in valid state") \ + _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_SWAPCHAIN_EXPECT_WIDTH, "sg_begin_pass: expected pass.swapchain.width > 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_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_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_CURPASS_ATTACHMENTS_ALIVE, "sg_apply_pipeline: current pass attachments no longer alive") \ + _SG_LOGITEM_XMACRO(VALIDATE_APIP_CURPASS_ATTACHMENTS_VALID, "sg_apply_pipeline: current pass attachments not in valid state") \ + _SG_LOGITEM_XMACRO(VALIDATE_APIP_ATT_COUNT, "sg_apply_pipeline: number of pipeline color attachments doesn't match number of pass color attachments") \ + _SG_LOGITEM_XMACRO(VALIDATE_APIP_COLOR_ATTACHMENT_IMAGE_ALIVE, "sg_apply_pipeline: one or more pass color attachments images are no longer alive") \ + _SG_LOGITEM_XMACRO(VALIDATE_APIP_COLOR_ATTACHMENT_IMAGE_VALID, "sg_apply_pipeline: one or more pass color attachments images are not in valid state") \ + _SG_LOGITEM_XMACRO(VALIDATE_APIP_DEPTHSTENCIL_ATTACHMENT_IMAGE_ALIVE, "sg_apply_pipeline: pass depth-stencil attachment image no longer alive") \ + _SG_LOGITEM_XMACRO(VALIDATE_APIP_DEPTHSTENCIL_ATTACHMENT_IMAGE_VALID, "sg_apply_pipeline: pass depth-stencil attachment image not in valid state") \ + _SG_LOGITEM_XMACRO(VALIDATE_APIP_COLOR_FORMAT, "sg_apply_pipeline: pipeline color attachment pixel format doesn't match pass color attachment pixel format") \ + _SG_LOGITEM_XMACRO(VALIDATE_APIP_DEPTH_FORMAT, "sg_apply_pipeline: pipeline depth pixel_format doesn't match pass depth attachment pixel format") \ + _SG_LOGITEM_XMACRO(VALIDATE_APIP_SAMPLE_COUNT, "sg_apply_pipeline: pipeline MSAA sample count doesn't match render pass attachment sample count") \ + _SG_LOGITEM_XMACRO(VALIDATE_APIP_EXPECTED_STORAGE_ATTACHMENT_IMAGE, "sg_apply_pipeline: shader expects storage image binding but compute pass doesn't have storage attachment image at expected bind slot") \ + _SG_LOGITEM_XMACRO(VALIDATE_APIP_STORAGE_ATTACHMENT_IMAGE_ALIVE, "sg_apply_pipeline: compute pass storage image attachment no longer exists") \ + _SG_LOGITEM_XMACRO(VALIDATE_APIP_STORAGE_ATTACHMENT_IMAGE_VALID, "sg_apply_pipeline: compute pass storage image attachment is not in valid state") \ + _SG_LOGITEM_XMACRO(VALIDATE_APIP_STORAGE_ATTACHMENT_PIXELFORMAT, "sg_apply_pipeline: compute pass storage image attachment pixel format doesn't match sg_shader_desc.storage_images[].access_format") \ + _SG_LOGITEM_XMACRO(VALIDATE_APIP_STORAGE_ATTACHMENT_IMAGE_TYPE, "sg_apply_pipeline: compute pass storage image attachment image type doesn't match sg_shader_desc.storage_images[].image_type") \ + _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_VBS, "sg_apply_bindings: vertex buffer bindings not allowed in a compute pass") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_COMPUTE_EXPECTED_NO_IB, "sg_apply_bindings: index buffer binding not allowed in compute pass") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_EXPECTED_VB, "sg_apply_bindings: vertex buffer binding is missing or buffer handle is invalid") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_VB_ALIVE, "sg_apply_bindings: vertex buffer no longer alive") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_VB_TYPE, "sg_apply_bindings: buffer in vertex buffer slot doesn't have vertex buffer usage (sg_buffer_desc.usage.storage_buffer)") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_VB_OVERFLOW, "sg_apply_bindings: buffer in vertex buffer slot is overflown") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_NO_IB, "sg_apply_bindings: pipeline object defines indexed rendering, but no index buffer provided") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_IB, "sg_apply_bindings: pipeline object defines non-indexed rendering, but index buffer provided") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_IB_ALIVE, "sg_apply_bindings: index buffer no longer alive") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_IB_TYPE, "sg_apply_bindings: buffer in index buffer slot doesn't have index buffer usage (sg_buffer_desc.usage.index_buffer)") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_IB_OVERFLOW, "sg_apply_bindings: buffer in index buffer slot is overflown") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_EXPECTED_IMAGE_BINDING, "sg_apply_bindings: image binding is missing or the image handle is invalid") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_IMG_ALIVE, "sg_apply_bindings: bound image no longer alive") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_IMAGE_TYPE_MISMATCH, "sg_apply_bindings: type of bound image doesn't match shader desc") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_EXPECTED_MULTISAMPLED_IMAGE, "sg_apply_bindings: expected image with sample_count > 1") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_IMAGE_MSAA, "sg_apply_bindings: cannot bind image with sample_count>1") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_EXPECTED_FILTERABLE_IMAGE, "sg_apply_bindings: filterable image expected") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_EXPECTED_DEPTH_IMAGE, "sg_apply_bindings: depth image expected") \ + _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_SMP_ALIVE, "sg_apply_bindings: bound sampler no longer alive") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_SMP_VALID, "sg_apply_bindings: bound sampler not in valid state") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_EXPECTED_STORAGEBUFFER_BINDING, "sg_apply_bindings: storage buffer binding is missing or the buffer handle is invalid") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_STORAGEBUFFER_ALIVE, "sg_apply_bindings: bound storage buffer no longer alive") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_STORAGEBUFFER_BINDING_BUFFERTYPE, "sg_apply_bindings: buffer bound to storage buffer slot doesn't have storage buffer usage (sg_buffer_desc.usage.storage_buffer)") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_STORAGEBUFFER_READWRITE_IMMUTABLE, "sg_apply_bindings: storage buffers bound as read/write must have usage immutable") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_IMAGE_BINDING_VS_DEPTHSTENCIL_ATTACHMENT, "sg_apply_bindings: cannot bind image in the same pass it is used as depth-stencil attachment") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_IMAGE_BINDING_VS_COLOR_ATTACHMENT, "sg_apply_bindings: cannot bind image in the same pass it is used as color attachment") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_IMAGE_BINDING_VS_RESOLVE_ATTACHMENT, "sg_apply_bindings: cannot bind image in the same pass it is used as resolve attachment") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_IMAGE_BINDING_VS_STORAGE_ATTACHMENT, "sg_apply_bindings: cannot bind image in the same pass it is used as storage attachment") \ + _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, "sg_draw: base_element cannot be < 0") \ + _SG_LOGITEM_XMACRO(VALIDATE_DRAW_NUMELEMENTS, "sg_draw: num_elements cannot be < 0") \ + _SG_LOGITEM_XMACRO(VALIDATE_DRAW_NUMINSTANCES, "sg_draw: num_instances cannot be < 0") \ + _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 + .attachments_pool_size 16 + .uniform_buffer_size 4 MB (4*1024*1024) + .max_dispatch_calls_per_pass 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 attachments_pool_size; + int uniform_buffer_size; + int max_dispatch_calls_per_pass; // max expected number of dispatch calls per pass (default: 1024) + int max_commit_listeners; + bool disable_validation; // disable validation layer even in debug mode, useful for tests + 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_attachments sg_make_attachments(const sg_attachments_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_attachments(sg_attachments atts); +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_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_attachments_state(sg_attachments atts); +// 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_attachments_info sg_query_attachments_info(sg_attachments atts); +// 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_attachments_desc sg_query_attachments_desc(sg_attachments atts); +// 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_attachments_desc sg_query_attachments_defaults(const sg_attachments_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); + +// 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_attachments sg_alloc_attachments(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_attachments(sg_attachments attachments); +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_attachments(sg_attachments attachments, const sg_attachments_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_attachments(sg_attachments atts); +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_attachments(sg_attachments atts); + +// 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) + const void* srv; // ID3D11ShaderResourceView* +} 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_attachments_info { + const void* color_rtv[SG_MAX_COLOR_ATTACHMENTS]; // ID3D11RenderTargetView + const void* dsv; // ID3D11DepthStencilView +} sg_d3d11_attachments_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 + const void* view; // WGPUTextureView +} 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_attachments_info { + const void* color_view[SG_MAX_COLOR_ATTACHMENTS]; // WGPUTextureView + const void* resolve_view[SG_MAX_COLOR_ATTACHMENTS]; // WGPUTextureView + const void* ds_view; // WGPUTextureView +} sg_wgpu_attachments_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; + uint32_t msaa_render_buffer; + 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_attachments_info { + uint32_t framebuffer; + uint32_t msaa_resolve_framebuffer[SG_MAX_COLOR_ATTACHMENTS]; +} sg_gl_attachments_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 pass resource objects +SOKOL_GFX_API_DECL sg_d3d11_attachments_info sg_d3d11_query_attachments_info(sg_attachments atts); + +// 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 pass resource objects +SOKOL_GFX_API_DECL sg_wgpu_attachments_info sg_wgpu_query_attachments_info(sg_attachments atts); + +// 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 pass resource objects +SOKOL_GFX_API_DECL sg_gl_attachments_info sg_gl_query_attachments_info(sg_attachments atts); + +#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_attachments sg_make_attachments(const sg_attachments_desc& desc) { return sg_make_attachments(&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_attachments_desc sg_query_attachments_defaults(const sg_attachments_desc& desc) { return sg_query_attachments_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_attachments(sg_attachments atts, const sg_attachments_desc& desc) { return sg_init_attachments(atts, &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) + #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 + #ifdef _MSC_VER + #pragma comment (lib, "kernel32") + #pragma comment (lib, "user32") + #pragma comment (lib, "dxgi") + #pragma comment (lib, "d3d11") + #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() + #define _SOKOL_GL_HAS_COMPUTE (1) + #define _SOKOL_GL_HAS_TEXSTORAGE (1) + #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 + #define _SOKOL_GL_HAS_TEXSTORAGE (1) + #endif + #elif defined(__EMSCRIPTEN__) + #if defined(SOKOL_GLES3) + #include + #define _SOKOL_GL_HAS_TEXSTORAGE (1) + #endif + #elif defined(__ANDROID__) + #include + #define _SOKOL_GL_HAS_COMPUTE (1) + #define _SOKOL_GL_HAS_TEXSTORAGE (1) + #elif defined(__linux__) || defined(__unix__) + #if defined(SOKOL_GLCORE) + #define GL_GLEXT_PROTOTYPES + #include + #else + #include + #include + #endif + #define _SOKOL_GL_HAS_COMPUTE (1) + #define _SOKOL_GL_HAS_TEXSTORAGE (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_MIN 0x8007 + #define GL_MAX 0x8008 + #define GL_WRITE_ONLY 0x88B9 + #define GL_READ_WRITE 0x88BA + #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 + #define _SG_GL_CHECK_ERROR() { SOKOL_ASSERT(glGetError() == GL_NO_ERROR); } + #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 +// 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_attachments_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_attachments_ref_s { + struct _sg_attachments_s* ptr; + _sg_sref_t sref; + +} _sg_attachments_ref_t; + +// resource tracking (for keeping track of gpu-written storage resources +typedef struct { + uint32_t size; + uint32_t cur; + uint32_t* items; +} _sg_tracker_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_ATTACHMENTS_POOL_SIZE = 16, + _SG_DEFAULT_UB_SIZE = 4 * 1024 * 1024, + _SG_DEFAULT_MAX_DISPATCH_CALLS_PER_PASS = 1024, + _SG_DEFAULT_MAX_COMMIT_LISTENERS = 1024, + _SG_DEFAULT_WGPU_BINDGROUP_CACHE_SIZE = 1024, +}; + +// 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; + bool readonly; +} _sg_shader_storage_buffer_t; + +typedef struct { + sg_shader_stage stage; + sg_image_type image_type; + sg_pixel_format access_format; + bool writeonly; +} _sg_shader_storage_image_t; + +typedef struct { + sg_shader_stage stage; + sg_image_type image_type; + sg_image_sample_type sample_type; + bool multisampled; +} _sg_shader_image_t; + +typedef struct { + sg_shader_stage stage; + sg_sampler_type sampler_type; +} _sg_shader_sampler_t; + +typedef struct { + sg_shader_stage stage; + uint8_t image_slot; + uint8_t sampler_slot; +} _sg_shader_image_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_storage_buffer_t storage_buffers[SG_MAX_STORAGEBUFFER_BINDSLOTS]; + _sg_shader_image_t images[SG_MAX_IMAGE_BINDSLOTS]; + _sg_shader_sampler_t samplers[SG_MAX_SAMPLER_BINDSLOTS]; + _sg_shader_image_sampler_t image_samplers[SG_MAX_IMAGE_SAMPLER_PAIRS]; + _sg_shader_storage_image_t storage_images[SG_MAX_STORAGE_ATTACHMENTS]; +} _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_image_ref_t image; + int mip_level; + int slice; +} _sg_attachment_common_t; + +typedef struct { + int width; + int height; + int num_colors; + bool has_render_attachments; + bool has_storage_attachments; + _sg_attachment_common_t colors[SG_MAX_COLOR_ATTACHMENTS]; + _sg_attachment_common_t resolves[SG_MAX_COLOR_ATTACHMENTS]; + _sg_attachment_common_t depth_stencil; + _sg_attachment_common_t storages[SG_MAX_STORAGE_ATTACHMENTS]; +} _sg_attachments_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_attachments_s { + _sg_slot_t slot; + _sg_attachments_common_t cmn; +} _sg_dummy_attachments_t; +typedef _sg_dummy_attachments_t _sg_attachments_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_BUFFER_ALL = _SG_GL_GPUDIRTY_VERTEXBUFFER | _SG_GL_GPUDIRTY_INDEXBUFFER | _SG_GL_GPUDIRTY_STORAGEBUFFER, +} _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]; + bool injected; // if true, external buffers were injected with sg_buffer_desc.gl_buffers + uint8_t gpu_dirty_flags; // combination of _sg_gl_gpudirty_t flags + } 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 msaa_render_buffer; + GLuint tex[SG_NUM_INFLIGHT_FRAMES]; + 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_STORAGEBUFFER_BINDSLOTS]; + uint8_t simg_binding[SG_MAX_STORAGE_ATTACHMENTS]; + int8_t tex_slot[SG_MAX_IMAGE_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_attachments_s { + _sg_slot_t slot; + _sg_attachments_common_t cmn; + struct { + GLuint fb; + GLuint msaa_resolve_framebuffer[SG_MAX_COLOR_ATTACHMENTS]; + } gl; +} _sg_gl_attachments_t; +typedef _sg_gl_attachments_t _sg_attachments_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_BINDSLOTS) +#define _SG_GL_MAX_SIMG_BINDINGS (SG_MAX_STORAGE_ATTACHMENTS) +#define _SG_GL_MAX_IMG_SMP_BINDINGS (SG_MAX_IMAGE_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]; + 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_IMG_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_state_cache_t; + +typedef struct { + bool valid; + GLuint vao; + _sg_gl_state_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; + ID3D11ShaderResourceView* srv; + ID3D11UnorderedAccessView* uav; + } 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 + ID3D11ShaderResourceView* srv; + } 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_STAGE_UB_BINDINGS (SG_MAX_UNIFORMBLOCK_BINDSLOTS) +#define _SG_D3D11_MAX_STAGE_SRV_BINDINGS (SG_MAX_IMAGE_BINDSLOTS + SG_MAX_STORAGEBUFFER_BINDSLOTS) +#define _SG_D3D11_MAX_STAGE_UAV_BINDINGS (SG_MAX_STORAGEBUFFER_BINDSLOTS + SG_MAX_STORAGE_ATTACHMENTS) +#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 img_register_t_n[SG_MAX_IMAGE_BINDSLOTS]; + uint8_t smp_register_s_n[SG_MAX_SAMPLER_BINDSLOTS]; + uint8_t sbuf_register_t_n[SG_MAX_STORAGEBUFFER_BINDSLOTS]; + uint8_t sbuf_register_u_n[SG_MAX_STORAGEBUFFER_BINDSLOTS]; + uint8_t simg_register_u_n[SG_MAX_STORAGE_ATTACHMENTS]; + 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 { + union { + ID3D11RenderTargetView* rtv; + ID3D11DepthStencilView* dsv; + ID3D11UnorderedAccessView* uav; + } view; +} _sg_d3d11_attachment_t; + +typedef struct _sg_attachments_s { + _sg_slot_t slot; + _sg_attachments_common_t cmn; + struct { + _sg_d3d11_attachment_t colors[SG_MAX_COLOR_ATTACHMENTS]; + _sg_d3d11_attachment_t depth_stencil; + _sg_d3d11_attachment_t storages[SG_MAX_STORAGE_ATTACHMENTS]; + } d3d11; +} _sg_d3d11_attachments_t; +typedef _sg_d3d11_attachments_t _sg_attachments_t; + +typedef struct { + bool valid; + ID3D11Device* dev; + ID3D11DeviceContext* ctx; + bool use_indexed_draw; + bool use_instanced_draw; + struct { + ID3D11RenderTargetView* render_view; + ID3D11RenderTargetView* resolve_view; + } cur_pass; + // on-demand loaded d3dcompiler_47.dll handles + HINSTANCE d3dcompiler_dll; + bool d3dcompiler_dll_load_failed; + pD3DCompile D3DCompile_func; + // global subresourcedata array for texture updates + D3D11_SUBRESOURCE_DATA subres_data[SG_MAX_MIPMAPS * SG_MAX_TEXTUREARRAY_LAYERS]; +} _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 img_texture_n[SG_MAX_IMAGE_BINDSLOTS]; + uint8_t smp_sampler_n[SG_MAX_SAMPLER_BINDSLOTS]; + uint8_t sbuf_buffer_n[SG_MAX_STORAGEBUFFER_BINDSLOTS]; + uint8_t simg_texture_n[SG_MAX_STORAGE_ATTACHMENTS]; + } 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_attachments_s { + _sg_slot_t slot; + _sg_attachments_common_t cmn; + struct { + int storage_views[SG_MAX_STORAGE_ATTACHMENTS]; + } mtl; +} _sg_mtl_attachments_t; +typedef _sg_mtl_attachments_t _sg_attachments_t; + +// resource binding state cache +#define _SG_MTL_MAX_STAGE_UB_BINDINGS (SG_MAX_UNIFORMBLOCK_BINDSLOTS) +#define _SG_MTL_MAX_STAGE_UB_SBUF_BINDINGS (_SG_MTL_MAX_STAGE_UB_BINDINGS + SG_MAX_STORAGEBUFFER_BINDSLOTS) +#define _SG_MTL_MAX_STAGE_BUFFER_BINDINGS (_SG_MTL_MAX_STAGE_UB_SBUF_BINDINGS + SG_MAX_VERTEXBUFFER_BINDSLOTS) +#define _SG_MTL_MAX_STAGE_TEXTURE_BINDINGS (SG_MAX_IMAGE_BINDSLOTS + SG_MAX_STORAGE_ATTACHMENTS) +#define _SG_MTL_MAX_STAGE_SAMPLER_BINDINGS (SG_MAX_SAMPLER_BINDSLOTS) +typedef struct { + _sg_sref_t cur_pip; + _sg_buffer_ref_t cur_ibuf; + int cur_ibuf_offset; + int cur_vs_buffer_offsets[_SG_MTL_MAX_STAGE_BUFFER_BINDINGS]; + _sg_sref_t cur_vsbufs[_SG_MTL_MAX_STAGE_BUFFER_BINDINGS]; + _sg_sref_t cur_fsbufs[_SG_MTL_MAX_STAGE_BUFFER_BINDINGS]; + _sg_sref_t cur_csbufs[_SG_MTL_MAX_STAGE_BUFFER_BINDINGS]; + _sg_sref_t cur_vsimgs[_SG_MTL_MAX_STAGE_TEXTURE_BINDINGS]; + _sg_sref_t cur_fsimgs[_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]; + // NOTE: special case: uint64_t for storage images, because we need + // to differentiate between storage pass attachments and regular + // textures bound to compute stages (but both binding types live + // in the texture bind space in Metal) + // This special case will be removed in the view update! + uint64_t cur_cs_image_ids[_SG_MTL_MAX_STAGE_TEXTURE_BINDINGS]; +} _sg_mtl_state_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_state_cache_t state_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 (3) // 0: uniforms, 1: images, samplers, storage buffers, 2: storage images (only in compute passes) +#define _SG_WGPU_UB_BINDGROUP_INDEX (0) +#define _SG_WGPU_IMG_SMP_SBUF_BINDGROUP_INDEX (1) +#define _SG_WGPU_SIMG_BINDGROUP_INDEX (2) +#define _SG_WGPU_MAX_UB_BINDGROUP_ENTRIES (SG_MAX_UNIFORMBLOCK_BINDSLOTS) +#define _SG_WGPU_MAX_UB_BINDGROUP_BIND_SLOTS (2 * SG_MAX_UNIFORMBLOCK_BINDSLOTS) +#define _SG_WGPU_MAX_IMG_SMP_SBUF_BINDGROUP_ENTRIES (SG_MAX_IMAGE_BINDSLOTS + SG_MAX_SAMPLER_BINDSLOTS + SG_MAX_STORAGEBUFFER_BINDSLOTS) +#define _SG_WGPU_MAX_IMG_SMP_SBUF_BIND_SLOTS (128) +#define _SG_WGPU_MAX_SIMG_BIND_SLOTS (SG_MAX_STORAGE_ATTACHMENTS) +#define _SG_WGPU_MAX_SIMG_BINDGROUP_ENTRIES (SG_MAX_STORAGE_ATTACHMENTS) + +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; + WGPUTextureView view; + } 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_img_smp_sbuf; + WGPUBindGroupLayout bgl_simg; + // 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 img_grp1_bnd_n[SG_MAX_IMAGE_BINDSLOTS]; + uint8_t smp_grp1_bnd_n[SG_MAX_SAMPLER_BINDSLOTS]; + uint8_t sbuf_grp1_bnd_n[SG_MAX_STORAGEBUFFER_BINDSLOTS]; + uint8_t simg_grp2_bnd_n[SG_MAX_STORAGE_ATTACHMENTS]; + } 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 { + WGPUTextureView view; +} _sg_wgpu_attachment_t; + +typedef struct _sg_attachments_s { + _sg_slot_t slot; + _sg_attachments_common_t cmn; + struct { + _sg_wgpu_attachment_t colors[SG_MAX_COLOR_ATTACHMENTS]; + _sg_wgpu_attachment_t resolves[SG_MAX_COLOR_ATTACHMENTS]; + _sg_wgpu_attachment_t depth_stencil; + _sg_wgpu_attachment_t storages[SG_MAX_STORAGE_ATTACHMENTS]; + } wgpu; +} _sg_wgpu_attachments_t; +typedef _sg_wgpu_attachments_t _sg_attachments_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_IMAGE = 1, + _SG_WGPU_BINDGROUPSCACHEITEMTYPE_SAMPLER = 2, + _SG_WGPU_BINDGROUPSCACHEITEMTYPE_STORAGEBUFFER = 3, + _SG_WGPU_BINDGROUPSCACHEITEMTYPE_PIPELINE = 4, +} _sg_wgpu_bindgroups_cache_item_type_t; + +#define _SG_WGPU_BINDGROUPSCACHEKEY_NUM_ITEMS (1 + _SG_WGPU_MAX_IMG_SMP_SBUF_BINDGROUP_ENTRIES) +typedef struct { + uint64_t hash; + // the format of cache key items is BBTCCCCCIIIIIIII + // where + // - BB: 8 bits WGPU binding + // - T: 3 bits _sg_wgpu_bindgroups_cache_item_type_t + // - CCCCC: 21 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; + bool use_indexed_draw; + 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 + +// 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 attachments_pool; + _sg_buffer_t* buffers; + _sg_image_t* images; + _sg_sampler_t* samplers; + _sg_shader_t* shaders; + _sg_pipeline_t* pipelines; + _sg_attachments_t* attachments; +} _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 { + _sg_image_t* color_images[SG_MAX_COLOR_ATTACHMENTS]; + _sg_image_t* resolve_images[SG_MAX_COLOR_ATTACHMENTS]; + _sg_image_t* ds_image; + _sg_image_t* storage_images[SG_MAX_STORAGE_ATTACHMENTS]; +} _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_image_t* imgs[SG_MAX_IMAGE_BINDSLOTS]; + _sg_sampler_t* smps[SG_MAX_SAMPLER_BINDSLOTS]; + _sg_buffer_t* sbufs[SG_MAX_STORAGEBUFFER_BINDSLOTS]; + _sg_image_t* simgs[SG_MAX_STORAGE_ATTACHMENTS]; +} _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_attachments_ref_t atts; // null in a swapchain pass + int width; + int height; + 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; + 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 + struct { + _sg_tracker_t readwrite_sbufs; // tracks read/write storage buffers used in compute pass + } compute; + _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_attachments_to_alloc_state(_sg_attachments_t* atts) { + SOKOL_ASSERT(atts); + _sg_slot_t slot = atts->slot; + _sg_clear(atts, sizeof(*atts)); + atts->slot = slot; + atts->slot.uninit_count += 1; + atts->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->attachments_pool_size > 0) && (desc->attachments_pool_size < _SG_MAX_POOL_SIZE)); + _sg_pool_init(&p->attachments_pool, desc->attachments_pool_size); + size_t attachments_pool_byte_size = sizeof(_sg_attachments_t) * (size_t)p->attachments_pool.size; + p->attachments = (_sg_attachments_t*) _sg_malloc_clear(attachments_pool_byte_size); +} + +_SOKOL_PRIVATE void _sg_discard_pools(_sg_pools_t* p) { + SOKOL_ASSERT(p); + _sg_free(p->attachments); p->attachments = 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->attachments_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_attachments_t* _sg_attachments_at(uint32_t atts_id) { + SOKOL_ASSERT(SG_INVALID_ID != atts_id); + int slot_index = _sg_slot_index(atts_id); + SOKOL_ASSERT((slot_index > _SG_INVALID_SLOT_INDEX) && (slot_index < _sg.pools.attachments_pool.size)); + return &_sg.pools.attachments[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_attachments_t* _sg_lookup_attachments(uint32_t atts_id) { + if (SG_INVALID_ID != atts_id) { + _sg_attachments_t* atts = _sg_attachments_at(atts_id); + if (atts->slot.id == atts_id) { + return atts; + } + } + 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_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 _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_attachments_ref_t _sg_attachments_ref(_sg_attachments_t* atts_or_null) { + _sg_attachments_ref_t ref; _sg_clear(&ref, sizeof(ref)); + if (atts_or_null) { + _sg_attachments_t* atts = atts_or_null; + SOKOL_ASSERT(atts->slot.id != SG_INVALID_ID); + ref.ptr = atts; + ref.sref = _sg_sref(&atts->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_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_attachments_ref_eql, _sg_attachments_ref_t, _sg_attachments_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_attachments_ref_null, _sg_attachments_ref_t) + +#define _SG_IMPL_RES_ALIVE(NAME,REF) _SOKOL_PRIVATE bool NAME(const REF* ref) { SOKOL_ASSERT(ref); return ref->ptr && _sg_sref_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_attachments_ref_alive, _sg_attachments_ref_t) + +#define _SG_IMPL_RES_PTR(NAME,REF,RES) _SOKOL_PRIVATE RES* NAME(const REF* ref) { SOKOL_ASSERT(ref && ref->ptr && _sg_sref_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_attachments_ref_ptr, _sg_attachments_ref_t, _sg_attachments_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) { SOKOL_ASSERT(_sg_sref_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_attachments_ref_ptr_or_null, _sg_attachments_ref_t, _sg_attachments_t) + +// ████████ ██████ █████ ██████ ██ ██ ███████ ██████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██ ██████ ███████ ██ █████ █████ ██████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██ ██ ██ ██ ██ ██████ ██ ██ ███████ ██ ██ +// +// >>tracker +_SOKOL_PRIVATE void _sg_tracker_init(_sg_tracker_t* tracker, uint32_t num) { + SOKOL_ASSERT(tracker); + SOKOL_ASSERT(num > 0); + SOKOL_ASSERT(0 == tracker->size); + SOKOL_ASSERT(0 == tracker->cur); + SOKOL_ASSERT(0 == tracker->items); + tracker->size = (uint32_t)num; + tracker->items = (uint32_t*)_sg_malloc_clear(num * sizeof(uint32_t)); +} + +_SOKOL_PRIVATE void _sg_tracker_discard(_sg_tracker_t* tracker) { + SOKOL_ASSERT(tracker); + if (tracker->items) { + _sg_free(tracker->items); + } + tracker->size = 0; + tracker->cur = 0; + tracker->items = 0; +} + +_SOKOL_PRIVATE void _sg_tracker_reset(_sg_tracker_t* tracker) { + SOKOL_ASSERT(tracker && tracker->items); + tracker->cur = 0; +} + +_SOKOL_PRIVATE bool _sg_tracker_add(_sg_tracker_t* tracker, uint32_t res_id) { + SOKOL_ASSERT(tracker && tracker->items); + if (tracker->cur < tracker->size) { + tracker->items[tracker->cur++] = res_id; + return true; + } else { + return false; + } +} + +// ██ ██ ███████ ██ ██████ ███████ ██████ ███████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ███████ █████ ██ ██████ █████ ██████ ███████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██ ██ ███████ ███████ ██ ███████ ██ ██ ███████ +// +// >>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 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); +} + +typedef struct { int x, y, w, h; } _sg_recti_t; + +_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; +} + +_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_STORAGEBUFFER_BINDSLOTS; i++) { + const sg_shader_storage_buffer* src = &desc->storage_buffers[i]; + _sg_shader_storage_buffer_t* dst = &cmn->storage_buffers[i]; + if (src->stage != SG_SHADERSTAGE_NONE) { + cmn->required_bindings_and_uniforms |= required_bindings_flag; + dst->stage = src->stage; + dst->readonly = src->readonly; + } + } + for (size_t i = 0; i < SG_MAX_IMAGE_BINDSLOTS; i++) { + const sg_shader_image* src = &desc->images[i]; + _sg_shader_image_t* dst = &cmn->images[i]; + if (src->stage != SG_SHADERSTAGE_NONE) { + cmn->required_bindings_and_uniforms |= required_bindings_flag; + dst->stage = src->stage; + dst->image_type = src->image_type; + dst->sample_type = src->sample_type; + dst->multisampled = src->multisampled; + } + } + 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_IMAGE_SAMPLER_PAIRS; i++) { + const sg_shader_image_sampler_pair* src = &desc->image_sampler_pairs[i]; + _sg_shader_image_sampler_t* dst = &cmn->image_samplers[i]; + if (src->stage != SG_SHADERSTAGE_NONE) { + dst->stage = src->stage; + SOKOL_ASSERT((src->image_slot >= 0) && (src->image_slot < SG_MAX_IMAGE_BINDSLOTS)); + SOKOL_ASSERT(desc->images[src->image_slot].stage == src->stage); + dst->image_slot = src->image_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; + } + } + for (size_t i = 0; i < SG_MAX_STORAGE_ATTACHMENTS; i++) { + const sg_shader_storage_image* src = &desc->storage_images[i]; + _sg_shader_storage_image_t* dst = &cmn->storage_images[i]; + if (src->stage != SG_SHADERSTAGE_NONE) { + dst->stage = src->stage; + dst->image_type = src->image_type; + dst->access_format = src->access_format; + dst->writeonly = src->writeonly; + } + } +} + +_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 (int i = 0; i < SG_MAX_VERTEXBUFFER_BINDSLOTS; i++) { + const sg_vertex_attr_state* a_state = &desc->layout.attrs[i]; + if (a_state->format != SG_VERTEXFORMAT_INVALID) { + SOKOL_ASSERT(a_state->buffer_index < SG_MAX_VERTEXBUFFER_BINDSLOTS); + cmn->vertex_buffer_layout_active[a_state->buffer_index] = true; + cmn->required_bindings_and_uniforms |= required_bindings_flag; + } + } + cmn->is_compute = desc->compute; + cmn->use_instanced_draw = false; + 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_attachment_common_init(_sg_attachment_common_t* cmn, const sg_attachment_desc* desc, _sg_image_t* img) { + cmn->image = _sg_image_ref(img); + cmn->mip_level = desc->mip_level; + cmn->slice = desc->slice; +} + +_SOKOL_PRIVATE void _sg_attachments_common_init(_sg_attachments_common_t* cmn, const sg_attachments_desc* desc, const _sg_attachments_ptrs_t* ptrs, int width, int height) { + // NOTE: width/height will be 0 for storage image attachments + cmn->width = width; + cmn->height = height; + for (size_t i = 0; i < SG_MAX_COLOR_ATTACHMENTS; i++) { + if (desc->colors[i].image.id != SG_INVALID_ID) { + cmn->num_colors++; + _sg_attachment_common_init(&cmn->colors[i], &desc->colors[i], ptrs->color_images[i]); + _sg_attachment_common_init(&cmn->resolves[i], &desc->resolves[i], ptrs->resolve_images[i]); + cmn->has_render_attachments = true; + } + } + if (desc->depth_stencil.image.id != SG_INVALID_ID) { + _sg_attachment_common_init(&cmn->depth_stencil, &desc->depth_stencil, ptrs->ds_image); + cmn->has_render_attachments = true; + } + // NOTE: storage attachment slots may be non-continuous, + // so a 'num_storages' doesn't make sense + for (size_t i = 0; i < SG_MAX_STORAGE_ATTACHMENTS; i++) { + if (desc->storages[i].image.id != SG_INVALID_ID) { + cmn->has_storage_attachments = true; + _sg_attachment_common_init(&cmn->storages[i], &desc->storages[i], ptrs->storage_images[i]); + } + } +} + +_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_attachment_storage_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 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); +} + +/* 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; +} + +_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_attachments(_sg_attachments_t* atts, const sg_attachments_desc* desc) { + SOKOL_ASSERT(atts && desc); + _SOKOL_UNUSED(atts); + _SOKOL_UNUSED(desc); + return SG_RESOURCESTATE_VALID; +} + +_SOKOL_PRIVATE void _sg_dummy_discard_attachments(_sg_attachments_t* atts) { + SOKOL_ASSERT(atts); + _SOKOL_UNUSED(atts); +} + +_SOKOL_PRIVATE void _sg_dummy_begin_pass(const sg_pass* pass) { + SOKOL_ASSERT(pass); + _SOKOL_UNUSED(pass); +} + +_SOKOL_PRIVATE void _sg_dummy_end_pass(void) { + // empty +} + +_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) { + _SOKOL_UNUSED(base_element); + _SOKOL_UNUSED(num_elements); + _SOKOL_UNUSED(num_instances); +} + +_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(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)) + +// 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_VERTEX_ATTRIBS, &gl_int); + _SG_GL_CHECK_ERROR(); + if (gl_int > SG_MAX_VERTEX_ATTRIBUTES) { + gl_int = SG_MAX_VERTEX_ATTRIBUTES; + } + _sg.limits.max_vertex_attrs = gl_int; + glGetIntegerv(GL_MAX_VERTEX_UNIFORM_COMPONENTS, &gl_int); + _SG_GL_CHECK_ERROR(); + _sg.limits.gl_max_vertex_uniform_components = 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; + 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; + #if defined(__APPLE__) + _sg.features.msaa_image_bindings = false; + #else + _sg.features.msaa_image_bindings = true; + #endif + + // 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_image_bindings = false; + #if defined(__EMSCRIPTEN__) + _sg.features.separate_buffer_types = true; + #else + _sg.features.separate_buffer_types = false; + #endif + + 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 (size_t i = 0; i < _SG_GL_MAX_SBUF_BINDINGS; i++) { + if (force || (_sg.gl.cache.storage_buffers[i] != 0)) { + if (_sg.features.compute) { + 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) { + SOKOL_ASSERT(glsl_binding_n < _SG_GL_MAX_SBUF_BINDINGS); + if (_sg.gl.cache.storage_buffers[glsl_binding_n] != buffer) { + _sg.gl.cache.storage_buffers[glsl_binding_n] = buffer; + _sg.gl.cache.storage_buffer = buffer; // not a bug + if (_sg.features.compute) { + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, glsl_binding_n, buffer); + } + _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 (size_t 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! + 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_IMG_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_IMG_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_IMG_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_IMG_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_IMG_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_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 + + glGenVertexArrays(1, &_sg.gl.vao); + glBindVertexArray(_sg.gl.vao); + _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.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_teximage(const _sg_image_t* img, GLenum tgt, int mip_index, int w, int h, int depth, const GLvoid* data_ptr, GLsizei data_size) { + const bool compressed = _sg_is_compressed_pixel_format(img->cmn.pixel_format); + #if defined(_SOKOL_GL_HAS_TEXSTORAGE) + if (data_ptr == 0) { + return; + } + SOKOL_ASSERT(img->cmn.sample_count == 1); + 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); + } + } + #else + 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) + if (msaa) { + glTexImage2DMultisample(tgt, img->cmn.sample_count, (GLint)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) + if (msaa) { + // NOTE: MSAA works only for array textures, not 3D textures + glTexImage3DMultisample(tgt, img->cmn.sample_count, (GLint)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; + } + + // GLES3/WebGL2/macOS doesn't have support for multisampled textures, so create a render buffer object instead + const bool msaa = img->cmn.sample_count > 1; + if (!_sg.features.msaa_image_bindings && img->cmn.usage.render_attachment && msaa) { + const GLenum gl_internal_format = _sg_gl_teximage_internal_format(img->cmn.pixel_format); + glGenRenderbuffers(1, &img->gl.msaa_render_buffer); + glBindRenderbuffer(GL_RENDERBUFFER, img->gl.msaa_render_buffer); + glRenderbufferStorageMultisample(GL_RENDERBUFFER, img->cmn.sample_count, gl_internal_format, img->cmn.width, img->cmn.height); + } else 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 { + // create our own GL texture(s) + 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); + const int num_faces = img->cmn.type == SG_IMAGETYPE_CUBE ? 6 : 1; + for (int face_index = 0; face_index < num_faces; face_index++) { + for (int mip_index = 0; mip_index < img->cmn.num_mipmaps; mip_index++) { + GLenum gl_img_target = img->gl.target; + if (SG_IMAGETYPE_CUBE == img->cmn.type) { + gl_img_target = _sg_gl_cubeface_target(face_index); + } + const GLvoid* data_ptr = desc->data.subimage[face_index][mip_index].ptr; + const GLsizei data_size = (GLsizei)desc->data.subimage[face_index][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; + _sg_gl_teximage(img, gl_img_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]); + } + } + } + if (img->gl.msaa_render_buffer) { + glDeleteRenderbuffers(1, &img->gl.msaa_render_buffer); + } + _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); + for (size_t i = 0; i < SG_MAX_STORAGEBUFFER_BINDSLOTS; i++) { + if (desc->storage_buffers[i].glsl_binding_n >= _SG_GL_MAX_SBUF_BINDINGS) { + _SG_ERROR(GL_STORAGEBUFFER_GLSL_BINDING_OUT_OF_RANGE); + return false; + } + } + for (size_t i = 0; i < SG_MAX_STORAGE_ATTACHMENTS; i++) { + if (desc->storage_images[i].glsl_binding_n >= _SG_GL_MAX_SIMG_BINDINGS) { + _SG_ERROR(GL_STORAGEIMAGE_GLSL_BINDING_OUT_OF_RANGE); + return false; + } + } + 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 storage buffer bind slots + for (size_t sbuf_index = 0; sbuf_index < SG_MAX_STORAGEBUFFER_BINDSLOTS; sbuf_index++) { + const sg_shader_storage_buffer* sbuf_desc = &desc->storage_buffers[sbuf_index]; + if (sbuf_desc->stage == SG_SHADERSTAGE_NONE) { + continue; + } + SOKOL_ASSERT(sbuf_desc->glsl_binding_n < _SG_GL_MAX_SBUF_BINDINGS); + shd->gl.sbuf_binding[sbuf_index] = sbuf_desc->glsl_binding_n; + } + + // copy storage image bind slots + for (size_t simg_index = 0; simg_index < SG_MAX_STORAGE_ATTACHMENTS; simg_index++) { + const sg_shader_storage_image* simg_desc = &desc->storage_images[simg_index]; + if (simg_desc->stage == SG_SHADERSTAGE_NONE) { + continue; + } + SOKOL_ASSERT(simg_desc->glsl_binding_n < _SG_GL_MAX_SIMG_BINDINGS); + shd->gl.simg_binding[simg_index] = simg_desc->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 img_smp_index = 0; img_smp_index < SG_MAX_IMAGE_SAMPLER_PAIRS; img_smp_index++) { + const sg_shader_image_sampler_pair* img_smp_desc = &desc->image_sampler_pairs[img_smp_index]; + if (img_smp_desc->stage == SG_SHADERSTAGE_NONE) { + continue; + } + SOKOL_ASSERT(img_smp_desc->glsl_name); + GLint gl_loc = glGetUniformLocation(gl_prog, img_smp_desc->glsl_name); + if (gl_loc != -1) { + glUniform1i(gl_loc, gl_tex_slot); + shd->gl.tex_slot[img_smp_index] = (int8_t)gl_tex_slot++; + } else { + shd->gl.tex_slot[img_smp_index] = -1; + _SG_WARN(GL_IMAGE_SAMPLER_NAME_NOT_FOUND_IN_SHADER); + _SG_LOGMSG(GL_IMAGE_SAMPLER_NAME_NOT_FOUND_IN_SHADER, img_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; + pip->cmn.use_instanced_draw = true; + } + 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_attachment_common_t* att, GLenum gl_att_type) { + const _sg_image_t* img = _sg_image_ref_ptr(&att->image); + 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 = att->mip_level; + const int slice = att->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 sg_resource_state _sg_gl_create_attachments(_sg_attachments_t* atts, const sg_attachments_desc* desc) { + SOKOL_ASSERT(atts && desc); + _SOKOL_UNUSED(desc); + _SG_GL_CHECK_ERROR(); + + // if this is a compute pass attachment we're done here + if (atts->cmn.has_storage_attachments) { + SOKOL_ASSERT(!atts->cmn.has_render_attachments); + return SG_RESOURCESTATE_VALID; + } + + // store current framebuffer binding (restored at end of function) + GLuint gl_orig_fb; + glGetIntegerv(GL_FRAMEBUFFER_BINDING, (GLint*)&gl_orig_fb); + + // create a framebuffer object + glGenFramebuffers(1, &atts->gl.fb); + glBindFramebuffer(GL_FRAMEBUFFER, atts->gl.fb); + + // attach color attachments to framebuffer + for (int i = 0; i < atts->cmn.num_colors; i++) { + const _sg_image_t* color_img = _sg_image_ref_ptr(&atts->cmn.colors[i].image); + const GLuint gl_msaa_render_buffer = color_img->gl.msaa_render_buffer; + if (gl_msaa_render_buffer) { + glFramebufferRenderbuffer(GL_FRAMEBUFFER, (GLenum)(GL_COLOR_ATTACHMENT0+i), GL_RENDERBUFFER, gl_msaa_render_buffer); + } else { + const GLenum gl_att_type = (GLenum)(GL_COLOR_ATTACHMENT0 + i); + _sg_gl_fb_attach_texture(&atts->cmn.colors[i], gl_att_type); + } + } + // attach depth-stencil attachment + if (!_sg_image_ref_null(&atts->cmn.depth_stencil.image)) { + const _sg_image_t* ds_img = _sg_image_ref_ptr(&atts->cmn.depth_stencil.image); + const GLenum gl_att_type = _sg_gl_depth_stencil_attachment_type(ds_img); + const GLuint gl_msaa_render_buffer = ds_img->gl.msaa_render_buffer; + if (gl_msaa_render_buffer) { + glFramebufferRenderbuffer(GL_FRAMEBUFFER, gl_att_type, GL_RENDERBUFFER, gl_msaa_render_buffer); + } else { + _sg_gl_fb_attach_texture(&atts->cmn.depth_stencil, gl_att_type); + } + } + + // check if framebuffer is complete + { + 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 SG_RESOURCESTATE_FAILED; + } + } + + // setup color attachments for the framebuffer + static const GLenum gl_draw_bufs[SG_MAX_COLOR_ATTACHMENTS] = { + GL_COLOR_ATTACHMENT0, + GL_COLOR_ATTACHMENT1, + GL_COLOR_ATTACHMENT2, + GL_COLOR_ATTACHMENT3 + }; + glDrawBuffers(atts->cmn.num_colors, gl_draw_bufs); + + // create MSAA resolve framebuffers if necessary + for (int i = 0; i < atts->cmn.num_colors; i++) { + _sg_attachment_common_t* cmn_resolve_att = &atts->cmn.resolves[i]; + if (!_sg_image_ref_null(&cmn_resolve_att->image)) { + SOKOL_ASSERT(0 == atts->gl.msaa_resolve_framebuffer[i]); + glGenFramebuffers(1, &atts->gl.msaa_resolve_framebuffer[i]); + glBindFramebuffer(GL_FRAMEBUFFER, atts->gl.msaa_resolve_framebuffer[i]); + _sg_gl_fb_attach_texture(cmn_resolve_att, GL_COLOR_ATTACHMENT0); + // check if framebuffer is complete + 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 SG_RESOURCESTATE_FAILED; + } + // setup color attachments for the framebuffer + glDrawBuffers(1, &gl_draw_bufs[0]); + } + } + + // restore original framebuffer binding + glBindFramebuffer(GL_FRAMEBUFFER, gl_orig_fb); + _SG_GL_CHECK_ERROR(); + return SG_RESOURCESTATE_VALID; +} + +_SOKOL_PRIVATE void _sg_gl_discard_attachments(_sg_attachments_t* atts) { + SOKOL_ASSERT(atts); + _SG_GL_CHECK_ERROR(); + if (0 != atts->gl.fb) { + glDeleteFramebuffers(1, &atts->gl.fb); + } + for (int i = 0; i < SG_MAX_COLOR_ATTACHMENTS; i++) { + if (atts->gl.msaa_resolve_framebuffer[i]) { + glDeleteFramebuffers(1, &atts->gl.msaa_resolve_framebuffer[i]); + } + } + _SG_GL_CHECK_ERROR(); +} + +_SOKOL_PRIVATE void _sg_gl_begin_pass(const sg_pass* pass) { + // FIXME: what if a texture used as render target is still bound, should we + // unbind all currently bound textures in begin pass? + SOKOL_ASSERT(pass); + _SG_GL_CHECK_ERROR(); + const _sg_attachments_t* atts = _sg_attachments_ref_ptr_or_null(&_sg.cur_pass.atts); + + // early out if this a compute pass + if (pass->compute) { + // first pipeline in pass needs to re-apply storage attachments + if (atts && atts->cmn.has_storage_attachments) { + _sg.gl.cache.cur_pip = _sg_sref(0); + } + return; + } + + const sg_swapchain* swapchain = &pass->swapchain; + const sg_pass_action* action = &pass->action; + + // 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 (atts) { + // offscreen pass + SOKOL_ASSERT(atts->gl.fb); + #if defined(SOKOL_GLCORE) + glEnable(GL_FRAMEBUFFER_SRGB); + #endif + glBindFramebuffer(GL_FRAMEBUFFER, atts->gl.fb); + } else { + // default 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.width, _sg.cur_pass.height); + glScissor(0, 0, _sg.cur_pass.width, _sg.cur_pass.height); + + // number of color attachments + const int num_color_atts = atts ? atts->cmn.num_colors : 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 ((atts == 0) || !_sg_image_ref_null(&atts->cmn.depth_stencil.image)) { + 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(void) { + const _sg_attachments_t* atts = _sg_attachments_ref_ptr_or_null(&_sg.cur_pass.atts); + if (atts) { + bool fb_read_bound = false; + bool fb_draw_bound = false; + const int num_color_atts = atts->cmn.num_colors; + for (int i = 0; i < num_color_atts; i++) { + // perform MSAA resolve if needed + if (atts->gl.msaa_resolve_framebuffer[i] != 0) { + if (!fb_read_bound) { + SOKOL_ASSERT(atts->gl.fb); + glBindFramebuffer(GL_READ_FRAMEBUFFER, atts->gl.fb); + fb_read_bound = true; + } + const _sg_image_t* img = _sg_image_ref_ptr(&atts->cmn.colors[i].image); + const int w = img->cmn.width; + const int h = img->cmn.height; + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, atts->gl.msaa_resolve_framebuffer[i]); + 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, atts->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 (!_sg_image_ref_null(&atts->cmn.depth_stencil.image)) { + 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_compute_pass(void) { + #if defined(_SOKOL_GL_HAS_COMPUTE) + const _sg_attachments_t* atts = _sg_attachments_ref_ptr_or_null(&_sg.cur_pass.atts); + if (atts && atts->cmn.has_storage_attachments) { + glMemoryBarrier(GL_TEXTURE_FETCH_BARRIER_BIT|GL_SHADER_IMAGE_ACCESS_BARRIER_BIT); + } + #endif +} + +_SOKOL_PRIVATE void _sg_gl_end_pass(void) { + _SG_GL_CHECK_ERROR(); + if (_sg.cur_pass.is_compute) { + _sg_gl_end_compute_pass(); + } else { + _sg_gl_end_render_pass(); + } + _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.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.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_compute_pipeline_state(_sg_pipeline_t* pip) { + #if defined(_SOKOL_GL_HAS_COMPUTE) + // apply storage attachment images (if any) + const _sg_attachments_t* atts = _sg_attachments_ref_ptr_or_null(&_sg.cur_pass.atts); + if (atts) { + const _sg_shader_t* shd = _sg_shader_ref_ptr(&pip->cmn.shader); + for (size_t i = 0; i < SG_MAX_STORAGE_ATTACHMENTS; i++) { + if (shd->cmn.storage_images[i].stage == SG_SHADERSTAGE_NONE) { + continue; + } + SOKOL_ASSERT(shd->cmn.storage_images[i].stage == SG_SHADERSTAGE_COMPUTE); + SOKOL_ASSERT(shd->gl.simg_binding[i] < _SG_GL_MAX_SIMG_BINDINGS); + _sg_image_t* img = _sg_image_ref_ptr(&atts->cmn.storages[i].image); + GLuint gl_unit = shd->gl.simg_binding[i]; + GLuint gl_tex = img->gl.tex[img->cmn.active_slot]; + GLint level = atts->cmn.storages[i].mip_level; + GLint layer = atts->cmn.storages[i].slice; + GLboolean layered = shd->cmn.storage_images[i].image_type != SG_IMAGETYPE_2D; + GLenum access = shd->cmn.storage_images[i].writeonly ? GL_WRITE_ONLY : GL_READ_WRITE; + GLenum format = _sg_gl_teximage_internal_format(shd->cmn.storage_images[i].access_format); + // FIXME: go through state cache, use attachment id as key + glBindImageTexture(gl_unit, gl_tex, level, layered, layer, access, format); + _SG_GL_CHECK_ERROR(); + } + } + #else + _SOKOL_UNUSED(pip); + #endif +} + +_SOKOL_PRIVATE void _sg_gl_apply_pipeline(_sg_pipeline_t* pip) { + SOKOL_ASSERT(pip); + _SG_GL_CHECK_ERROR(); + if (!_sg_sref_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_compute_pipeline_state(pip); + } else { + _sg_gl_apply_render_pipeline_state(pip); + } + } + _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) { + 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 + 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_STORAGEBUFFER_BINDSLOTS; i++) { + _sg_buffer_t* buf = bnd->sbufs[i]; + if (!buf) { + continue; + } + SOKOL_ASSERT(shd->cmn.storage_buffers[i].stage != SG_SHADERSTAGE_NONE); + 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; + } + } + + // mark storage buffers as dirty which will be written by compute shaders + // (don't merge this into the above loop, this would mess up the dirty + // dirty flags if the same buffer is bound multiple times) + for (size_t i = 0; i < SG_MAX_STORAGEBUFFER_BINDSLOTS; i++) { + _sg_buffer_t* buf = bnd->sbufs[i]; + if (!buf) { + continue; + } + if (!shd->cmn.storage_buffers[i].readonly) { + buf->gl.gpu_dirty_flags = _SG_GL_GPUDIRTY_BUFFER_ALL; + } + } + if (0 != gl_barrier_bits) { + glMemoryBarrier(gl_barrier_bits); + _sg_stats_add(gl.num_memory_barriers, 1); + } +} +#endif + +_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 image-samplers + _SG_GL_CHECK_ERROR(); + for (size_t img_smp_index = 0; img_smp_index < SG_MAX_IMAGE_SAMPLER_PAIRS; img_smp_index++) { + const _sg_shader_image_sampler_t* img_smp = &shd->cmn.image_samplers[img_smp_index]; + if (img_smp->stage == SG_SHADERSTAGE_NONE) { + continue; + } + const int8_t gl_tex_slot = (GLint)shd->gl.tex_slot[img_smp_index]; + if (gl_tex_slot != -1) { + SOKOL_ASSERT(img_smp->image_slot < SG_MAX_IMAGE_BINDSLOTS); + SOKOL_ASSERT(img_smp->sampler_slot < SG_MAX_SAMPLER_BINDSLOTS); + const _sg_image_t* img = bnd->imgs[img_smp->image_slot]; + const _sg_sampler_t* smp = bnd->smps[img_smp->sampler_slot]; + SOKOL_ASSERT(img); + SOKOL_ASSERT(smp); + const GLenum gl_tgt = img->gl.target; + const GLuint gl_tex = img->gl.tex[img->cmn.active_slot]; + const GLuint gl_smp = smp->gl.smp; + _sg_gl_cache_bind_texture_sampler(gl_tex_slot, gl_tgt, gl_tex, gl_smp); + } + } + _SG_GL_CHECK_ERROR(); + + // bind storage buffers + for (size_t sbuf_index = 0; sbuf_index < SG_MAX_STORAGEBUFFER_BINDSLOTS; sbuf_index++) { + if (shd->cmn.storage_buffers[sbuf_index].stage == SG_SHADERSTAGE_NONE) { + continue; + } + const _sg_buffer_t* sbuf = bnd->sbufs[sbuf_index]; + const uint8_t binding = shd->gl.sbuf_binding[sbuf_index]; + GLuint gl_sbuf = sbuf->gl.buf[sbuf->cmn.active_slot]; + _sg_gl_cache_bind_storage_buffer(binding, gl_sbuf); + } + _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 buffer memory barriers (this needs to happen after the bindings are set) + #if defined(_SOKOL_GL_HAS_COMPUTE) + _sg_gl_handle_memory_barriers(shd, bnd); + _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) { + const GLenum i_type = _sg.gl.cache.cur_index_type; + const GLenum p_type = _sg.gl.cache.cur_primitive_type; + const bool use_instanced_draw = (num_instances > 1) || _sg_pipeline_ref_ptr(&_sg.cur_pip)->cmn.use_instanced_draw; + if (0 != i_type) { + // indexed rendering + 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) { + glDrawElementsInstanced(p_type, num_elements, i_type, indices, num_instances); + } else { + glDrawElements(p_type, num_elements, i_type, indices); + } + } else { + // non-indexed rendering + if (use_instanced_draw) { + glDrawArraysInstanced(p_type, base_element, num_elements, num_instances); + } 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 GLenum gl_img_format = _sg_gl_teximage_format(img->cmn.pixel_format); + const GLenum gl_img_type = _sg_gl_teximage_type(img->cmn.pixel_format); + const int num_faces = img->cmn.type == SG_IMAGETYPE_CUBE ? 6 : 1; + const int num_mips = img->cmn.num_mipmaps; + for (int face_index = 0; face_index < num_faces; face_index++) { + for (int mip_index = 0; mip_index < num_mips; mip_index++) { + GLenum gl_img_target = img->gl.target; + if (SG_IMAGETYPE_CUBE == img->cmn.type) { + gl_img_target = _sg_gl_cubeface_target(face_index); + } + const GLvoid* data_ptr = data->subimage[face_index][mip_index].ptr; + int mip_width = _sg_miplevel_dim(img->cmn.width, mip_index); + int mip_height = _sg_miplevel_dim(img->cmn.height, mip_index); + if ((SG_IMAGETYPE_2D == img->cmn.type) || (SG_IMAGETYPE_CUBE == img->cmn.type)) { + glTexSubImage2D(gl_img_target, mip_index, + 0, 0, + mip_width, mip_height, + gl_img_format, gl_img_type, + data_ptr); + } else if ((SG_IMAGETYPE_3D == img->cmn.type) || (SG_IMAGETYPE_ARRAY == img->cmn.type)) { + int mip_depth = img->cmn.num_slices; + if (SG_IMAGETYPE_3D == img->cmn.type) { + mip_depth = _sg_miplevel_dim(img->cmn.num_slices, mip_index); + } + glTexSubImage3D(gl_img_target, mip_index, + 0, 0, 0, + mip_width, mip_height, mip_depth, + gl_img_format, gl_img_type, + data_ptr); + + } + } + } + _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 +} + +//-- enum translation functions ------------------------------------------------ +_SOKOL_PRIVATE D3D11_USAGE _sg_d3d11_image_usage(const sg_image_usage* usg) { + if (usg->immutable) { + if (usg->render_attachment || usg->storage_attachment) { + 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, sg_pixel_format fmt) { + UINT res = D3D11_BIND_SHADER_RESOURCE; + if (usg->render_attachment) { + if (_sg_is_depth_or_depth_stencil_format(fmt)) { + res |= D3D11_BIND_DEPTH_STENCIL; + } else { + res |= D3D11_BIND_RENDER_TARGET; + } + } else if (usg->storage_attachment) { + res |= D3D11_BIND_UNORDERED_ACCESS; + } + return res; +} + +_SOKOL_PRIVATE UINT _sg_d3d11_image_cpu_access_flags(const sg_image_usage* usg) { + if (usg->render_attachment || usg->storage_attachment || 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) { + if (usg->immutable) { + res |= D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_UNORDERED_ACCESS; + } else { + res |= D3D11_BIND_SHADER_RESOURCE; + } + } + 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_image_bindings = 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 = 2 * 1024; + _sg.limits.max_vertex_attrs = SG_MAX_VERTEX_ATTRIBUTES; + + // 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(); +} + +_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; + } + + // for storage buffers need to create a shader-resource-view + // for read-only access, and an unordered-access-view for + // read-write access + if (buf->cmn.usage.storage_buffer) { + SOKOL_ASSERT(_sg_multiple_u64((uint64_t)buf->cmn.size, 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 = 0; + d3d11_srv_desc.BufferEx.NumElements = ((UINT)buf->cmn.size) / 4; + d3d11_srv_desc.BufferEx.Flags = D3D11_BUFFEREX_SRV_FLAG_RAW; + hr = _sg_d3d11_CreateShaderResourceView(_sg.d3d11.dev, (ID3D11Resource*)buf->d3d11.buf, &d3d11_srv_desc, &buf->d3d11.srv); + if (!(SUCCEEDED(hr) && buf->d3d11.srv)) { + _SG_ERROR(D3D11_CREATE_BUFFER_SRV_FAILED); + return SG_RESOURCESTATE_FAILED; + } + 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 = 0; + d3d11_uav_desc.Buffer.NumElements = ((UINT)buf->cmn.size) / 4; + d3d11_uav_desc.Buffer.Flags = D3D11_BUFFER_UAV_FLAG_RAW; + hr = _sg_d3d11_CreateUnorderedAccessView(_sg.d3d11.dev, (ID3D11Resource*)buf->d3d11.buf, &d3d11_uav_desc, &buf->d3d11.uav); + if (!(SUCCEEDED(hr) && buf->d3d11.uav)) { + _SG_ERROR(D3D11_CREATE_BUFFER_UAV_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); + } + if (buf->d3d11.srv) { + _sg_d3d11_Release(buf->d3d11.srv); + } + if (buf->d3d11.uav) { + _sg_d3d11_Release(buf->d3d11.uav); + } +} + +_SOKOL_PRIVATE void _sg_d3d11_fill_subres_data(const _sg_image_t* img, const sg_image_data* data) { + const int num_faces = (img->cmn.type == SG_IMAGETYPE_CUBE) ? 6:1; + const int num_slices = (img->cmn.type == SG_IMAGETYPE_ARRAY) ? img->cmn.num_slices:1; + int subres_index = 0; + for (int face_index = 0; face_index < num_faces; face_index++) { + 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_MAX_MIPMAPS * SG_MAX_TEXTUREARRAY_LAYERS)); + 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* subimg_data = &(data->subimage[face_index][mip_index]); + const size_t slice_size = subimg_data->size / (size_t)num_slices; + const size_t slice_offset = slice_size * (size_t)slice_index; + const uint8_t* ptr = (const uint8_t*) subimg_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) { + // FIXME? const int mip_depth = _sg_miplevel_dim(img->depth, mip_index); + 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) && (0 == img->d3d11.srv)); + 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.subimage[0][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); + img->d3d11.srv = (ID3D11ShaderResourceView*) desc->d3d11_shader_resource_view; + if (img->d3d11.srv) { + _sg_d3d11_AddRef(img->d3d11.srv); + } + } 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; + switch (img->cmn.type) { + case SG_IMAGETYPE_ARRAY: d3d11_tex_desc.ArraySize = (UINT)img->cmn.num_slices; break; + case SG_IMAGETYPE_CUBE: d3d11_tex_desc.ArraySize = 6; break; + default: d3d11_tex_desc.ArraySize = 1; break; + } + d3d11_tex_desc.Format = img->d3d11.format; + d3d11_tex_desc.BindFlags = _sg_d3d11_image_bind_flags(&img->cmn.usage, img->cmn.pixel_format); + 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); + + // create shader-resource-view for 2D 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: + d3d11_srv_desc.ViewDimension = msaa ? D3D11_SRV_DIMENSION_TEXTURE2DMS : D3D11_SRV_DIMENSION_TEXTURE2D; + d3d11_srv_desc.Texture2D.MipLevels = (UINT)img->cmn.num_mipmaps; + break; + case SG_IMAGETYPE_CUBE: + d3d11_srv_desc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURECUBE; + d3d11_srv_desc.TextureCube.MipLevels = (UINT)img->cmn.num_mipmaps; + break; + case SG_IMAGETYPE_ARRAY: + d3d11_srv_desc.ViewDimension = msaa ? D3D11_SRV_DIMENSION_TEXTURE2DMSARRAY : D3D11_SRV_DIMENSION_TEXTURE2DARRAY; + d3d11_srv_desc.Texture2DArray.MipLevels = (UINT)img->cmn.num_mipmaps; + d3d11_srv_desc.Texture2DArray.ArraySize = (UINT)img->cmn.num_slices; + break; + default: + SOKOL_UNREACHABLE; break; + } + hr = _sg_d3d11_CreateShaderResourceView(_sg.d3d11.dev, (ID3D11Resource*)img->d3d11.tex2d, &d3d11_srv_desc, &img->d3d11.srv); + if (!(SUCCEEDED(hr) && img->d3d11.srv)) { + _SG_ERROR(D3D11_CREATE_2D_SRV_FAILED); + return SG_RESOURCESTATE_FAILED; + } + _sg_d3d11_setlabel(img->d3d11.srv, 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); + img->d3d11.srv = (ID3D11ShaderResourceView*) desc->d3d11_shader_resource_view; + if (img->d3d11.srv) { + _sg_d3d11_AddRef(img->d3d11.srv); + } + } 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, img->cmn.pixel_format); + 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); + + // create shader-resource-view for 3D texture + if (!msaa) { + 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); + d3d11_srv_desc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE3D; + d3d11_srv_desc.Texture3D.MipLevels = (UINT)img->cmn.num_mipmaps; + hr = _sg_d3d11_CreateShaderResourceView(_sg.d3d11.dev, (ID3D11Resource*)img->d3d11.tex3d, &d3d11_srv_desc, &img->d3d11.srv); + if (!(SUCCEEDED(hr) && img->d3d11.srv)) { + _SG_ERROR(D3D11_CREATE_3D_SRV_FAILED); + return SG_RESOURCESTATE_FAILED; + } + _sg_d3d11_setlabel(img->d3d11.srv, 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); + } + if (img->d3d11.srv) { + _sg_d3d11_Release(img->d3d11.srv); + } +} + +_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 + NULL, // pSourceName + NULL, // pDefines + NULL, // 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++) { + if (desc->uniform_blocks[i].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_STORAGEBUFFER_BINDSLOTS; i++) { + if (desc->storage_buffers[i].hlsl_register_t_n >= _SG_D3D11_MAX_STAGE_SRV_BINDINGS) { + _SG_ERROR(D3D11_STORAGEBUFFER_HLSL_REGISTER_T_OUT_OF_RANGE); + return false; + } + if (desc->storage_buffers[i].hlsl_register_u_n >= _SG_D3D11_MAX_STAGE_UAV_BINDINGS) { + _SG_ERROR(D3D11_STORAGEBUFFER_HLSL_REGISTER_U_OUT_OF_RANGE); + return false; + } + } + for (size_t i = 0; i < SG_MAX_IMAGE_BINDSLOTS; i++) { + if (desc->images[i].hlsl_register_t_n >= _SG_D3D11_MAX_STAGE_SRV_BINDINGS) { + _SG_ERROR(D3D11_IMAGE_HLSL_REGISTER_T_OUT_OF_RANGE); + return false; + } + } + for (size_t i = 0; i < SG_MAX_SAMPLER_BINDSLOTS; i++) { + if (desc->samplers[i].hlsl_register_s_n >= _SG_D3D11_MAX_STAGE_SMP_BINDINGS) { + _SG_ERROR(D3D11_SAMPLER_HLSL_REGISTER_S_OUT_OF_RANGE); + return false; + } + } + for (size_t i = 0; i < SG_MAX_STORAGE_ATTACHMENTS; i++) { + if (desc->storage_images[i].hlsl_register_u_n >= _SG_D3D11_MAX_STAGE_UAV_BINDINGS) { + _SG_ERROR(D3D11_STORAGEIMAGE_HLSL_REGISTER_U_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++) { + shd->d3d11.ub_register_b_n[i] = desc->uniform_blocks[i].hlsl_register_b_n; + } + for (size_t i = 0; i < SG_MAX_STORAGEBUFFER_BINDSLOTS; i++) { + shd->d3d11.sbuf_register_t_n[i] = desc->storage_buffers[i].hlsl_register_t_n; + shd->d3d11.sbuf_register_u_n[i] = desc->storage_buffers[i].hlsl_register_u_n; + } + for (size_t i = 0; i < SG_MAX_IMAGE_BINDSLOTS; i++) { + shd->d3d11.img_register_t_n[i] = desc->images[i].hlsl_register_t_n; + } + for (size_t i = 0; i < SG_MAX_SAMPLER_BINDSLOTS; i++) { + shd->d3d11.smp_register_s_n[i] = desc->samplers[i].hlsl_register_s_n; + } + for (size_t i = 0; i < SG_MAX_STORAGE_ATTACHMENTS; i++) { + shd->d3d11.simg_register_u_n[i] = desc->storage_images[i].hlsl_register_u_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; + pip->cmn.use_instanced_draw = true; + } + } + 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_attachments(_sg_attachments_t* atts, const sg_attachments_desc* desc) { + SOKOL_ASSERT(atts && desc); + SOKOL_ASSERT(_sg.d3d11.dev); + _SOKOL_UNUSED(desc); + + // create render-target views + for (int i = 0; i < atts->cmn.num_colors; i++) { + const _sg_attachment_common_t* cmn_color_att = &atts->cmn.colors[i]; + const _sg_image_t* clr_img = _sg_image_ref_ptr(&atts->cmn.colors[i].image); + SOKOL_ASSERT(0 == atts->d3d11.colors[i].view.rtv); + const bool msaa = clr_img->cmn.sample_count > 1; + 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(clr_img->cmn.pixel_format); + switch (clr_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 = (UINT)cmn_color_att->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 = (UINT)cmn_color_att->slice; + d3d11_rtv_desc.Texture2DMSArray.ArraySize = 1; + } else { + d3d11_rtv_desc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2DARRAY; + d3d11_rtv_desc.Texture2DArray.MipSlice = (UINT)cmn_color_att->mip_level; + d3d11_rtv_desc.Texture2DArray.FirstArraySlice = (UINT)cmn_color_att->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 = (UINT)cmn_color_att->mip_level; + d3d11_rtv_desc.Texture3D.FirstWSlice = (UINT)cmn_color_att->slice; + d3d11_rtv_desc.Texture3D.WSize = 1; + break; + default: SOKOL_UNREACHABLE; break; + } + SOKOL_ASSERT(clr_img->d3d11.res); + HRESULT hr = _sg_d3d11_CreateRenderTargetView(_sg.d3d11.dev, clr_img->d3d11.res, &d3d11_rtv_desc, &atts->d3d11.colors[i].view.rtv); + if (!(SUCCEEDED(hr) && atts->d3d11.colors[i].view.rtv)) { + _SG_ERROR(D3D11_CREATE_RTV_FAILED); + return SG_RESOURCESTATE_FAILED; + } + _sg_d3d11_setlabel(atts->d3d11.colors[i].view.rtv, desc->label); + } + SOKOL_ASSERT(0 == atts->d3d11.depth_stencil.view.dsv); + if (!_sg_image_ref_null(&atts->cmn.depth_stencil.image)) { + const _sg_attachment_common_t* cmn_ds_att = &atts->cmn.depth_stencil; + const _sg_image_t* ds_img = _sg_image_ref_ptr(&atts->cmn.depth_stencil.image); + const bool msaa = ds_img->cmn.sample_count > 1; + 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(ds_img->cmn.pixel_format); + SOKOL_ASSERT(ds_img && ds_img->cmn.type != SG_IMAGETYPE_3D); + switch(ds_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 = (UINT)cmn_ds_att->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 = (UINT)cmn_ds_att->slice; + d3d11_dsv_desc.Texture2DMSArray.ArraySize = 1; + } else { + d3d11_dsv_desc.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2DARRAY; + d3d11_dsv_desc.Texture2DArray.MipSlice = (UINT)cmn_ds_att->mip_level; + d3d11_dsv_desc.Texture2DArray.FirstArraySlice = (UINT)cmn_ds_att->slice; + d3d11_dsv_desc.Texture2DArray.ArraySize = 1; + } + break; + default: SOKOL_UNREACHABLE; break; + } + SOKOL_ASSERT(ds_img->d3d11.res); + HRESULT hr = _sg_d3d11_CreateDepthStencilView(_sg.d3d11.dev, ds_img->d3d11.res, &d3d11_dsv_desc, &atts->d3d11.depth_stencil.view.dsv); + if (!(SUCCEEDED(hr) && atts->d3d11.depth_stencil.view.dsv)) { + _SG_ERROR(D3D11_CREATE_DSV_FAILED); + return SG_RESOURCESTATE_FAILED; + } + _sg_d3d11_setlabel(atts->d3d11.depth_stencil.view.dsv, desc->label); + } + + // create storage attachments unordered access views + for (int i = 0; i < SG_MAX_STORAGE_ATTACHMENTS; i++) { + const _sg_attachment_common_t* cmn_stg_att = &atts->cmn.storages[i]; + const _sg_image_t* stg_img = _sg_image_ref_ptr_or_null(&atts->cmn.storages[i].image); + if (!stg_img) { + continue; + } + SOKOL_ASSERT(stg_img->cmn.sample_count == 1); + SOKOL_ASSERT(0 == atts->d3d11.storages[i].view.uav); + 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(stg_img->cmn.pixel_format); + switch (stg_img->cmn.type) { + case SG_IMAGETYPE_2D: + d3d11_uav_desc.ViewDimension = D3D11_UAV_DIMENSION_TEXTURE2D; + d3d11_uav_desc.Texture2D.MipSlice = (UINT)cmn_stg_att->mip_level; + break; + case SG_IMAGETYPE_CUBE: + case SG_IMAGETYPE_ARRAY: + d3d11_uav_desc.ViewDimension = D3D11_UAV_DIMENSION_TEXTURE2DARRAY; + d3d11_uav_desc.Texture2DArray.MipSlice = (UINT)cmn_stg_att->mip_level; + d3d11_uav_desc.Texture2DArray.FirstArraySlice = (UINT)cmn_stg_att->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 = (UINT)cmn_stg_att->mip_level; + d3d11_uav_desc.Texture3D.FirstWSlice = (UINT)cmn_stg_att->slice; + d3d11_uav_desc.Texture3D.WSize = 1; + break; + default: SOKOL_UNREACHABLE; break; + } + SOKOL_ASSERT(stg_img->d3d11.res); + HRESULT hr = _sg_d3d11_CreateUnorderedAccessView(_sg.d3d11.dev, stg_img->d3d11.res, &d3d11_uav_desc, &atts->d3d11.storages[i].view.uav); + if (!(SUCCEEDED(hr) && atts->d3d11.storages[i].view.uav)) { + _SG_ERROR(D3D11_CREATE_UAV_FAILED); + return SG_RESOURCESTATE_FAILED; + } + _sg_d3d11_setlabel(atts->d3d11.storages[i].view.uav, desc->label); + } + return SG_RESOURCESTATE_VALID; +} + +_SOKOL_PRIVATE void _sg_d3d11_discard_attachments(_sg_attachments_t* atts) { + SOKOL_ASSERT(atts); + for (size_t i = 0; i < SG_MAX_COLOR_ATTACHMENTS; i++) { + if (atts->d3d11.colors[i].view.rtv) { + _sg_d3d11_Release(atts->d3d11.colors[i].view.rtv); + } + } + if (atts->d3d11.depth_stencil.view.dsv) { + _sg_d3d11_Release(atts->d3d11.depth_stencil.view.dsv); + } + for (size_t i = 0; i < SG_MAX_STORAGE_ATTACHMENTS; i++) { + if (atts->d3d11.storages[i].view.uav) { + _sg_d3d11_Release(atts->d3d11.storages[i].view.uav); + } + } +} + +_SOKOL_PRIVATE void _sg_d3d11_begin_pass(const sg_pass* pass) { + SOKOL_ASSERT(pass); + if (_sg.cur_pass.is_compute) { + // nothing to do in compute passes + return; + } + const _sg_attachments_t* atts = _sg_attachments_ref_ptr_or_null(&_sg.cur_pass.atts); + const sg_swapchain* swapchain = &pass->swapchain; + const sg_pass_action* action = &pass->action; + + int num_rtvs = 0; + ID3D11RenderTargetView* rtvs[SG_MAX_COLOR_ATTACHMENTS] = { 0 }; + ID3D11DepthStencilView* dsv = 0; + _sg.d3d11.cur_pass.render_view = 0; + _sg.d3d11.cur_pass.resolve_view = 0; + if (atts) { + num_rtvs = atts->cmn.num_colors; + for (size_t i = 0; i < SG_MAX_COLOR_ATTACHMENTS; i++) { + rtvs[i] = atts->d3d11.colors[i].view.rtv; + } + dsv = atts->d3d11.depth_stencil.view.dsv; + } else { + // NOTE: depth-stencil-view is optional + 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_pass.render_view = (ID3D11RenderTargetView*) swapchain->d3d11.render_view; + _sg.d3d11.cur_pass.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.width; + vp.Height = (FLOAT) _sg.cur_pass.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.width; + rect.bottom = _sg.cur_pass.height; + _sg_d3d11_RSSetScissorRects(_sg.d3d11.ctx, 1, &rect); + + // perform clear 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(void) { + SOKOL_ASSERT(_sg.d3d11.ctx); + const _sg_attachments_t* atts = _sg_attachments_ref_ptr_or_null(&_sg.cur_pass.atts); + + if (!_sg.cur_pass.is_compute) { + // need to resolve MSAA render attachments into texture? + if (atts) { + // ...for offscreen pass... + for (size_t i = 0; i < (size_t)atts->cmn.num_colors; i++) { + const _sg_image_t* resolve_img = _sg_image_ref_ptr_or_null(&atts->cmn.resolves[i].image); + if (resolve_img) { + const _sg_image_t* color_img = _sg_image_ref_ptr(&atts->cmn.colors[i].image); + const _sg_attachment_common_t* cmn_color_att = &atts->cmn.colors[i]; + const _sg_attachment_common_t* cmn_resolve_att = &atts->cmn.resolves[i]; + SOKOL_ASSERT(color_img->cmn.sample_count > 1); + SOKOL_ASSERT(resolve_img->cmn.sample_count == 1); + const UINT src_subres = _sg_d3d11_calcsubresource( + (UINT)cmn_color_att->mip_level, + (UINT)cmn_color_att->slice, + (UINT)color_img->cmn.num_mipmaps); + const UINT dst_subres = _sg_d3d11_calcsubresource( + (UINT)cmn_resolve_att->mip_level, + (UINT)cmn_resolve_att->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_pass.resolve_view) { + SOKOL_ASSERT(_sg.d3d11.cur_pass.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_pass.render_view, &d3d11_render_res); + _sg_d3d11_GetResource((ID3D11View*)_sg.d3d11.cur_pass.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_pass.render_view = 0; + _sg.d3d11.cur_pass.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.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.height - (y + h))); + rect.right = x + w; + rect.bottom = origin_top_left ? (y + h) : (_sg.cur_pass.height - y); + _sg_d3d11_RSSetScissorRects(_sg.d3d11.ctx, 1, &rect); +} + +_SOKOL_PRIVATE void _sg_d3d11_populate_storage_attachment_uavs(_sg_pipeline_t* pip, ID3D11UnorderedAccessView** d3d11_cs_uavs) { + const _sg_attachments_t* atts = _sg_attachments_ref_ptr(&_sg.cur_pass.atts); + const _sg_shader_t* shd = _sg_shader_ref_ptr(&pip->cmn.shader); + for (size_t i = 0; i < SG_MAX_STORAGE_ATTACHMENTS; i++) { + if (shd->cmn.storage_images[i].stage != SG_SHADERSTAGE_COMPUTE) { + continue; + } + SOKOL_ASSERT(shd->d3d11.simg_register_u_n[i] < _SG_D3D11_MAX_STAGE_UAV_BINDINGS); + SOKOL_ASSERT(atts->d3d11.storages[i].view.uav); + SOKOL_ASSERT(0 == d3d11_cs_uavs[i]); + d3d11_cs_uavs[shd->d3d11.simg_register_u_n[i]] = atts->d3d11.storages[i].view.uav; + } +} + +_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); + + // bind storage attachment UAVs + if (!_sg_attachments_ref_null(&_sg.cur_pass.atts)) { + ID3D11UnorderedAccessView* d3d11_cs_uavs[_SG_D3D11_MAX_STAGE_UAV_BINDINGS] = {0}; + _sg_d3d11_populate_storage_attachment_uavs(pip, d3d11_cs_uavs); + _sg_d3d11_CSSetUnorderedAccessViews(_sg.d3d11.ctx, 0, _SG_D3D11_MAX_STAGE_UAV_BINDINGS, d3d11_cs_uavs, NULL); + _sg_stats_add(d3d11.bindings.num_cs_set_unordered_access_views, 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.use_indexed_draw = (pip->d3d11.index_format != DXGI_FORMAT_UNKNOWN); + _sg.d3d11.use_instanced_draw = pip->cmn.use_instanced_draw; + + _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; + + // gather all the D3D11 resources into arrays + ID3D11Buffer* d3d11_ib = bnd->ib ? bnd->ib->d3d11.buf : 0; + ID3D11Buffer* d3d11_vbs[SG_MAX_VERTEXBUFFER_BINDSLOTS] = {0}; + UINT d3d11_vb_offsets[SG_MAX_VERTEXBUFFER_BINDSLOTS] = {0}; + ID3D11ShaderResourceView* d3d11_vs_srvs[_SG_D3D11_MAX_STAGE_SRV_BINDINGS] = {0}; + ID3D11ShaderResourceView* d3d11_fs_srvs[_SG_D3D11_MAX_STAGE_SRV_BINDINGS] = {0}; + ID3D11ShaderResourceView* d3d11_cs_srvs[_SG_D3D11_MAX_STAGE_SRV_BINDINGS] = {0}; + ID3D11SamplerState* d3d11_vs_smps[_SG_D3D11_MAX_STAGE_SMP_BINDINGS] = {0}; + ID3D11SamplerState* d3d11_fs_smps[_SG_D3D11_MAX_STAGE_SMP_BINDINGS] = {0}; + ID3D11SamplerState* d3d11_cs_smps[_SG_D3D11_MAX_STAGE_SMP_BINDINGS] = {0}; + ID3D11UnorderedAccessView* d3d11_cs_uavs[_SG_D3D11_MAX_STAGE_UAV_BINDINGS] = {0}; + + if (!is_compute) { + 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); + d3d11_vbs[i] = vb->d3d11.buf; + d3d11_vb_offsets[i] = (UINT)bnd->vb_offsets[i]; + } + } + for (size_t i = 0; i < SG_MAX_IMAGE_BINDSLOTS; i++) { + const _sg_image_t* img = bnd->imgs[i]; + if (img == 0) { + continue; + } + const sg_shader_stage stage = shd->cmn.images[i].stage; + SOKOL_ASSERT(stage != SG_SHADERSTAGE_NONE); + const uint8_t d3d11_slot = shd->d3d11.img_register_t_n[i]; + SOKOL_ASSERT(d3d11_slot < _SG_D3D11_MAX_STAGE_SRV_BINDINGS); + SOKOL_ASSERT(img->d3d11.srv); + ID3D11ShaderResourceView* d3d11_srv = img->d3d11.srv; + switch (stage) { + case SG_SHADERSTAGE_VERTEX: d3d11_vs_srvs[d3d11_slot] = d3d11_srv; break; + case SG_SHADERSTAGE_FRAGMENT: d3d11_fs_srvs[d3d11_slot] = d3d11_srv; break; + case SG_SHADERSTAGE_COMPUTE: d3d11_cs_srvs[d3d11_slot] = d3d11_srv; break; + default: SOKOL_UNREACHABLE; + } + } + for (size_t i = 0; i < SG_MAX_STORAGEBUFFER_BINDSLOTS; i++) { + const _sg_buffer_t* sbuf = bnd->sbufs[i]; + if (sbuf == 0) { + continue; + } + const sg_shader_stage stage = shd->cmn.storage_buffers[i].stage; + SOKOL_ASSERT(stage != SG_SHADERSTAGE_NONE); + if (shd->cmn.storage_buffers[i].readonly) { + SOKOL_ASSERT(sbuf->d3d11.srv); + const uint8_t d3d11_slot = shd->d3d11.sbuf_register_t_n[i]; + SOKOL_ASSERT(d3d11_slot < _SG_D3D11_MAX_STAGE_SRV_BINDINGS); + ID3D11ShaderResourceView* d3d11_srv = sbuf->d3d11.srv; + switch (stage) { + case SG_SHADERSTAGE_VERTEX: d3d11_vs_srvs[d3d11_slot] = d3d11_srv; break; + case SG_SHADERSTAGE_FRAGMENT: d3d11_fs_srvs[d3d11_slot] = d3d11_srv; break; + case SG_SHADERSTAGE_COMPUTE: d3d11_cs_srvs[d3d11_slot] = d3d11_srv; break; + default: SOKOL_UNREACHABLE; + } + } else { + SOKOL_ASSERT(sbuf->d3d11.uav); + SOKOL_ASSERT(stage == SG_SHADERSTAGE_COMPUTE); + const uint8_t d3d11_slot = shd->d3d11.sbuf_register_u_n[i]; + SOKOL_ASSERT(d3d11_slot < _SG_D3D11_MAX_STAGE_UAV_BINDINGS); + d3d11_cs_uavs[d3d11_slot] = sbuf->d3d11.uav; + } + } + 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: d3d11_vs_smps[d3d11_slot] = d3d11_smp; break; + case SG_SHADERSTAGE_FRAGMENT: d3d11_fs_smps[d3d11_slot] = d3d11_smp; break; + case SG_SHADERSTAGE_COMPUTE: d3d11_cs_smps[d3d11_slot] = d3d11_smp; break; + default: SOKOL_UNREACHABLE; + } + } + if (is_compute) { + // in a compute pass with storage attachments, also need to rebind the storage attachments + if (!_sg_attachments_ref_null(&_sg.cur_pass.atts)) { + _sg_d3d11_populate_storage_attachment_uavs(bnd->pip, d3d11_cs_uavs); + } + _sg_d3d11_CSSetShaderResources(_sg.d3d11.ctx, 0, _SG_D3D11_MAX_STAGE_SRV_BINDINGS, d3d11_cs_srvs); + _sg_d3d11_CSSetSamplers(_sg.d3d11.ctx, 0, _SG_D3D11_MAX_STAGE_SMP_BINDINGS, d3d11_cs_smps); + _sg_d3d11_CSSetUnorderedAccessViews(_sg.d3d11.ctx, 0, _SG_D3D11_MAX_STAGE_UAV_BINDINGS, d3d11_cs_uavs, NULL); + _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, d3d11_vbs, bnd->pip->d3d11.vb_strides, d3d11_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, d3d11_vs_srvs); + _sg_d3d11_PSSetShaderResources(_sg.d3d11.ctx, 0, _SG_D3D11_MAX_STAGE_SRV_BINDINGS, d3d11_fs_srvs); + _sg_d3d11_VSSetSamplers(_sg.d3d11.ctx, 0, _SG_D3D11_MAX_STAGE_SMP_BINDINGS, d3d11_vs_smps); + _sg_d3d11_PSSetSamplers(_sg.d3d11.ctx, 0, _SG_D3D11_MAX_STAGE_SMP_BINDINGS, d3d11_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) { + const bool use_instanced_draw = (num_instances > 1) || (_sg.d3d11.use_instanced_draw); + if (_sg.d3d11.use_indexed_draw) { + if (use_instanced_draw) { + _sg_d3d11_DrawIndexedInstanced(_sg.d3d11.ctx, (UINT)num_elements, (UINT)num_instances, (UINT)base_element, 0, 0); + _sg_stats_add(d3d11.draw.num_draw_indexed_instanced, 1); + } else { + _sg_d3d11_DrawIndexed(_sg.d3d11.ctx, (UINT)num_elements, (UINT)base_element, 0); + _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, 0); + _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_faces = (img->cmn.type == SG_IMAGETYPE_CUBE) ? 6:1; + const int num_slices = (img->cmn.type == SG_IMAGETYPE_ARRAY) ? img->cmn.num_slices:1; + 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 face_index = 0; face_index < num_faces; face_index++) { + 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_MAX_MIPMAPS * SG_MAX_TEXTUREARRAY_LAYERS)); + 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* subimg_data = &(data->subimage[face_index][mip_index]); + const size_t slice_size = subimg_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*)subimg_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->attachments_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.state_cache, sizeof(_sg.mtl.state_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_image_bindings = 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; + #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; + #endif + _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_faces = (img->cmn.type == SG_IMAGETYPE_CUBE) ? 6:1; + const int num_slices = (img->cmn.type == SG_IMAGETYPE_ARRAY) ? img->cmn.num_slices : 1; + for (int face_index = 0; face_index < num_faces; face_index++) { + for (int mip_index = 0; mip_index < img->cmn.num_mipmaps; mip_index++) { + SOKOL_ASSERT(data->subimage[face_index][mip_index].ptr); + SOKOL_ASSERT(data->subimage[face_index][mip_index].size > 0); + const uint8_t* data_ptr = (const uint8_t*)data->subimage[face_index][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 mtl_slice_index = (img->cmn.type == SG_IMAGETYPE_CUBE) ? face_index : slice_index; + const int slice_offset = slice_index * bytes_per_slice; + SOKOL_ASSERT((slice_offset + bytes_per_slice) <= (int)data->subimage[face_index][mip_index].size); + [mtl_tex replaceRegion:region + mipmapLevel:(NSUInteger)mip_index + slice:(NSUInteger)mtl_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; + + MTLTextureUsage mtl_tex_usage = MTLTextureUsageShaderRead; + if (img->cmn.usage.render_attachment) { + mtl_tex_usage |= MTLTextureUsageRenderTarget; + } else if (img->cmn.usage.storage_attachment) { + mtl_tex_usage |= MTLTextureUsageShaderWrite; + } + mtl_desc.usage = mtl_tex_usage; + + MTLResourceOptions mtl_res_options = 0; + if (img->cmn.usage.render_attachment || img->cmn.usage.storage_attachment) { + 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.subimage[0][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++) { + if (desc->uniform_blocks[i].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_IMAGE_BINDSLOTS; i++) { + if (desc->images[i].msl_texture_n >= _SG_MTL_MAX_STAGE_TEXTURE_BINDINGS) { + _SG_ERROR(METAL_IMAGE_MSL_TEXTURE_SLOT_OUT_OF_RANGE); + return false; + } + } + for (size_t i = 0; i < SG_MAX_SAMPLER_BINDSLOTS; i++) { + if (desc->samplers[i].msl_sampler_n >= _SG_MTL_MAX_STAGE_SAMPLER_BINDINGS) { + _SG_ERROR(METAL_SAMPLER_MSL_SAMPLER_SLOT_OUT_OF_RANGE); + return false; + } + } + for (size_t i = 0; i < SG_MAX_STORAGEBUFFER_BINDSLOTS; i++) { + if (desc->storage_buffers[i].msl_buffer_n >= _SG_MTL_MAX_STAGE_UB_SBUF_BINDINGS) { + _SG_ERROR(METAL_STORAGEBUFFER_MSL_BUFFER_SLOT_OUT_OF_RANGE); + return false; + } + } + for (size_t i = 0; i < SG_MAX_STORAGE_ATTACHMENTS; i++) { + if (desc->storage_images[i].msl_texture_n >= _SG_MTL_MAX_STAGE_TEXTURE_BINDINGS) { + _SG_ERROR(METAL_STORAGEIMAGE_MSL_TEXTURE_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_IMAGE_BINDSLOTS; i++) { + shd->mtl.img_texture_n[i] = desc->images[i].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; + } + for (size_t i = 0; i < SG_MAX_STORAGEBUFFER_BINDSLOTS; i++) { + shd->mtl.sbuf_buffer_n[i] = desc->storage_buffers[i].msl_buffer_n; + } + for (size_t i = 0; i < SG_MAX_STORAGE_ATTACHMENTS; i++) { + shd->mtl.simg_texture_n[i] = desc->storage_images[i].msl_texture_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_STORAGEBUFFER_BINDSLOTS; i++) { + const sg_shader_stage stage = shd->cmn.storage_buffers[i].stage; + SOKOL_ASSERT((stage != SG_SHADERSTAGE_VERTEX) && (stage != SG_SHADERSTAGE_FRAGMENT)); + if ((stage == SG_SHADERSTAGE_COMPUTE) && shd->cmn.storage_buffers[i].readonly) { + const NSUInteger mtl_slot = shd->mtl.sbuf_buffer_n[i]; + 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; + if (SG_VERTEXSTEP_PER_INSTANCE == l_state->step_func) { + // NOTE: not actually used in _sg_mtl_draw() + pip->cmn.use_instanced_draw = true; + } + } + } + + // 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 read-only buffers (vertex buffers and read-only storage buffers) + 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_STORAGEBUFFER_BINDSLOTS; i++) { + const NSUInteger mtl_slot = shd->mtl.sbuf_buffer_n[i]; + const sg_shader_stage stage = shd->cmn.storage_buffers[i].stage; + SOKOL_ASSERT(stage != SG_SHADERSTAGE_COMPUTE); + if (stage == SG_SHADERSTAGE_VERTEX) { + SOKOL_ASSERT(shd->cmn.storage_buffers[i].readonly); + rp_desc.vertexBuffers[mtl_slot].mutability = MTLMutabilityImmutable; + } else if (stage == SG_SHADERSTAGE_FRAGMENT) { + SOKOL_ASSERT(shd->cmn.storage_buffers[i].readonly); + 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_attachments(_sg_attachments_t* atts, const sg_attachments_desc* desc) { + SOKOL_ASSERT(atts && desc); + _SOKOL_UNUSED(desc); + + // create texture views for storage attachments + for (int i = 0; i < SG_MAX_STORAGE_ATTACHMENTS; i++) { + const _sg_image_t* stg_img = _sg_image_ref_ptr_or_null(&atts->cmn.storages[i].image); + if (stg_img) { + id mtl_tex_view = [_sg_mtl_id(stg_img->mtl.tex[0]) + newTextureViewWithPixelFormat: _sg_mtl_pixel_format(stg_img->cmn.pixel_format) + textureType: _sg_mtl_texture_type(stg_img->cmn.type, false) + levels: NSMakeRange((NSUInteger)atts->cmn.storages[i].mip_level, 1) + slices: NSMakeRange((NSUInteger)atts->cmn.storages[i].slice, 1)]; + atts->mtl.storage_views[i] = _sg_mtl_add_resource(mtl_tex_view); + } + } + return SG_RESOURCESTATE_VALID; +} + +_SOKOL_PRIVATE void _sg_mtl_discard_attachments(_sg_attachments_t* atts) { + SOKOL_ASSERT(atts); + _SOKOL_UNUSED(atts); + for (int i = 0; i < SG_MAX_STORAGE_ATTACHMENTS; i++) { + // it's valid to call _sg_mtl_release_resource with a null handle + _sg_mtl_release_resource(_sg.frame_index, atts->mtl.storage_views[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) { + SOKOL_ASSERT(pass); + 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); + const _sg_attachments_t* atts = _sg_attachments_ref_ptr_or_null(&_sg.cur_pass.atts); + if (atts) { + // setup pass descriptor for offscreen rendering + for (NSUInteger i = 0; i < (NSUInteger)atts->cmn.num_colors; i++) { + const _sg_attachment_common_t* cmn_color_att = &atts->cmn.colors[i]; + const _sg_image_t* color_att_img = _sg_image_ref_ptr(&cmn_color_att->image); + const _sg_attachment_common_t* cmn_resolve_att = &atts->cmn.resolves[i]; + const _sg_image_t* resolve_att_img = _sg_image_ref_ptr_or_null(&cmn_resolve_att->image); + SOKOL_ASSERT(color_att_img->slot.state == SG_RESOURCESTATE_VALID); + SOKOL_ASSERT(color_att_img->mtl.tex[color_att_img->cmn.active_slot] != _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_att_img != 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_att_img->mtl.tex[color_att_img->cmn.active_slot]); + pass_desc.colorAttachments[i].level = (NSUInteger)cmn_color_att->mip_level; + switch (color_att_img->cmn.type) { + case SG_IMAGETYPE_CUBE: + case SG_IMAGETYPE_ARRAY: + pass_desc.colorAttachments[i].slice = (NSUInteger)cmn_color_att->slice; + break; + case SG_IMAGETYPE_3D: + pass_desc.colorAttachments[i].depthPlane = (NSUInteger)cmn_color_att->slice; + break; + default: break; + } + if (resolve_att_img) { + SOKOL_ASSERT(resolve_att_img->slot.state == SG_RESOURCESTATE_VALID); + SOKOL_ASSERT(resolve_att_img->mtl.tex[resolve_att_img->cmn.active_slot] != _SG_MTL_INVALID_SLOT_INDEX); + pass_desc.colorAttachments[i].resolveTexture = _sg_mtl_id(resolve_att_img->mtl.tex[resolve_att_img->cmn.active_slot]); + pass_desc.colorAttachments[i].resolveLevel = (NSUInteger)cmn_resolve_att->mip_level; + switch (resolve_att_img->cmn.type) { + case SG_IMAGETYPE_CUBE: + case SG_IMAGETYPE_ARRAY: + pass_desc.colorAttachments[i].resolveSlice = (NSUInteger)cmn_resolve_att->slice; + break; + case SG_IMAGETYPE_3D: + pass_desc.colorAttachments[i].resolveDepthPlane = (NSUInteger)cmn_resolve_att->slice; + break; + default: break; + } + } + } + const _sg_image_t* ds_att_img = _sg_image_ref_ptr_or_null(&atts->cmn.depth_stencil.image); + if (0 != ds_att_img) { + SOKOL_ASSERT(ds_att_img->slot.state == SG_RESOURCESTATE_VALID); + SOKOL_ASSERT(ds_att_img->mtl.tex[ds_att_img->cmn.active_slot] != _SG_MTL_INVALID_SLOT_INDEX); + pass_desc.depthAttachment.texture = _sg_mtl_id(ds_att_img->mtl.tex[ds_att_img->cmn.active_slot]); + 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; + const _sg_attachment_common_t* cmn_ds_att = &atts->cmn.depth_stencil; + switch (ds_att_img->cmn.type) { + case SG_IMAGETYPE_CUBE: + case SG_IMAGETYPE_ARRAY: + pass_desc.depthAttachment.slice = (NSUInteger)cmn_ds_att->slice; + break; + case SG_IMAGETYPE_3D: + pass_desc.depthAttachment.resolveDepthPlane = (NSUInteger)cmn_ds_att->slice; + break; + default: break; + } + if (_sg_is_depth_stencil_format(ds_att_img->cmn.pixel_format)) { + pass_desc.stencilAttachment.texture = _sg_mtl_id(ds_att_img->mtl.tex[ds_att_img->cmn.active_slot]); + 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_att_img->cmn.type) { + case SG_IMAGETYPE_CUBE: + case SG_IMAGETYPE_ARRAY: + pass_desc.stencilAttachment.slice = (NSUInteger)cmn_ds_att->slice; + break; + case SG_IMAGETYPE_3D: + pass_desc.stencilAttachment.resolveDepthPlane = (NSUInteger)cmn_ds_att->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) { + SOKOL_ASSERT(pass); + 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); + } + + // 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(void) { + 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; + + // synchronize any managed resources written by the GPU + // NOTE: storage attachment images are currently not managed and are not eligible for syncing + #if defined(_SG_TARGET_MACOS) + if (_sg_mtl_resource_options_storage_mode_managed_or_shared() == MTLResourceStorageModeManaged) { + const bool needs_sync = _sg.compute.readwrite_sbufs.cur > 0; + if (needs_sync) { + id blit_cmd_encoder = [_sg.mtl.cmd_buffer blitCommandEncoder]; + for (uint32_t i = 0; i < _sg.compute.readwrite_sbufs.cur; i++) { + _sg_buffer_t* sbuf = _sg_lookup_buffer(_sg.compute.readwrite_sbufs.items[i]); + if (sbuf) { + [blit_cmd_encoder synchronizeResource:_sg_mtl_id(sbuf->mtl.buf[sbuf->cmn.active_slot])]; + } + } + [blit_cmd_encoder endEncoding]; + } + } + #endif + } + // 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.height > 0); + MTLViewport vp; + vp.originX = (double) x; + vp.originY = (double) (origin_top_left ? y : (_sg.cur_pass.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.width > 0); + SOKOL_ASSERT(_sg.cur_pass.height > 0); + // clip against framebuffer rect + const _sg_recti_t clip = _sg_clipi(x, y, w, h, _sg.cur_pass.width, _sg.cur_pass.height); + MTLScissorRect r; + r.x = (NSUInteger)clip.x; + r.y = (NSUInteger) (origin_top_left ? clip.y : (_sg.cur_pass.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_eql(&_sg.mtl.state_cache.cur_pip, &pip->slot)) { + _sg.mtl.state_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)]; + // apply storage image bindings for writing + const _sg_attachments_t* atts = _sg_attachments_ref_ptr_or_null(&_sg.cur_pass.atts); + if (atts) { + const _sg_shader_t* shd = _sg_shader_ref_ptr(&pip->cmn.shader); + for (size_t i = 0; i < SG_MAX_STORAGE_ATTACHMENTS; i++) { + const sg_shader_stage stage = shd->cmn.storage_images[i].stage; + if (stage != SG_SHADERSTAGE_COMPUTE) { + continue; + } + const NSUInteger mtl_slot = shd->mtl.simg_texture_n[i]; + SOKOL_ASSERT(mtl_slot < _SG_MTL_MAX_STAGE_TEXTURE_BINDINGS); + const uint64_t cache_key = ((uint64_t)atts->cmn.storages[i].image.sref.id) << 32; + SOKOL_ASSERT(cache_key != 0); + if (_sg.mtl.state_cache.cur_cs_image_ids[mtl_slot] != cache_key) { + _sg.mtl.state_cache.cur_cs_image_ids[mtl_slot] = cache_key; + [_sg.mtl.compute_cmd_encoder setTexture:_sg_mtl_id(atts->mtl.storage_views[i]) atIndex:mtl_slot]; + _sg_stats_add(metal.bindings.num_set_compute_texture, 1); + } + } + } + } 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 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.state_cache.cur_ibuf = _sg_buffer_ref(bnd->ib); + if (bnd->ib) { + SOKOL_ASSERT(bnd->pip->cmn.index_type != SG_INDEXTYPE_NONE); + } else { + SOKOL_ASSERT(bnd->pip->cmn.index_type == SG_INDEXTYPE_NONE); + } + _sg.mtl.state_cache.cur_ibuf_offset = bnd->ib_offset; + // 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 vb_offset = bnd->vb_offsets[i]; + const bool ref_eql = _sg_sref_eql(&_sg.mtl.state_cache.cur_vsbufs[mtl_slot], &vb->slot); + if (!ref_eql || (_sg.mtl.state_cache.cur_vs_buffer_offsets[mtl_slot] != vb_offset)) { + _sg.mtl.state_cache.cur_vs_buffer_offsets[mtl_slot] = vb_offset; + if (!ref_eql) { + // vertex buffer has changed + _sg.mtl.state_cache.cur_vsbufs[mtl_slot] = _sg_sref(&vb->slot); + SOKOL_ASSERT(vb->mtl.buf[vb->cmn.active_slot] != _SG_MTL_INVALID_SLOT_INDEX); + [_sg.mtl.render_cmd_encoder setVertexBuffer:_sg_mtl_id(vb->mtl.buf[vb->cmn.active_slot]) + offset:(NSUInteger)vb_offset + atIndex:mtl_slot]; + } else { + // only vertex buffer offset has changed + [_sg.mtl.render_cmd_encoder setVertexBufferOffset:(NSUInteger)vb_offset atIndex:mtl_slot]; + } + _sg_stats_add(metal.bindings.num_set_vertex_buffer, 1); + } + } + } + + // apply image bindings + for (size_t i = 0; i < SG_MAX_IMAGE_BINDSLOTS; i++) { + const _sg_image_t* img = bnd->imgs[i]; + if (img == 0) { + continue; + } + SOKOL_ASSERT(img->mtl.tex[img->cmn.active_slot] != _SG_MTL_INVALID_SLOT_INDEX); + const sg_shader_stage stage = shd->cmn.images[i].stage; + SOKOL_ASSERT((stage == SG_SHADERSTAGE_VERTEX) || (stage == SG_SHADERSTAGE_FRAGMENT) || (stage == SG_SHADERSTAGE_COMPUTE)); + const NSUInteger mtl_slot = shd->mtl.img_texture_n[i]; + SOKOL_ASSERT(mtl_slot < _SG_MTL_MAX_STAGE_TEXTURE_BINDINGS); + if (stage == SG_SHADERSTAGE_VERTEX) { + SOKOL_ASSERT(nil != _sg.mtl.render_cmd_encoder); + if (!_sg_sref_eql(&_sg.mtl.state_cache.cur_vsimgs[mtl_slot], &img->slot)) { + _sg.mtl.state_cache.cur_vsimgs[mtl_slot] = _sg_sref(&img->slot); + [_sg.mtl.render_cmd_encoder setVertexTexture:_sg_mtl_id(img->mtl.tex[img->cmn.active_slot]) atIndex:mtl_slot]; + _sg_stats_add(metal.bindings.num_set_vertex_texture, 1); + } + } else if (stage == SG_SHADERSTAGE_FRAGMENT) { + SOKOL_ASSERT(nil != _sg.mtl.render_cmd_encoder); + if (!_sg_sref_eql(&_sg.mtl.state_cache.cur_fsimgs[mtl_slot], &img->slot)) { + _sg.mtl.state_cache.cur_fsimgs[mtl_slot] = _sg_sref(&img->slot); + [_sg.mtl.render_cmd_encoder setFragmentTexture:_sg_mtl_id(img->mtl.tex[img->cmn.active_slot]) atIndex:mtl_slot]; + _sg_stats_add(metal.bindings.num_set_fragment_texture, 1); + } + } else if (stage == SG_SHADERSTAGE_COMPUTE) { + SOKOL_ASSERT(nil != _sg.mtl.compute_cmd_encoder); + if (_sg.mtl.state_cache.cur_cs_image_ids[mtl_slot] != img->slot.id) { + _sg.mtl.state_cache.cur_cs_image_ids[mtl_slot] = img->slot.id; + [_sg.mtl.compute_cmd_encoder setTexture:_sg_mtl_id(img->mtl.tex[img->cmn.active_slot]) atIndex:mtl_slot]; + _sg_stats_add(metal.bindings.num_set_compute_texture, 1); + } + } + } + + // 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_eql(&_sg.mtl.state_cache.cur_vssmps[mtl_slot], &smp->slot)) { + _sg.mtl.state_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 if (stage == SG_SHADERSTAGE_FRAGMENT) { + SOKOL_ASSERT(nil != _sg.mtl.render_cmd_encoder); + if (!_sg_sref_eql(&_sg.mtl.state_cache.cur_fssmps[mtl_slot], &smp->slot)) { + _sg.mtl.state_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 if (stage == SG_SHADERSTAGE_COMPUTE) { + SOKOL_ASSERT(nil != _sg.mtl.compute_cmd_encoder); + if (!_sg_sref_eql(&_sg.mtl.state_cache.cur_cssmps[mtl_slot], &smp->slot)) { + _sg.mtl.state_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); + } + } + } + + // apply storage buffer bindings + for (size_t i = 0; i < SG_MAX_STORAGEBUFFER_BINDSLOTS; i++) { + const _sg_buffer_t* sbuf = bnd->sbufs[i]; + if (sbuf == 0) { + continue; + } + SOKOL_ASSERT(sbuf->mtl.buf[sbuf->cmn.active_slot] != _SG_MTL_INVALID_SLOT_INDEX); + const sg_shader_stage stage = shd->cmn.storage_buffers[i].stage; + SOKOL_ASSERT((stage == SG_SHADERSTAGE_VERTEX) || (stage == SG_SHADERSTAGE_FRAGMENT) || (stage == SG_SHADERSTAGE_COMPUTE)); + const NSUInteger mtl_slot = shd->mtl.sbuf_buffer_n[i]; + SOKOL_ASSERT(mtl_slot < _SG_MTL_MAX_STAGE_UB_SBUF_BINDINGS); + if (stage == SG_SHADERSTAGE_VERTEX) { + SOKOL_ASSERT(nil != _sg.mtl.render_cmd_encoder); + if (!_sg_sref_eql(&_sg.mtl.state_cache.cur_vsbufs[mtl_slot], &sbuf->slot)) { + _sg.mtl.state_cache.cur_vsbufs[mtl_slot] = _sg_sref(&sbuf->slot); + [_sg.mtl.render_cmd_encoder setVertexBuffer:_sg_mtl_id(sbuf->mtl.buf[sbuf->cmn.active_slot]) offset:0 atIndex:mtl_slot]; + _sg_stats_add(metal.bindings.num_set_vertex_buffer, 1); + } + } else if (stage == SG_SHADERSTAGE_FRAGMENT) { + SOKOL_ASSERT(nil != _sg.mtl.render_cmd_encoder); + if (!_sg_sref_eql(&_sg.mtl.state_cache.cur_fsbufs[mtl_slot], &sbuf->slot)) { + _sg.mtl.state_cache.cur_fsbufs[mtl_slot] = _sg_sref(&sbuf->slot); + [_sg.mtl.render_cmd_encoder setFragmentBuffer:_sg_mtl_id(sbuf->mtl.buf[sbuf->cmn.active_slot]) offset:0 atIndex:mtl_slot]; + _sg_stats_add(metal.bindings.num_set_fragment_buffer, 1); + } + } else if (stage == SG_SHADERSTAGE_COMPUTE) { + SOKOL_ASSERT(nil != _sg.mtl.compute_cmd_encoder); + if (!_sg_sref_eql(&_sg.mtl.state_cache.cur_csbufs[mtl_slot], &sbuf->slot)) { + _sg.mtl.state_cache.cur_csbufs[mtl_slot] = _sg_sref(&sbuf->slot); + [_sg.mtl.compute_cmd_encoder setBuffer:_sg_mtl_id(sbuf->mtl.buf[sbuf->cmn.active_slot]) offset:0 atIndex:mtl_slot]; + _sg_stats_add(metal.bindings.num_set_compute_buffer, 1); + } + } + } + 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) { + 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_INDEXTYPE_NONE != pip->cmn.index_type) { + // indexed rendering + const _sg_buffer_t* ib = _sg_buffer_ref_ptr(&_sg.mtl.state_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.state_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]; + } 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]; + } +} + +_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) + +#define WGPUSType_ShaderModuleWGSLDescriptor WGPUSType_ShaderSourceWGSL +_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 |= WGPUBufferUsage_Vertex; + } + if (usg->index_buffer) { + res |= WGPUBufferUsage_Index; + } + if (usg->storage_buffer) { + res |= WGPUBufferUsage_Storage; + } + if (!usg->immutable) { + res |= 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 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(uint8_t m) { + // FIXME: change to WGPUColorWriteMask once Emscripten and Dawn webgpu.h agree + int res = 0; + if (0 != (m & SG_COLORMASK_R)) { + res |= WGPUColorWriteMask_Red; + } + if (0 != (m & SG_COLORMASK_G)) { + res |= WGPUColorWriteMask_Green; + } + if (0 != (m & SG_COLORMASK_B)) { + res |= WGPUColorWriteMask_Blue; + } + if (0 != (m & SG_COLORMASK_A)) { + res |= 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_image_bindings = 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; + + // 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); + case 2: h2 ^= (uint32_t)(((unsigned char*)data)[1] << 8); + case 1: h2 ^= ((unsigned char*)data)[0]; + 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 & 7; + const uint64_t ccccc = uninit_count & ((1 << 21) - 1); + const uint64_t iiiiiiii = id; + return (bb << 56) | (t << 53) | (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_image_item(uint8_t wgpu_binding, const _sg_slot_t* slot) { + return _sg_wgpu_bindgroups_cache_item(_SG_WGPU_BINDGROUPSCACHEITEMTYPE_IMAGE, 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 uint64_t _sg_wgpu_bindgroups_cache_sbuf_item(uint8_t wgpu_binding, const _sg_slot_t* slot) { + return _sg_wgpu_bindgroups_cache_item(_SG_WGPU_BINDGROUPSCACHEITEMTYPE_STORAGEBUFFER, 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_IMAGE_BINDSLOTS; i++) { + if (shd->cmn.images[i].stage == SG_SHADERSTAGE_NONE) { + continue; + } + SOKOL_ASSERT(bnd->imgs[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.img_grp1_bnd_n[i]; + key->items[item_idx] = _sg_wgpu_bindgroups_cache_image_item(wgpu_binding, &bnd->imgs[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_IMAGE_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); + } + for (size_t i = 0; i < SG_MAX_STORAGEBUFFER_BINDSLOTS; i++) { + if (shd->cmn.storage_buffers[i].stage == SG_SHADERSTAGE_NONE) { + continue; + } + SOKOL_ASSERT(bnd->sbufs[i]); + const size_t item_idx = i + 1 + SG_MAX_IMAGE_BINDSLOTS + SG_MAX_SAMPLER_BINDSLOTS; + SOKOL_ASSERT(item_idx < _SG_WGPU_BINDGROUPSCACHEKEY_NUM_ITEMS); + SOKOL_ASSERT(0 == key->items[item_idx]); + const uint8_t wgpu_binding = shd->wgpu.sbuf_grp1_bnd_n[i]; + key->items[item_idx] = _sg_wgpu_bindgroups_cache_sbuf_item(wgpu_binding, &bnd->sbufs[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_img_smp_sbuf; + SOKOL_ASSERT(bgl); + WGPUBindGroupEntry bg_entries[_SG_WGPU_MAX_IMG_SMP_SBUF_BINDGROUP_ENTRIES]; + _sg_clear(&bg_entries, sizeof(bg_entries)); + size_t bgl_index = 0; + for (size_t i = 0; i < SG_MAX_IMAGE_BINDSLOTS; i++) { + if (shd->cmn.images[i].stage == SG_SHADERSTAGE_NONE) { + continue; + } + SOKOL_ASSERT(bnd->imgs[i]); + SOKOL_ASSERT(bgl_index < _SG_WGPU_MAX_IMG_SMP_SBUF_BINDGROUP_ENTRIES); + WGPUBindGroupEntry* bg_entry = &bg_entries[bgl_index]; + bg_entry->binding = shd->wgpu.img_grp1_bnd_n[i]; + bg_entry->textureView = bnd->imgs[i]->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_IMG_SMP_SBUF_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; + } + for (size_t i = 0; i < SG_MAX_STORAGEBUFFER_BINDSLOTS; i++) { + if (shd->cmn.storage_buffers[i].stage == SG_SHADERSTAGE_NONE) { + continue; + } + SOKOL_ASSERT(bnd->sbufs[i]); + SOKOL_ASSERT(bgl_index < _SG_WGPU_MAX_IMG_SMP_SBUF_BINDGROUP_ENTRIES); + WGPUBindGroupEntry* bg_entry = &bg_entries[bgl_index]; + bg_entry->binding = shd->wgpu.sbuf_grp1_bnd_n[i]; + bg_entry->buffer = bnd->sbufs[i]->wgpu.buf; + bg_entry->size = (uint64_t) bnd->sbufs[i]->cmn.size; + 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 >= 0) && (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 >= 0) && (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(size_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_IMG_SMP_SBUF_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_IMG_SMP_SBUF_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); + + // create initial per-frame command encoder + 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); +} + +_SOKOL_PRIVATE void _sg_wgpu_discard_backend(void) { + SOKOL_ASSERT(_sg.wgpu.valid); + SOKOL_ASSERT(_sg.wgpu.cmd_enc); + _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; + 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; + } + // NOTE: assume that WebGPU creates zero-initialized buffers + 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->cmn.usage.storage_buffer) { + _sg_wgpu_bindgroups_cache_invalidate(_SG_WGPU_BINDGROUPSCACHEITEMTYPE_STORAGEBUFFER, &buf->slot); + } + 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 }; + 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_src_ptr, 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)); + const int num_faces = (img->cmn.type == SG_IMAGETYPE_CUBE) ? 6 : 1; + for (int face_index = 0; face_index < num_faces; face_index++) { + for (int mip_index = 0; mip_index < img->cmn.num_mipmaps; mip_index++) { + wgpu_copy_tex.mipLevel = (uint32_t)mip_index; + wgpu_copy_tex.origin.z = (uint32_t)face_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; + switch (img->cmn.type) { + case SG_IMAGETYPE_CUBE: + mip_slices = 1; + break; + case SG_IMAGETYPE_3D: + mip_slices = _sg_miplevel_dim(img->cmn.num_slices, mip_index); + break; + default: + mip_slices = img->cmn.num_slices; + break; + } + 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.offset = 0; + 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->subimage[face_index][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); + img->wgpu.view = (WGPUTextureView)desc->wgpu_texture_view; + if (img->wgpu.view) { + wgpuTextureViewAddRef(img->wgpu.view); + } + } 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.render_attachment) { + wgpu_tex_desc.usage |= WGPUTextureUsage_RenderAttachment; + } + if (desc->usage.storage_attachment) { + 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.subimage[0][0].ptr) { + _sg_wgpu_copy_image_data(img, img->wgpu.tex, &desc->data); + } + 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.dimension = _sg_wgpu_texture_view_dimension(img->cmn.type); + wgpu_texview_desc.mipLevelCount = (uint32_t)img->cmn.num_mipmaps; + if (img->cmn.type == SG_IMAGETYPE_CUBE) { + wgpu_texview_desc.arrayLayerCount = 6; + } else if (img->cmn.type == SG_IMAGETYPE_ARRAY) { + wgpu_texview_desc.arrayLayerCount = (uint32_t)img->cmn.num_slices; + } else { + wgpu_texview_desc.arrayLayerCount = 1; + } + 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; + } + img->wgpu.view = wgpuTextureCreateView(img->wgpu.tex, &wgpu_texview_desc); + if (0 == img->wgpu.view) { + _SG_ERROR(WGPU_CREATE_TEXTURE_VIEW_FAILED); + return SG_RESOURCESTATE_FAILED; + } + } + return SG_RESOURCESTATE_VALID; +} + +_SOKOL_PRIVATE void _sg_wgpu_discard_image(_sg_image_t* img) { + SOKOL_ASSERT(img); + _sg_wgpu_bindgroups_cache_invalidate(_SG_WGPU_BINDGROUPSCACHEITEMTYPE_IMAGE, &img->slot); + if (img->wgpu.view) { + wgpuTextureViewRelease(img->wgpu.view); + img->wgpu.view = 0; + } + 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); + + WGPUShaderModuleWGSLDescriptor wgpu_shdmod_wgsl_desc; + _sg_clear(&wgpu_shdmod_wgsl_desc, sizeof(wgpu_shdmod_wgsl_desc)); + wgpu_shdmod_wgsl_desc.chain.sType = WGPUSType_ShaderSourceWGSL; + wgpu_shdmod_wgsl_desc.code = _sg_wgpu_stringview(func->source); + + WGPUShaderModuleDescriptor wgpu_shdmod_desc; + _sg_clear(&wgpu_shdmod_desc, sizeof(wgpu_shdmod_desc)); + wgpu_shdmod_desc.nextInChain = &wgpu_shdmod_wgsl_desc.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++) { + if (desc->uniform_blocks[i].wgsl_group0_binding_n >= _SG_WGPU_MAX_UB_BINDGROUP_BIND_SLOTS) { + _SG_ERROR(WGPU_UNIFORMBLOCK_WGSL_GROUP0_BINDING_OUT_OF_RANGE); + return false; + } + } + for (size_t i = 0; i < SG_MAX_STORAGEBUFFER_BINDSLOTS; i++) { + if (desc->storage_buffers[i].wgsl_group1_binding_n >= _SG_WGPU_MAX_IMG_SMP_SBUF_BIND_SLOTS) { + _SG_ERROR(WGPU_STORAGEBUFFER_WGSL_GROUP1_BINDING_OUT_OF_RANGE); + return false; + } + } + for (size_t i = 0; i < SG_MAX_IMAGE_BINDSLOTS; i++) { + if (desc->images[i].wgsl_group1_binding_n >= _SG_WGPU_MAX_IMG_SMP_SBUF_BIND_SLOTS) { + _SG_ERROR(WGPU_IMAGE_WGSL_GROUP1_BINDING_OUT_OF_RANGE); + return false; + } + } + for (size_t i = 0; i < SG_MAX_SAMPLER_BINDSLOTS; i++) { + if (desc->samplers[i].wgsl_group1_binding_n >= _SG_WGPU_MAX_IMG_SMP_SBUF_BIND_SLOTS) { + _SG_ERROR(WGPU_SAMPLER_WGSL_GROUP1_BINDING_OUT_OF_RANGE); + return false; + } + } + for (size_t i = 0; i < SG_MAX_STORAGE_ATTACHMENTS; i++) { + if (desc->storage_images[i].wgsl_group2_binding_n >= _SG_WGPU_MAX_SIMG_BIND_SLOTS) { + _SG_ERROR(WGPU_STORAGEIMAGE_WGSL_GROUP2_BINDING_OUT_OF_RANGE); + } + } + 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_img_smp_sbuf == 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_IMG_SMP_SBUF_BINDGROUP_ENTRIES); + SOKOL_ASSERT(_SG_WGPU_MAX_SIMG_BINDGROUP_ENTRIES <= _SG_WGPU_MAX_IMG_SMP_SBUF_BINDGROUP_ENTRIES); + WGPUBindGroupLayoutEntry bgl_entries[_SG_WGPU_MAX_IMG_SMP_SBUF_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_IMG_SMP_SBUF_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 = i; + dynoffset_map[i].wgpu_slot = 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 = 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 images, samplers and storage buffers + _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_IMAGE_BINDSLOTS; i++) { + if (shd->cmn.images[i].stage == SG_SHADERSTAGE_NONE) { + continue; + } + const bool msaa = shd->cmn.images[i].multisampled; + shd->wgpu.img_grp1_bnd_n[i] = desc->images[i].wgsl_group1_binding_n; + WGPUBindGroupLayoutEntry* bgl_entry = &bgl_entries[bgl_index]; + bgl_entry->binding = shd->wgpu.img_grp1_bnd_n[i]; + bgl_entry->visibility = _sg_wgpu_shader_stage(shd->cmn.images[i].stage); + bgl_entry->texture.viewDimension = _sg_wgpu_texture_view_dimension(shd->cmn.images[i].image_type); + bgl_entry->texture.sampleType = _sg_wgpu_texture_sample_type(shd->cmn.images[i].sample_type, msaa); + bgl_entry->texture.multisampled = msaa; + 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; + } + for (size_t i = 0; i < SG_MAX_STORAGEBUFFER_BINDSLOTS; i++) { + if (shd->cmn.storage_buffers[i].stage == SG_SHADERSTAGE_NONE) { + continue; + } + shd->wgpu.sbuf_grp1_bnd_n[i] = desc->storage_buffers[i].wgsl_group1_binding_n; + WGPUBindGroupLayoutEntry* bgl_entry = &bgl_entries[bgl_index]; + bgl_entry->binding = shd->wgpu.sbuf_grp1_bnd_n[i]; + bgl_entry->visibility = _sg_wgpu_shader_stage(shd->cmn.storage_buffers[i].stage); + if (shd->cmn.storage_buffers[i].readonly) { + bgl_entry->buffer.type = WGPUBufferBindingType_ReadOnlyStorage; + } else { + bgl_entry->buffer.type = WGPUBufferBindingType_Storage; + } + bgl_index += 1; + } + bgl_desc.entryCount = bgl_index; + bgl_desc.entries = bgl_entries; + shd->wgpu.bgl_img_smp_sbuf = wgpuDeviceCreateBindGroupLayout(_sg.wgpu.dev, &bgl_desc); + if (shd->wgpu.bgl_img_smp_sbuf == 0) { + _SG_ERROR(WGPU_SHADER_CREATE_BINDGROUP_LAYOUT_FAILED); + return SG_RESOURCESTATE_FAILED; + } + + // create optional bindgroup layout for storage images (separate bindgroup because + // those are not applied in sg_apply_bindings() but are defined as pass attachments) + _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_STORAGE_ATTACHMENTS; i++) { + if (shd->cmn.storage_images[i].stage == SG_SHADERSTAGE_NONE) { + continue; + } + shd->wgpu.simg_grp2_bnd_n[i] = desc->storage_images[i].wgsl_group2_binding_n; + WGPUBindGroupLayoutEntry* bgl_entry = &bgl_entries[bgl_index]; + bgl_entry->binding = shd->wgpu.simg_grp2_bnd_n[i]; + bgl_entry->visibility = _sg_wgpu_shader_stage(shd->cmn.storage_images[i].stage); + if (shd->cmn.storage_images[i].writeonly) { + bgl_entry->storageTexture.access = WGPUStorageTextureAccess_WriteOnly; + } else { + bgl_entry->storageTexture.access = WGPUStorageTextureAccess_ReadWrite; + } + bgl_entry->storageTexture.format = _sg_wgpu_textureformat(desc->storage_images[i].access_format); + bgl_entry->texture.viewDimension = _sg_wgpu_texture_view_dimension(shd->cmn.storage_images[i].image_type); + bgl_index += 1; + } + if (bgl_index > 0) { + bgl_desc.entryCount = bgl_index; + bgl_desc.entries = bgl_entries; + shd->wgpu.bgl_simg = wgpuDeviceCreateBindGroupLayout(_sg.wgpu.dev, &bgl_desc); + if (shd->wgpu.bgl_simg == 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_img_smp_sbuf) { + wgpuBindGroupLayoutRelease(shd->wgpu.bgl_img_smp_sbuf); + shd->wgpu.bgl_img_smp_sbuf = 0; + } + if (shd->wgpu.bgl_simg) { + wgpuBindGroupLayoutRelease(shd->wgpu.bgl_simg); + shd->wgpu.bgl_simg = 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_img_smp_sbuf); + + 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_IMG_SMP_SBUF_BINDGROUP_INDEX] = shd->wgpu.bgl_img_smp_sbuf; + if (shd->wgpu.bgl_simg) { + wgpu_bgl[_SG_WGPU_SIMG_BINDGROUP_INDEX] = shd->wgpu.bgl_simg; + num_bgls += 1; + } + 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_attachments(_sg_attachments_t* atts, const sg_attachments_desc* desc) { + SOKOL_ASSERT(atts && desc); + + // create texture views + for (int i = 0; i < atts->cmn.num_colors; i++) { + const sg_attachment_desc* color_desc = &desc->colors[i]; + _sg_image_t* clr_img = _sg_image_ref_ptr(&atts->cmn.colors[i].image); + SOKOL_ASSERT(_sg_is_valid_attachment_color_format(clr_img->cmn.pixel_format)); + SOKOL_ASSERT(clr_img->wgpu.tex); + + WGPUTextureViewDescriptor wgpu_color_view_desc; + _sg_clear(&wgpu_color_view_desc, sizeof(wgpu_color_view_desc)); + wgpu_color_view_desc.baseMipLevel = (uint32_t) color_desc->mip_level; + wgpu_color_view_desc.mipLevelCount = 1; + wgpu_color_view_desc.baseArrayLayer = (uint32_t) color_desc->slice; + wgpu_color_view_desc.arrayLayerCount = 1; + atts->wgpu.colors[i].view = wgpuTextureCreateView(clr_img->wgpu.tex, &wgpu_color_view_desc); + if (0 == atts->wgpu.colors[i].view) { + _SG_ERROR(WGPU_ATTACHMENTS_CREATE_TEXTURE_VIEW_FAILED); + return SG_RESOURCESTATE_FAILED; + } + + const sg_attachment_desc* resolve_desc = &desc->resolves[i]; + if (resolve_desc->image.id != SG_INVALID_ID) { + _sg_image_t* rsv_img = _sg_image_ref_ptr(&atts->cmn.resolves[i].image); + SOKOL_ASSERT(clr_img->cmn.pixel_format == rsv_img->cmn.pixel_format); + SOKOL_ASSERT(rsv_img->wgpu.tex); + + WGPUTextureViewDescriptor wgpu_resolve_view_desc; + _sg_clear(&wgpu_resolve_view_desc, sizeof(wgpu_resolve_view_desc)); + wgpu_resolve_view_desc.baseMipLevel = (uint32_t) resolve_desc->mip_level; + wgpu_resolve_view_desc.mipLevelCount = 1; + wgpu_resolve_view_desc.baseArrayLayer = (uint32_t) resolve_desc->slice; + wgpu_resolve_view_desc.arrayLayerCount = 1; + atts->wgpu.resolves[i].view = wgpuTextureCreateView(rsv_img->wgpu.tex, &wgpu_resolve_view_desc); + if (0 == atts->wgpu.resolves[i].view) { + _SG_ERROR(WGPU_ATTACHMENTS_CREATE_TEXTURE_VIEW_FAILED); + return SG_RESOURCESTATE_FAILED; + } + } + } + const sg_attachment_desc* ds_desc = &desc->depth_stencil; + if (ds_desc->image.id != SG_INVALID_ID) { + _sg_image_t* ds_img = _sg_image_ref_ptr(&atts->cmn.depth_stencil.image); + SOKOL_ASSERT(_sg_is_valid_attachment_depth_format(ds_img->cmn.pixel_format)); + SOKOL_ASSERT(ds_img->wgpu.tex); + + WGPUTextureViewDescriptor wgpu_ds_view_desc; + _sg_clear(&wgpu_ds_view_desc, sizeof(wgpu_ds_view_desc)); + wgpu_ds_view_desc.baseMipLevel = (uint32_t) ds_desc->mip_level; + wgpu_ds_view_desc.mipLevelCount = 1; + wgpu_ds_view_desc.baseArrayLayer = (uint32_t) ds_desc->slice; + wgpu_ds_view_desc.arrayLayerCount = 1; + atts->wgpu.depth_stencil.view = wgpuTextureCreateView(ds_img->wgpu.tex, &wgpu_ds_view_desc); + if (0 == atts->wgpu.depth_stencil.view) { + _SG_ERROR(WGPU_ATTACHMENTS_CREATE_TEXTURE_VIEW_FAILED); + return SG_RESOURCESTATE_FAILED; + } + } + for (int i = 0; i < SG_MAX_STORAGE_ATTACHMENTS; i++) { + const sg_attachment_desc* storage_desc = &desc->storages[i]; + if (storage_desc->image.id != SG_INVALID_ID) { + _sg_image_t* stg_img = _sg_image_ref_ptr(&atts->cmn.storages[i].image); + SOKOL_ASSERT(_sg_is_valid_attachment_storage_format(stg_img->cmn.pixel_format)); + + WGPUTextureViewDescriptor wgpu_storage_view_desc; + _sg_clear(&wgpu_storage_view_desc, sizeof(wgpu_storage_view_desc)); + wgpu_storage_view_desc.baseMipLevel = (uint32_t) storage_desc->mip_level; + wgpu_storage_view_desc.mipLevelCount = 1; + wgpu_storage_view_desc.baseArrayLayer = (uint32_t) storage_desc->slice; + wgpu_storage_view_desc.arrayLayerCount = 1; + if (_sg_is_depth_or_depth_stencil_format(stg_img->cmn.pixel_format)) { + wgpu_storage_view_desc.aspect = WGPUTextureAspect_DepthOnly; + } else { + wgpu_storage_view_desc.aspect = WGPUTextureAspect_All; + } + atts->wgpu.storages[i].view = wgpuTextureCreateView(stg_img->wgpu.tex, &wgpu_storage_view_desc); + if (0 == atts->wgpu.storages[i].view) { + _SG_ERROR(WGPU_ATTACHMENTS_CREATE_TEXTURE_VIEW_FAILED); + return SG_RESOURCESTATE_FAILED; + } + } + } + return SG_RESOURCESTATE_VALID; +} + +_SOKOL_PRIVATE void _sg_wgpu_discard_attachments(_sg_attachments_t* atts) { + SOKOL_ASSERT(atts); + for (int i = 0; i < atts->cmn.num_colors; i++) { + if (atts->wgpu.colors[i].view) { + wgpuTextureViewRelease(atts->wgpu.colors[i].view); + atts->wgpu.colors[i].view = 0; + } + if (atts->wgpu.resolves[i].view) { + wgpuTextureViewRelease(atts->wgpu.resolves[i].view); + atts->wgpu.resolves[i].view = 0; + } + } + if (atts->wgpu.depth_stencil.view) { + wgpuTextureViewRelease(atts->wgpu.depth_stencil.view); + atts->wgpu.depth_stencil.view = 0; + } + for (int i = 0; i < SG_MAX_STORAGE_ATTACHMENTS; i++) { + if (atts->wgpu.storages[i].view) { + wgpuTextureViewRelease(atts->wgpu.storages[i].view); + atts->wgpu.storages[i].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_IMG_SMP_SBUF_BINDGROUP_INDEX, _sg.wgpu.empty_bind_group, 0, 0); + wgpuComputePassEncoderSetBindGroup(_sg.wgpu.cpass_enc, _SG_WGPU_SIMG_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_swapchain* swapchain = &pass->swapchain; + const sg_pass_action* action = &pass->action; + const _sg_attachments_t* atts = _sg_attachments_ref_ptr_or_null(&_sg.cur_pass.atts); + + 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) { + SOKOL_ASSERT(atts->slot.state == SG_RESOURCESTATE_VALID); + for (int i = 0; i < atts->cmn.num_colors; i++) { + _sg_wgpu_init_color_att(&wgpu_color_att[i], &action->colors[i], atts->wgpu.colors[i].view, atts->wgpu.resolves[i].view); + } + wgpu_pass_desc.colorAttachmentCount = (size_t)atts->cmn.num_colors; + wgpu_pass_desc.colorAttachments = &wgpu_color_att[0]; + const _sg_image_t* ds_img = _sg_image_ref_ptr_or_null(&atts->cmn.depth_stencil.image); + if (ds_img) { + _sg_wgpu_init_ds_att(&wgpu_ds_att, action, ds_img->cmn.pixel_format, atts->wgpu.depth_stencil.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_IMG_SMP_SBUF_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) { + SOKOL_ASSERT(pass); + SOKOL_ASSERT(_sg.wgpu.dev); + SOKOL_ASSERT(_sg.wgpu.cmd_enc); + SOKOL_ASSERT(0 == _sg.wgpu.rpass_enc); + SOKOL_ASSERT(0 == _sg.wgpu.cpass_enc); + + _sg_wgpu_bindings_cache_clear(); + if (pass->compute) { + _sg_wgpu_begin_compute_pass(pass); + } else { + _sg_wgpu_begin_render_pass(pass); + } +} + +_SOKOL_PRIVATE void _sg_wgpu_end_pass(void) { + 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); + + // create a new render-command-encoder for next frame + 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_PRIVATE void _sg_wgpu_apply_viewport(int x, int y, int w, int h, bool origin_top_left) { + SOKOL_ASSERT(_sg.wgpu.rpass_enc); + // FIXME FIXME FIXME: CLIPPING THE VIEWPORT HERE IS WRONG!!! + // (but currently required because WebGPU insists that the viewport rectangle must be + // fully contained inside the framebuffer, but this doesn't make any sense, and also + // isn't required by the backend APIs) + const _sg_recti_t clip = _sg_clipi(x, y, w, h, _sg.cur_pass.width, _sg.cur_pass.height); + float xf = (float) clip.x; + float yf = (float) (origin_top_left ? clip.y : (_sg.cur_pass.height - (clip.y + clip.h))); + float wf = (float) clip.w; + float hf = (float) clip.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.width, _sg.cur_pass.height); + uint32_t sx = (uint32_t) clip.x; + uint32_t sy = (uint32_t) (origin_top_left ? clip.y : (_sg.cur_pass.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); + + // adhoc-create a storage attachment bindgroup without going through the bindgroups cache + // FIXME: the 'resource view update' will get rid of this special case because then storage images + // will be regular resource bindings + if (shd->wgpu.bgl_simg) { + _sg_stats_add(wgpu.bindings.num_create_bindgroup, 1); + SOKOL_ASSERT(shd); + WGPUBindGroupLayout bgl = shd->wgpu.bgl_simg; + WGPUBindGroupEntry bg_entries[_SG_WGPU_MAX_SIMG_BINDGROUP_ENTRIES]; + _sg_clear(&bg_entries, sizeof(bg_entries)); + size_t bgl_index = 0; + for (size_t i = 0; i < SG_MAX_STORAGE_ATTACHMENTS; i++) { + if (shd->cmn.storage_images[i].stage == SG_SHADERSTAGE_NONE) { + continue; + } + _sg_attachments_t* atts = _sg_attachments_ref_ptr(&_sg.cur_pass.atts); + SOKOL_ASSERT(atts->wgpu.storages[i].view); + WGPUBindGroupEntry* bg_entry = &bg_entries[bgl_index]; + bg_entry->binding = shd->wgpu.simg_grp2_bnd_n[i]; + bg_entry->textureView = atts->wgpu.storages[i].view; + bgl_index++; + } + SOKOL_ASSERT(bgl_index > 0); + WGPUBindGroupDescriptor bg_desc; + _sg_clear(&bg_desc, sizeof(bg_desc)); + bg_desc.layout = bgl; + bg_desc.entryCount = bgl_index; + bg_desc.entries = bg_entries; + WGPUBindGroup bg = wgpuDeviceCreateBindGroup(_sg.wgpu.dev, &bg_desc); + if (bg) { + wgpuComputePassEncoderSetBindGroup(_sg.wgpu.cpass_enc, _SG_WGPU_SIMG_BINDGROUP_INDEX, bg, 0, 0); + wgpuBindGroupRelease(bg); + } else { + _SG_ERROR(WGPU_CREATEBINDGROUP_FAILED); + } + } else { + // no storage attachment bindings, clear bindgroup slot + wgpuComputePassEncoderSetBindGroup(_sg.wgpu.cpass_enc, _SG_WGPU_SIMG_BINDGROUP_INDEX, _sg.wgpu.empty_bind_group, 0, 0); + } + } else { + SOKOL_ASSERT(!_sg.cur_pass.is_compute); + SOKOL_ASSERT(pip->wgpu.rpip); + SOKOL_ASSERT(_sg.wgpu.rpass_enc); + _sg.wgpu.use_indexed_draw = (pip->cmn.index_type != SG_INDEXTYPE_NONE); + 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_IMG_SMP_SBUF_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) { + SOKOL_ASSERT(_sg.wgpu.rpass_enc); + const _sg_pipeline_t* pip = _sg_pipeline_ref_ptr(&_sg.cur_pip); + if (SG_INDEXTYPE_NONE != pip->cmn.index_type) { + wgpuRenderPassEncoderDrawIndexed(_sg.wgpu.rpass_enc, (uint32_t)num_elements, (uint32_t)num_instances, (uint32_t)base_element, 0, 0); + } else { + wgpuRenderPassEncoderDraw(_sg.wgpu.rpass_enc, (uint32_t)num_elements, (uint32_t)num_instances, (uint32_t)base_element, 0); + } +} + +_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_attachments(_sg_attachments_t* atts, const sg_attachments_desc* desc) { + #if defined(_SOKOL_ANY_GL) + return _sg_gl_create_attachments(atts, desc); + #elif defined(SOKOL_METAL) + return _sg_mtl_create_attachments(atts, desc); + #elif defined(SOKOL_D3D11) + return _sg_d3d11_create_attachments(atts, desc); + #elif defined(SOKOL_WGPU) + return _sg_wgpu_create_attachments(atts, desc); + #elif defined(SOKOL_DUMMY_BACKEND) + return _sg_dummy_create_attachments(atts, desc); + #else + #error("INVALID BACKEND"); + #endif +} + +static inline void _sg_discard_attachments(_sg_attachments_t* atts) { + #if defined(_SOKOL_ANY_GL) + _sg_gl_discard_attachments(atts); + #elif defined(SOKOL_METAL) + _sg_mtl_discard_attachments(atts); + #elif defined(SOKOL_D3D11) + _sg_d3d11_discard_attachments(atts); + #elif defined(SOKOL_WGPU) + return _sg_wgpu_discard_attachments(atts); + #elif defined(SOKOL_DUMMY_BACKEND) + _sg_dummy_discard_attachments(atts); + #else + #error("INVALID BACKEND"); + #endif +} + +static inline void _sg_begin_pass(const sg_pass* pass) { + #if defined(_SOKOL_ANY_GL) + _sg_gl_begin_pass(pass); + #elif defined(SOKOL_METAL) + _sg_mtl_begin_pass(pass); + #elif defined(SOKOL_D3D11) + _sg_d3d11_begin_pass(pass); + #elif defined(SOKOL_WGPU) + _sg_wgpu_begin_pass(pass); + #elif defined(SOKOL_DUMMY_BACKEND) + _sg_dummy_begin_pass(pass); + #else + #error("INVALID BACKEND"); + #endif +} + +static inline void _sg_end_pass(void) { + #if defined(_SOKOL_ANY_GL) + _sg_gl_end_pass(); + #elif defined(SOKOL_METAL) + _sg_mtl_end_pass(); + #elif defined(SOKOL_D3D11) + _sg_d3d11_end_pass(); + #elif defined(SOKOL_WGPU) + _sg_wgpu_end_pass(); + #elif defined(SOKOL_DUMMY_BACKEND) + _sg_dummy_end_pass(); + #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) { + #if defined(_SOKOL_ANY_GL) + _sg_gl_draw(base_element, num_elements, num_instances); + #elif defined(SOKOL_METAL) + _sg_mtl_draw(base_element, num_elements, num_instances); + #elif defined(SOKOL_D3D11) + _sg_d3d11_draw(base_element, num_elements, num_instances); + #elif defined(SOKOL_WGPU) + _sg_wgpu_draw(base_element, num_elements, num_instances); + #elif defined(SOKOL_DUMMY_BACKEND) + _sg_dummy_draw(base_element, num_elements, num_instances); + #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_faces, 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_faces); + _SOKOL_UNUSED(num_mips); + _SOKOL_UNUSED(num_slices); + #else + for (int face_index = 0; face_index < num_faces; face_index++) { + for (int mip_index = 0; mip_index < num_mips; mip_index++) { + const bool has_data = data->subimage[face_index][mip_index].ptr != 0; + const bool has_size = data->subimage[face_index][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->subimage[face_index][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; + } + 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(desc->usage.immutable, desc->usage.dynamic_update, desc->usage.stream_update), VALIDATE_IMAGEDESC_IMMUTABLE_DYNAMIC_STREAM); + if (desc->usage.render_attachment || desc->usage.storage_attachment) { + _SG_VALIDATE(_sg_one(desc->usage.render_attachment, desc->usage.storage_attachment, false), VALIDATE_IMAGEDESC_RENDER_VS_STORAGE_ATTACHMENT); + } + _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 sg_image_usage* usage = &desc->usage; + 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 (usage->render_attachment || usage->storage_attachment) { + SOKOL_ASSERT(((int)fmt >= 0) && ((int)fmt < _SG_PIXELFORMAT_NUM)); + _SG_VALIDATE(usage->immutable, VALIDATE_IMAGEDESC_ATTACHMENT_EXPECT_IMMUTABLE); + _SG_VALIDATE(desc->data.subimage[0][0].ptr==0, VALIDATE_IMAGEDESC_ATTACHMENT_EXPECT_NO_DATA); + if (usage->render_attachment) { + _SG_VALIDATE(_sg.formats[fmt].render, VALIDATE_IMAGEDESC_RENDERATTACHMENT_PIXELFORMAT); + if (desc->sample_count > 1) { + _SG_VALIDATE(_sg.formats[fmt].msaa, VALIDATE_IMAGEDESC_RENDERATTACHMENT_NO_MSAA_SUPPORT); + _SG_VALIDATE(desc->num_mipmaps == 1, VALIDATE_IMAGEDESC_RENDERATTACHMENT_MSAA_NUM_MIPMAPS); + _SG_VALIDATE(desc->type != SG_IMAGETYPE_ARRAY, VALIDATE_IMAGEDESC_RENDERATTACHMENT_MSAA_ARRAY_IMAGE); + _SG_VALIDATE(desc->type != SG_IMAGETYPE_3D, VALIDATE_IMAGEDESC_RENDERATTACHMENT_MSAA_3D_IMAGE); + _SG_VALIDATE(desc->type != SG_IMAGETYPE_CUBE, VALIDATE_IMAGEDESC_RENDERATTACHMENT_MSAA_CUBE_IMAGE); + } + } else if (usage->storage_attachment) { + _SG_VALIDATE(_sg_is_valid_attachment_storage_format(fmt), VALIDATE_IMAGEDESC_STORAGEATTACHMENT_PIXELFORMAT); + // D3D11 doesn't allow multisampled UAVs (see: https://github.com/gpuweb/gpuweb/issues/513) + _SG_VALIDATE(desc->sample_count == 1, VALIDATE_IMAGEDESC_STORAGEATTACHMENT_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(usage->immutable, VALIDATE_IMAGEDESC_COMPRESSED_IMMUTABLE); + } + if (!injected && usage->immutable) { + // image desc must have valid data + _sg_validate_image_data(&desc->data, + desc->pixel_format, + desc->width, + desc->height, + (desc->type == SG_IMAGETYPE_CUBE) ? 6 : 1, + desc->num_mipmaps, + desc->num_slices); + } else { + // image desc must not have data + for (int face_index = 0; face_index < SG_CUBEFACE_NUM; face_index++) { + for (int mip_index = 0; mip_index < SG_MAX_MIPMAPS; mip_index++) { + const bool no_data = 0 == desc->data.subimage[face_index][mip_index].ptr; + const bool no_size = 0 == desc->data.subimage[face_index][mip_index].size; + if (injected) { + _SG_VALIDATE(no_data && no_size, VALIDATE_IMAGEDESC_INJECTED_NO_DATA); + } + if (!usage->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) { + _SG_VALIDATE(desc->mtl_threads_per_threadgroup.x > 0, VALIDATE_SHADERDESC_METAL_THREADS_PER_THREADGROUP); + _SG_VALIDATE(desc->mtl_threads_per_threadgroup.y > 0, VALIDATE_SHADERDESC_METAL_THREADS_PER_THREADGROUP); + _SG_VALIDATE(desc->mtl_threads_per_threadgroup.z > 0, VALIDATE_SHADERDESC_METAL_THREADS_PER_THREADGROUP); + } + #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(); + _sg_u128_t wgsl_group2_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(ub_desc->msl_buffer_n < _SG_MTL_MAX_STAGE_UB_BINDINGS, VALIDATE_SHADERDESC_UNIFORMBLOCK_METAL_BUFFER_SLOT_OUT_OF_RANGE); + _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(ub_desc->hlsl_register_b_n < _SG_D3D11_MAX_STAGE_UB_BINDINGS, VALIDATE_SHADERDESC_UNIFORMBLOCK_HLSL_REGISTER_B_OUT_OF_RANGE); + _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(ub_desc->wgsl_group0_binding_n < _SG_WGPU_MAX_UB_BINDGROUP_BIND_SLOTS, VALIDATE_SHADERDESC_UNIFORMBLOCK_WGSL_GROUP0_BINDING_OUT_OF_RANGE); + _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 + } + + for (size_t sbuf_idx = 0; sbuf_idx < SG_MAX_STORAGEBUFFER_BINDSLOTS; sbuf_idx++) { + const sg_shader_storage_buffer* sbuf_desc = &desc->storage_buffers[sbuf_idx]; + if (sbuf_desc->stage == SG_SHADERSTAGE_NONE) { + continue; + } + #if defined(SOKOL_METAL) + _SG_VALIDATE((sbuf_desc->msl_buffer_n >= _SG_MTL_MAX_STAGE_UB_BINDINGS) && (sbuf_desc->msl_buffer_n < _SG_MTL_MAX_STAGE_UB_SBUF_BINDINGS), VALIDATE_SHADERDESC_STORAGEBUFFER_METAL_BUFFER_SLOT_OUT_OF_RANGE); + _SG_VALIDATE(_sg_validate_slot_bits(msl_buf_bits, sbuf_desc->stage, sbuf_desc->msl_buffer_n), VALIDATE_SHADERDESC_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(sbuf_desc->hlsl_register_t_n < _SG_D3D11_MAX_STAGE_SRV_BINDINGS, VALIDATE_SHADERDESC_STORAGEBUFFER_HLSL_REGISTER_T_OUT_OF_RANGE); + _SG_VALIDATE(_sg_validate_slot_bits(hlsl_srv_bits, sbuf_desc->stage, sbuf_desc->hlsl_register_t_n), VALIDATE_SHADERDESC_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(sbuf_desc->hlsl_register_u_n < _SG_D3D11_MAX_STAGE_UAV_BINDINGS, VALIDATE_SHADERDESC_STORAGEBUFFER_HLSL_REGISTER_U_OUT_OF_RANGE); + _SG_VALIDATE(_sg_validate_slot_bits(hlsl_uav_bits, sbuf_desc->stage, sbuf_desc->hlsl_register_u_n), VALIDATE_SHADERDESC_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(sbuf_desc->glsl_binding_n < _SG_GL_MAX_SBUF_BINDINGS, VALIDATE_SHADERDESC_STORAGEBUFFER_GLSL_BINDING_OUT_OF_RANGE); + _SG_VALIDATE(_sg_validate_slot_bits(glsl_sbuf_bnd_bits, SG_SHADERSTAGE_NONE, sbuf_desc->glsl_binding_n), VALIDATE_SHADERDESC_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(sbuf_desc->wgsl_group1_binding_n < _SG_WGPU_MAX_IMG_SMP_SBUF_BIND_SLOTS, VALIDATE_SHADERDESC_STORAGEBUFFER_WGSL_GROUP1_BINDING_OUT_OF_RANGE); + _SG_VALIDATE(_sg_validate_slot_bits(wgsl_group1_bits, SG_SHADERSTAGE_NONE, sbuf_desc->wgsl_group1_binding_n), VALIDATE_SHADERDESC_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); + #endif + } + + for (size_t simg_idx = 0; simg_idx < SG_MAX_STORAGE_ATTACHMENTS; simg_idx++) { + const sg_shader_storage_image* simg_desc = &desc->storage_images[simg_idx]; + if (simg_desc->stage == SG_SHADERSTAGE_NONE) { + continue; + } + _SG_VALIDATE(simg_desc->stage == SG_SHADERSTAGE_COMPUTE, VALIDATE_SHADERDESC_STORAGEIMAGE_EXPECT_COMPUTE_STAGE); + #if defined(SOKOL_METAL) + _SG_VALIDATE(simg_desc->msl_texture_n < _SG_MTL_MAX_STAGE_TEXTURE_BINDINGS, VALIDATE_SHADERDESC_STORAGEIMAGE_METAL_TEXTURE_SLOT_OUT_OF_RANGE); + _SG_VALIDATE(_sg_validate_slot_bits(msl_tex_bits, simg_desc->stage, simg_desc->msl_texture_n), VALIDATE_SHADERDESC_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(simg_desc->hlsl_register_u_n < _SG_D3D11_MAX_STAGE_UAV_BINDINGS, VALIDATE_SHADERDESC_STORAGEIMAGE_HLSL_REGISTER_U_OUT_OF_RANGE); + _SG_VALIDATE(_sg_validate_slot_bits(hlsl_uav_bits, simg_desc->stage, simg_desc->hlsl_register_u_n), VALIDATE_SHADERDESC_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(simg_desc->glsl_binding_n < _SG_GL_MAX_SIMG_BINDINGS, VALIDATE_SHADERDESC_STORAGEIMAGE_GLSL_BINDING_OUT_OF_RANGE); + _SG_VALIDATE(_sg_validate_slot_bits(glsl_simg_bnd_bits, SG_SHADERSTAGE_NONE, simg_desc->glsl_binding_n), VALIDATE_SHADERDESC_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(simg_desc->wgsl_group2_binding_n < _SG_WGPU_MAX_SIMG_BIND_SLOTS, VALIDATE_SHADERDESC_STORAGEIMAGE_WGSL_GROUP2_BINDING_OUT_OF_RANGE); + _SG_VALIDATE(_sg_validate_slot_bits(wgsl_group2_bits, SG_SHADERSTAGE_NONE, simg_desc->wgsl_group2_binding_n), VALIDATE_SHADERDESC_STORAGEIMAGE_WGSL_GROUP2_BINDING_COLLISION); + wgsl_group2_bits = _sg_validate_set_slot_bit(wgsl_group2_bits, SG_SHADERSTAGE_NONE, simg_desc->wgsl_group2_binding_n); + #endif + } + + uint32_t img_slot_mask = 0; + for (size_t img_idx = 0; img_idx < SG_MAX_IMAGE_BINDSLOTS; img_idx++) { + const sg_shader_image* img_desc = &desc->images[img_idx]; + if (img_desc->stage == SG_SHADERSTAGE_NONE) { + continue; + } + img_slot_mask |= (1 << img_idx); + #if defined(SOKOL_METAL) + _SG_VALIDATE(img_desc->msl_texture_n < _SG_MTL_MAX_STAGE_TEXTURE_BINDINGS, VALIDATE_SHADERDESC_IMAGE_METAL_TEXTURE_SLOT_OUT_OF_RANGE); + _SG_VALIDATE(_sg_validate_slot_bits(msl_tex_bits, img_desc->stage, img_desc->msl_texture_n), VALIDATE_SHADERDESC_IMAGE_METAL_TEXTURE_SLOT_COLLISION); + msl_tex_bits = _sg_validate_set_slot_bit(msl_tex_bits, img_desc->stage, img_desc->msl_texture_n); + #elif defined(SOKOL_D3D11) + _SG_VALIDATE(img_desc->hlsl_register_t_n < _SG_D3D11_MAX_STAGE_SRV_BINDINGS, VALIDATE_SHADERDESC_IMAGE_HLSL_REGISTER_T_OUT_OF_RANGE); + _SG_VALIDATE(_sg_validate_slot_bits(hlsl_srv_bits, img_desc->stage, img_desc->hlsl_register_t_n), VALIDATE_SHADERDESC_IMAGE_HLSL_REGISTER_T_COLLISION); + hlsl_srv_bits = _sg_validate_set_slot_bit(hlsl_srv_bits, img_desc->stage, img_desc->hlsl_register_t_n); + #elif defined(SOKOL_WGPU) + _SG_VALIDATE(img_desc->wgsl_group1_binding_n < _SG_WGPU_MAX_IMG_SMP_SBUF_BIND_SLOTS, VALIDATE_SHADERDESC_IMAGE_WGSL_GROUP1_BINDING_OUT_OF_RANGE); + _SG_VALIDATE(_sg_validate_slot_bits(wgsl_group1_bits, SG_SHADERSTAGE_NONE, img_desc->wgsl_group1_binding_n), VALIDATE_SHADERDESC_IMAGE_WGSL_GROUP1_BINDING_COLLISION); + wgsl_group1_bits = _sg_validate_set_slot_bit(wgsl_group1_bits, SG_SHADERSTAGE_NONE, img_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(smp_desc->msl_sampler_n < _SG_MTL_MAX_STAGE_SAMPLER_BINDINGS, VALIDATE_SHADERDESC_SAMPLER_METAL_SAMPLER_SLOT_OUT_OF_RANGE); + _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(smp_desc->hlsl_register_s_n < _SG_D3D11_MAX_STAGE_SMP_BINDINGS, VALIDATE_SHADERDESC_SAMPLER_HLSL_REGISTER_S_OUT_OF_RANGE); + _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(smp_desc->wgsl_group1_binding_n < _SG_WGPU_MAX_IMG_SMP_SBUF_BIND_SLOTS, VALIDATE_SHADERDESC_SAMPLER_WGSL_GROUP1_BINDING_OUT_OF_RANGE); + _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_img_slot_mask = 0; + uint32_t ref_smp_slot_mask = 0; + for (size_t img_smp_idx = 0; img_smp_idx < SG_MAX_IMAGE_SAMPLER_PAIRS; img_smp_idx++) { + const sg_shader_image_sampler_pair* img_smp_desc = &desc->image_sampler_pairs[img_smp_idx]; + if (img_smp_desc->stage == SG_SHADERSTAGE_NONE) { + continue; + } + #if defined(_SOKOL_ANY_GL) + _SG_VALIDATE(img_smp_desc->glsl_name != 0, VALIDATE_SHADERDESC_IMAGE_SAMPLER_PAIR_GLSL_NAME); + #endif + const bool img_slot_in_range = img_smp_desc->image_slot < SG_MAX_IMAGE_BINDSLOTS; + const bool smp_slot_in_range = img_smp_desc->sampler_slot < SG_MAX_SAMPLER_BINDSLOTS; + _SG_VALIDATE(img_slot_in_range, VALIDATE_SHADERDESC_IMAGE_SAMPLER_PAIR_IMAGE_SLOT_OUT_OF_RANGE); + _SG_VALIDATE(smp_slot_in_range, VALIDATE_SHADERDESC_IMAGE_SAMPLER_PAIR_SAMPLER_SLOT_OUT_OF_RANGE); + if (img_slot_in_range && smp_slot_in_range) { + ref_img_slot_mask |= 1 << img_smp_desc->image_slot; + ref_smp_slot_mask |= 1 << img_smp_desc->sampler_slot; + const sg_shader_image* img_desc = &desc->images[img_smp_desc->image_slot]; + const sg_shader_sampler* smp_desc = &desc->samplers[img_smp_desc->sampler_slot]; + _SG_VALIDATE(img_desc->stage == img_smp_desc->stage, VALIDATE_SHADERDESC_IMAGE_SAMPLER_PAIR_IMAGE_STAGE_MISMATCH); + _SG_VALIDATE(smp_desc->stage == img_smp_desc->stage, VALIDATE_SHADERDESC_IMAGE_SAMPLER_PAIR_SAMPLER_STAGE_MISMATCH); + const bool needs_nonfiltering = (img_desc->sample_type == SG_IMAGESAMPLETYPE_UINT) + || (img_desc->sample_type == SG_IMAGESAMPLETYPE_SINT) + || (img_desc->sample_type == SG_IMAGESAMPLETYPE_UNFILTERABLE_FLOAT); + const bool needs_comparison = img_desc->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(img_slot_mask == ref_img_slot_mask, VALIDATE_SHADERDESC_IMAGE_NOT_REFERENCED_BY_IMAGE_SAMPLER_PAIRS); + _SG_VALIDATE(smp_slot_mask == ref_smp_slot_mask, VALIDATE_SHADERDESC_SAMPLER_NOT_REFERENCED_BY_IMAGE_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_STORAGEBUFFER_BINDSLOTS; i++) { + if (shd->cmn.storage_buffers[i].stage != SG_SHADERSTAGE_NONE) { + _SG_VALIDATE(shd->cmn.storage_buffers[i].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_attachments_desc(const sg_attachments_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_ATTACHMENTSDESC_CANARY); + _SG_VALIDATE(desc->_end_canary == 0, VALIDATE_ATTACHMENTSDESC_CANARY); + + // check color attachments + bool has_color_atts = false; + bool has_depth_stencil_att = 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++) { + const sg_attachment_desc* att = &desc->colors[att_index]; + if (att->image.id == SG_INVALID_ID) { + atts_cont = false; + continue; + } + has_color_atts = true; + _SG_VALIDATE(atts_cont, VALIDATE_ATTACHMENTSDESC_NO_CONT_COLOR_ATTS); + const _sg_image_t* img = _sg_lookup_image(att->image.id); + _SG_VALIDATE(img, VALIDATE_ATTACHMENTSDESC_COLOR_IMAGE); + if (img) { + _SG_VALIDATE(img->slot.state == SG_RESOURCESTATE_VALID, VALIDATE_ATTACHMENTSDESC_COLOR_IMAGE); + _SG_VALIDATE(img->cmn.usage.render_attachment, VALIDATE_ATTACHMENTSDESC_COLOR_IMAGE_NO_RENDERATTACHMENT); + _SG_VALIDATE(att->mip_level < img->cmn.num_mipmaps, VALIDATE_ATTACHMENTSDESC_COLOR_MIPLEVEL); + if (img->cmn.type == SG_IMAGETYPE_CUBE) { + _SG_VALIDATE(att->slice < 6, VALIDATE_ATTACHMENTSDESC_COLOR_FACE); + } else if (img->cmn.type == SG_IMAGETYPE_ARRAY) { + _SG_VALIDATE(att->slice < img->cmn.num_slices, VALIDATE_ATTACHMENTSDESC_COLOR_LAYER); + } else if (img->cmn.type == SG_IMAGETYPE_3D) { + _SG_VALIDATE(att->slice < img->cmn.num_slices, VALIDATE_ATTACHMENTSDESC_COLOR_SLICE); + } + if (att_index == 0) { + color_width = _sg_miplevel_dim(img->cmn.width, att->mip_level); + color_height = _sg_miplevel_dim(img->cmn.height, att->mip_level); + color_sample_count = img->cmn.sample_count; + } else { + _SG_VALIDATE(color_width == _sg_miplevel_dim(img->cmn.width, att->mip_level), VALIDATE_ATTACHMENTSDESC_IMAGE_SIZES); + _SG_VALIDATE(color_height == _sg_miplevel_dim(img->cmn.height, att->mip_level), VALIDATE_ATTACHMENTSDESC_IMAGE_SIZES); + _SG_VALIDATE(color_sample_count == img->cmn.sample_count, VALIDATE_ATTACHMENTSDESC_IMAGE_SAMPLE_COUNTS); + } + _SG_VALIDATE(_sg_is_valid_attachment_color_format(img->cmn.pixel_format), VALIDATE_ATTACHMENTSDESC_COLOR_INV_PIXELFORMAT); + + // check resolve attachment + const sg_attachment_desc* res_att = &desc->resolves[att_index]; + if (res_att->image.id != SG_INVALID_ID) { + // associated color attachment must be MSAA + _SG_VALIDATE(img->cmn.sample_count > 1, VALIDATE_ATTACHMENTSDESC_RESOLVE_COLOR_IMAGE_MSAA); + const _sg_image_t* res_img = _sg_lookup_image(res_att->image.id); + _SG_VALIDATE(res_img, VALIDATE_ATTACHMENTSDESC_RESOLVE_IMAGE); + if (res_img) { + _SG_VALIDATE(res_img->slot.state == SG_RESOURCESTATE_VALID, VALIDATE_ATTACHMENTSDESC_RESOLVE_IMAGE); + _SG_VALIDATE(res_img->cmn.usage.render_attachment, VALIDATE_ATTACHMENTSDESC_RESOLVE_IMAGE_NO_RT); + _SG_VALIDATE(res_img->cmn.sample_count == 1, VALIDATE_ATTACHMENTSDESC_RESOLVE_SAMPLE_COUNT); + _SG_VALIDATE(res_att->mip_level < res_img->cmn.num_mipmaps, VALIDATE_ATTACHMENTSDESC_RESOLVE_MIPLEVEL); + if (res_img->cmn.type == SG_IMAGETYPE_CUBE) { + _SG_VALIDATE(res_att->slice < SG_CUBEFACE_NUM, VALIDATE_ATTACHMENTSDESC_RESOLVE_FACE); + } else if (res_img->cmn.type == SG_IMAGETYPE_ARRAY) { + _SG_VALIDATE(res_att->slice < res_img->cmn.num_slices, VALIDATE_ATTACHMENTSDESC_RESOLVE_LAYER); + } else if (res_img->cmn.type == SG_IMAGETYPE_3D) { + _SG_VALIDATE(res_att->slice < res_img->cmn.num_slices, VALIDATE_ATTACHMENTSDESC_RESOLVE_SLICE); + } + _SG_VALIDATE(img->cmn.pixel_format == res_img->cmn.pixel_format, VALIDATE_ATTACHMENTSDESC_RESOLVE_IMAGE_FORMAT); + _SG_VALIDATE(color_width == _sg_miplevel_dim(res_img->cmn.width, res_att->mip_level), VALIDATE_ATTACHMENTSDESC_RESOLVE_IMAGE_SIZES); + _SG_VALIDATE(color_height == _sg_miplevel_dim(res_img->cmn.height, res_att->mip_level), VALIDATE_ATTACHMENTSDESC_RESOLVE_IMAGE_SIZES); + } + } + } + } + // check depth stencil attachments + if (desc->depth_stencil.image.id != SG_INVALID_ID) { + has_depth_stencil_att = true; + const sg_attachment_desc* att = &desc->depth_stencil; + const _sg_image_t* img = _sg_lookup_image(att->image.id); + _SG_VALIDATE(img, VALIDATE_ATTACHMENTSDESC_DEPTH_IMAGE); + if (img) { + _SG_VALIDATE(img->slot.state == SG_RESOURCESTATE_VALID, VALIDATE_ATTACHMENTSDESC_DEPTH_IMAGE); + _SG_VALIDATE(att->mip_level < img->cmn.num_mipmaps, VALIDATE_ATTACHMENTSDESC_DEPTH_MIPLEVEL); + if (img->cmn.type == SG_IMAGETYPE_CUBE) { + _SG_VALIDATE(att->slice < 6, VALIDATE_ATTACHMENTSDESC_DEPTH_FACE); + } else if (img->cmn.type == SG_IMAGETYPE_ARRAY) { + _SG_VALIDATE(att->slice < img->cmn.num_slices, VALIDATE_ATTACHMENTSDESC_DEPTH_LAYER); + } else if (img->cmn.type == SG_IMAGETYPE_3D) { + // NOTE: this can't actually happen because of VALIDATE_IMAGEDESC_DEPTH_3D_IMAGE + _SG_VALIDATE(att->slice < img->cmn.num_slices, VALIDATE_ATTACHMENTSDESC_DEPTH_SLICE); + } + _SG_VALIDATE(img->cmn.usage.render_attachment, VALIDATE_ATTACHMENTSDESC_DEPTH_IMAGE_NO_RENDERATTACHMENT); + _SG_VALIDATE((color_width == -1) || (color_width == _sg_miplevel_dim(img->cmn.width, att->mip_level)), VALIDATE_ATTACHMENTSDESC_DEPTH_IMAGE_SIZES); + _SG_VALIDATE((color_height == -1) || (color_height == _sg_miplevel_dim(img->cmn.height, att->mip_level)), VALIDATE_ATTACHMENTSDESC_DEPTH_IMAGE_SIZES); + _SG_VALIDATE((color_sample_count == -1) || (color_sample_count == img->cmn.sample_count), VALIDATE_ATTACHMENTSDESC_DEPTH_IMAGE_SAMPLE_COUNT); + _SG_VALIDATE(_sg_is_valid_attachment_depth_format(img->cmn.pixel_format), VALIDATE_ATTACHMENTSDESC_DEPTH_INV_PIXELFORMAT); + } + } + } + + // check storage attachments (note: storage attachments don't need to continuous) + bool has_storage_atts = false; + { + for (int att_index = 0; att_index < SG_MAX_STORAGE_ATTACHMENTS; att_index++) { + const sg_attachment_desc* att = &desc->storages[att_index]; + if (att->image.id == SG_INVALID_ID) { + continue; + } + has_storage_atts = true; + const _sg_image_t* img = _sg_lookup_image(att->image.id); + _SG_VALIDATE(img, VALIDATE_ATTACHMENTSDESC_STORAGE_IMAGE); + if (img) { + _SG_VALIDATE(img->slot.state == SG_RESOURCESTATE_VALID, VALIDATE_ATTACHMENTSDESC_STORAGE_IMAGE); + _SG_VALIDATE(img->cmn.usage.storage_attachment, VALIDATE_ATTACHMENTSDESC_STORAGE_IMAGE_NO_STORAGEATTACHMENT); + _SG_VALIDATE(att->mip_level < img->cmn.num_mipmaps, VALIDATE_ATTACHMENTSDESC_STORAGE_MIPLEVEL); + if (img->cmn.type == SG_IMAGETYPE_CUBE) { + _SG_VALIDATE(att->slice < 6, VALIDATE_ATTACHMENTSDESC_STORAGE_FACE); + } else if (img->cmn.type == SG_IMAGETYPE_ARRAY) { + _SG_VALIDATE(att->slice < img->cmn.num_slices, VALIDATE_ATTACHMENTSDESC_STORAGE_LAYER); + } else if (img->cmn.type == SG_IMAGETYPE_3D) { + _SG_VALIDATE(att->slice < img->cmn.num_slices, VALIDATE_ATTACHMENTSDESC_STORAGE_SLICE); + } + _SG_VALIDATE(_sg_is_valid_attachment_storage_format(img->cmn.pixel_format), VALIDATE_ATTACHMENTSDESC_STORAGE_INV_PIXELFORMAT); + } + } + } + _SG_VALIDATE(has_color_atts || has_depth_stencil_att || has_storage_atts, VALIDATE_ATTACHMENTSDESC_NO_ATTACHMENTS); + if (has_color_atts || has_depth_stencil_att) { + _SG_VALIDATE(!has_storage_atts, VALIDATE_ATTACHMENTSDESC_RENDER_VS_STORAGE_ATTACHMENTS); + } + if (has_storage_atts) { + _SG_VALIDATE(!(has_color_atts || has_depth_stencil_att), VALIDATE_ATTACHMENTSDESC_RENDER_VS_STORAGE_ATTACHMENTS); + } + 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 && (pass->attachments.id == SG_INVALID_ID); + 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) { + // this is a compute pass with optional storage attachments + if (pass->attachments.id != SG_INVALID_ID) { + const _sg_attachments_t* atts = _sg_lookup_attachments(pass->attachments.id); + if (atts) { + _SG_VALIDATE(atts->slot.state == SG_RESOURCESTATE_VALID, VALIDATE_BEGINPASS_ATTACHMENTS_VALID); + _SG_VALIDATE(!atts->cmn.has_render_attachments, VALIDATE_BEGINPASS_COMPUTEPASS_STORAGE_ATTACHMENTS_ONLY); + for (int i = 0; i < SG_MAX_STORAGE_ATTACHMENTS; i++) { + const _sg_image_ref_t* img_ref = &atts->cmn.storages[i].image; + const bool img_null = _sg_image_ref_null(img_ref); + const bool img_alive = _sg_image_ref_alive(img_ref); + if (!img_null) { + _SG_VALIDATE(img_alive, VALIDATE_BEGINPASS_STORAGE_ATTACHMENT_IMAGE_ALIVE); + if (img_alive) { + _SG_VALIDATE(_sg_image_ref_ptr(img_ref)->slot.state == SG_RESOURCESTATE_VALID, VALIDATE_BEGINPASS_STORAGE_ATTACHMENT_IMAGE_VALID); + } + } + } + } else { + _SG_VALIDATE(atts != 0, VALIDATE_BEGINPASS_ATTACHMENTS_EXISTS); + } + } + } 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' + const _sg_attachments_t* atts = _sg_lookup_attachments(pass->attachments.id); + if (atts) { + _SG_VALIDATE(atts->slot.state == SG_RESOURCESTATE_VALID, VALIDATE_BEGINPASS_ATTACHMENTS_VALID); + _SG_VALIDATE(!atts->cmn.has_storage_attachments, VALIDATE_BEGINPASS_RENDERPASS_RENDER_ATTACHMENTS_ONLY); + for (int i = 0; i < SG_MAX_COLOR_ATTACHMENTS; i++) { + { + const _sg_image_ref_t* img_ref = &atts->cmn.colors[i].image; + const bool img_null = _sg_image_ref_null(img_ref); + const bool img_alive = _sg_image_ref_alive(img_ref); + if (!img_null) { + _SG_VALIDATE(img_alive, VALIDATE_BEGINPASS_COLOR_ATTACHMENT_IMAGE_ALIVE); + if (img_alive) { + _SG_VALIDATE(_sg_image_ref_ptr(img_ref)->slot.state == SG_RESOURCESTATE_VALID, VALIDATE_BEGINPASS_COLOR_ATTACHMENT_IMAGE_VALID); + } + } + } + { + const _sg_image_ref_t* img_ref = &atts->cmn.resolves[i].image; + const bool img_null = _sg_image_ref_null(img_ref); + const bool img_alive = _sg_image_ref_alive(img_ref); + if (!img_null) { + _SG_VALIDATE(img_alive, VALIDATE_BEGINPASS_RESOLVE_ATTACHMENT_IMAGE_ALIVE); + if (img_alive) { + _SG_VALIDATE(_sg_image_ref_ptr(img_ref)->slot.state == SG_RESOURCESTATE_VALID, VALIDATE_BEGINPASS_RESOLVE_ATTACHMENT_IMAGE_VALID); + } + } + } + } + { + const _sg_image_ref_t* img_ref = &atts->cmn.depth_stencil.image; + const bool img_null = _sg_image_ref_null(img_ref); + const bool img_alive = _sg_image_ref_alive(img_ref); + if (!img_null) { + _SG_VALIDATE(img_alive, VALIDATE_BEGINPASS_DEPTHSTENCIL_ATTACHMENT_IMAGE_ALIVE); + if (img_alive) { + _SG_VALIDATE(_sg_image_ref_ptr(img_ref)->slot.state == SG_RESOURCESTATE_VALID, VALIDATE_BEGINPASS_DEPTHSTENCIL_ATTACHMENT_IMAGE_VALID); + } + } + } + } else { + _SG_VALIDATE(atts != 0, VALIDATE_BEGINPASS_ATTACHMENTS_EXISTS); + } + } + 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 pass attachments exist, check that the attachment object is still valid + bool atts_null = _sg_attachments_ref_null(&_sg.cur_pass.atts); + bool atts_alive = _sg_attachments_ref_alive(&_sg.cur_pass.atts); + const _sg_attachments_t* atts = atts_alive ? _sg_attachments_ref_ptr(&_sg.cur_pass.atts) : 0; + if (!atts_null) { + _SG_VALIDATE(atts_alive, VALIDATE_APIP_CURPASS_ATTACHMENTS_ALIVE); + if (atts) { + _SG_VALIDATE(atts->slot.state == SG_RESOURCESTATE_VALID, VALIDATE_APIP_CURPASS_ATTACHMENTS_VALID); + } + } + if (pip->cmn.is_compute) { + _SG_VALIDATE(_sg.cur_pass.is_compute, VALIDATE_APIP_COMPUTEPASS_EXPECTED); + if (atts) { + // a compute pass with storage attachments + // check that the pass storage attachments match the shader expectations + for (size_t i = 0; i < SG_MAX_STORAGE_ATTACHMENTS; i++) { + if (shd->cmn.storage_images[i].stage != SG_SHADERSTAGE_NONE) { + const _sg_image_ref_t* img_ref = &atts->cmn.storages[i].image; + const bool img_null = _sg_image_ref_null(img_ref); + const bool img_alive = _sg_image_ref_alive(img_ref); + _SG_VALIDATE(!img_null, VALIDATE_APIP_EXPECTED_STORAGE_ATTACHMENT_IMAGE); + if (!img_null) { + _SG_VALIDATE(img_alive, VALIDATE_APIP_STORAGE_ATTACHMENT_IMAGE_ALIVE); + if (img_alive) { + const _sg_image_t* img = _sg_image_ref_ptr(img_ref); + _SG_VALIDATE(img->slot.state == SG_RESOURCESTATE_VALID, VALIDATE_APIP_STORAGE_ATTACHMENT_IMAGE_VALID); + _SG_VALIDATE(img->cmn.pixel_format == shd->cmn.storage_images[i].access_format, VALIDATE_APIP_STORAGE_ATTACHMENT_PIXELFORMAT); + _SG_VALIDATE(img->cmn.type == shd->cmn.storage_images[i].image_type, VALIDATE_APIP_STORAGE_ATTACHMENT_IMAGE_TYPE); + } + } + } + } + } + } else { + _SG_VALIDATE(!_sg.cur_pass.is_compute, VALIDATE_APIP_RENDERPASS_EXPECTED); + // check that pipeline attributes match current pass attributes + if (!atts_null) { + if (atts) { + // an offscreen pass + _SG_VALIDATE(pip->cmn.color_count == atts->cmn.num_colors, VALIDATE_APIP_ATT_COUNT); + for (int i = 0; i < pip->cmn.color_count; i++) { + const _sg_image_ref_t* img_ref = &atts->cmn.colors[i].image; + const bool img_null = _sg_image_ref_null(img_ref); + const bool img_alive = _sg_image_ref_alive(img_ref); + if (!img_null) { + _SG_VALIDATE(img_alive, VALIDATE_APIP_COLOR_ATTACHMENT_IMAGE_ALIVE); + if (img_alive) { + const _sg_image_t* img = _sg_image_ref_ptr(img_ref); + _SG_VALIDATE(img->slot.state == SG_RESOURCESTATE_VALID, VALIDATE_APIP_COLOR_ATTACHMENT_IMAGE_VALID); + _SG_VALIDATE(pip->cmn.colors[i].pixel_format == img->cmn.pixel_format, VALIDATE_APIP_COLOR_FORMAT); + _SG_VALIDATE(pip->cmn.sample_count == img->cmn.sample_count, VALIDATE_APIP_SAMPLE_COUNT); + } + } + } + { + const _sg_image_ref_t* img_ref = &atts->cmn.depth_stencil.image; + const bool img_null = _sg_image_ref_null(img_ref); + const bool img_alive = _sg_image_ref_alive(img_ref); + if (!img_null) { + _SG_VALIDATE(img_alive, VALIDATE_APIP_DEPTHSTENCIL_ATTACHMENT_IMAGE_ALIVE); + if (img_alive) { + const _sg_image_t* img = _sg_image_ref_ptr(img_ref); + _SG_VALIDATE(img->slot.state == SG_RESOURCESTATE_VALID, VALIDATE_APIP_DEPTHSTENCIL_ATTACHMENT_IMAGE_VALID); + _SG_VALIDATE(pip->cmn.depth.pixel_format == img->cmn.pixel_format, VALIDATE_APIP_DEPTH_FORMAT); + } + } else { + _SG_VALIDATE(pip->cmn.depth.pixel_format == SG_PIXELFORMAT_NONE, VALIDATE_APIP_DEPTH_FORMAT); + } + } + } + } else { + // default pass + _SG_VALIDATE(pip->cmn.color_count == 1, VALIDATE_APIP_ATT_COUNT); + _SG_VALIDATE(pip->cmn.colors[0].pixel_format == _sg.cur_pass.swapchain.color_fmt, VALIDATE_APIP_COLOR_FORMAT); + _SG_VALIDATE(pip->cmn.depth.pixel_format == _sg.cur_pass.swapchain.depth_fmt, VALIDATE_APIP_DEPTH_FORMAT); + _SG_VALIDATE(pip->cmn.sample_count == _sg.cur_pass.swapchain.sample_count, VALIDATE_APIP_SAMPLE_COUNT); + } + } + 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_IMAGE_BINDSLOTS; i++) { + has_any_bindings |= bindings->images[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; + } + if (!has_any_bindings) for (size_t i = 0; i < SG_MAX_STORAGEBUFFER_BINDSLOTS; i++) { + has_any_bindings |= bindings->storage_buffers[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_VBS); + } + } else { + // has expected vertex buffers, and vertex buffers still exist + 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_VB); + // buffers in vertex-buffer-slots must have vertex buffer usage + 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_VB_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_VB_TYPE); + _SG_VALIDATE(!buf->cmn.append_overflow, VALIDATE_ABND_VB_OVERFLOW); + } + } + } + } + } + + if (_sg.cur_pass.is_compute) { + _SG_VALIDATE(bindings->index_buffer.id == SG_INVALID_ID, VALIDATE_ABND_COMPUTE_EXPECTED_NO_IB); + } 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_IB); + } else { + // pipeline defines indexed rendering, but no index buffer provided + _SG_VALIDATE(bindings->index_buffer.id != SG_INVALID_ID, VALIDATE_ABND_NO_IB); + } + 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_IB_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_IB_TYPE); + _SG_VALIDATE(!buf->cmn.append_overflow, VALIDATE_ABND_IB_OVERFLOW); + } + } + } + + // has expected images + for (size_t i = 0; i < SG_MAX_IMAGE_BINDSLOTS; i++) { + if (shd->cmn.images[i].stage != SG_SHADERSTAGE_NONE) { + _SG_VALIDATE(bindings->images[i].id != SG_INVALID_ID, VALIDATE_ABND_EXPECTED_IMAGE_BINDING); + if (bindings->images[i].id != SG_INVALID_ID) { + const _sg_image_t* img = _sg_lookup_image(bindings->images[i].id); + _SG_VALIDATE(img != 0, VALIDATE_ABND_IMG_ALIVE); + // NOTE: state != VALID is legal and skips rendering! + if (img && img->slot.state == SG_RESOURCESTATE_VALID) { + _SG_VALIDATE(img->cmn.type == shd->cmn.images[i].image_type, VALIDATE_ABND_IMAGE_TYPE_MISMATCH); + if (!_sg.features.msaa_image_bindings) { + _SG_VALIDATE(img->cmn.sample_count == 1, VALIDATE_ABND_IMAGE_MSAA); + } + if (shd->cmn.images[i].multisampled) { + _SG_VALIDATE(img->cmn.sample_count > 1, VALIDATE_ABND_EXPECTED_MULTISAMPLED_IMAGE); + } + const _sg_pixelformat_info_t* info = &_sg.formats[img->cmn.pixel_format]; + switch (shd->cmn.images[i].sample_type) { + case SG_IMAGESAMPLETYPE_FLOAT: + _SG_VALIDATE(info->filter, VALIDATE_ABND_EXPECTED_FILTERABLE_IMAGE); + break; + case SG_IMAGESAMPLETYPE_DEPTH: + _SG_VALIDATE(info->depth, VALIDATE_ABND_EXPECTED_DEPTH_IMAGE); + break; + default: + break; + } + } + } + } + } + + // 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_SMP_ALIVE); + if (smp) { + // NOTE: for samplers we're specifically checking that they are in valid state + // (technically an invalid sample skips rendering, but an invalid sampler is + // most likely an oversight) + _SG_VALIDATE(smp->slot.state == SG_RESOURCESTATE_VALID, VALIDATE_ABND_SMP_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); + } + } + } + } + } + + // has expected storage buffers + for (size_t i = 0; i < SG_MAX_STORAGEBUFFER_BINDSLOTS; i++) { + if (shd->cmn.storage_buffers[i].stage != SG_SHADERSTAGE_NONE) { + _SG_VALIDATE(bindings->storage_buffers[i].id != SG_INVALID_ID, VALIDATE_ABND_EXPECTED_STORAGEBUFFER_BINDING); + if (bindings->storage_buffers[i].id != SG_INVALID_ID) { + const _sg_buffer_t* sbuf = _sg_lookup_buffer(bindings->storage_buffers[i].id); + _SG_VALIDATE(sbuf != 0, VALIDATE_ABND_STORAGEBUFFER_ALIVE); + // NOTE: state != VALID is legal and skips rendering! + if (sbuf && sbuf->slot.state == SG_RESOURCESTATE_VALID) { + _SG_VALIDATE(sbuf->cmn.usage.storage_buffer, VALIDATE_ABND_STORAGEBUFFER_BINDING_BUFFERTYPE); + // read/write bindings are only allowed for immutable buffers + if (!shd->cmn.storage_buffers[i].readonly) { + _SG_VALIDATE(sbuf->cmn.usage.immutable, VALIDATE_ABND_STORAGEBUFFER_READWRITE_IMMUTABLE); + } + } + } + } + } + + // the same image cannot be bound as texture and pass attachment + if (!_sg_attachments_ref_null(&_sg.cur_pass.atts)) { + const _sg_attachments_t* atts = _sg_attachments_ref_ptr(&_sg.cur_pass.atts); + for (size_t img_idx = 0; img_idx < SG_MAX_IMAGE_BINDSLOTS; img_idx++) { + if (shd->cmn.images[img_idx].stage != SG_SHADERSTAGE_NONE) { + const uint32_t img_id = bindings->images[img_idx].id; + if (img_id == SG_INVALID_ID) { + continue; + } + _SG_VALIDATE(img_id != atts->cmn.depth_stencil.image.sref.id, VALIDATE_ABND_IMAGE_BINDING_VS_DEPTHSTENCIL_ATTACHMENT); + for (size_t att_idx = 0; att_idx < SG_MAX_COLOR_ATTACHMENTS; att_idx++) { + _SG_VALIDATE(img_id != atts->cmn.colors[att_idx].image.sref.id, VALIDATE_ABND_IMAGE_BINDING_VS_COLOR_ATTACHMENT); + _SG_VALIDATE(img_id != atts->cmn.resolves[att_idx].image.sref.id, VALIDATE_ABND_IMAGE_BINDING_VS_RESOLVE_ATTACHMENT); + } + for (size_t att_idx = 0; att_idx < SG_MAX_STORAGE_ATTACHMENTS; att_idx++) { + _SG_VALIDATE(img_id != atts->cmn.storages[att_idx].image.sref.id, VALIDATE_ABND_IMAGE_BINDING_VS_STORAGE_ATTACHMENT); + } + } + } + } + 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); + _SG_VALIDATE(num_elements >= 0, VALIDATE_DRAW_NUMELEMENTS); + _SG_VALIDATE(num_instances >= 0, VALIDATE_DRAW_NUMINSTANCES); + _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.type == SG_IMAGETYPE_CUBE) ? 6 : 1, + img->cmn.num_mipmaps, + img->cmn.num_slices); + return _sg_validate_end(); + #endif +} + +// ██████ ███████ ███████ ██████ ██ ██ ██████ ██████ ███████ ███████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██████ █████ ███████ ██ ██ ██ ██ ██████ ██ █████ ███████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██ ██ ███████ ███████ ██████ ██████ ██ ██ ██████ ███████ ███████ +// +// >>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, 1); + def.num_mipmaps = _sg_def(def.num_mipmaps, 1); + if (def.usage.render_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 { + 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 img_index = 0; img_index < SG_MAX_IMAGE_BINDSLOTS; img_index++) { + sg_shader_image* img_desc = &def.images[img_index]; + if (img_desc->stage != SG_SHADERSTAGE_NONE) { + img_desc->image_type = _sg_def(img_desc->image_type, SG_IMAGETYPE_2D); + img_desc->sample_type = _sg_def(img_desc->sample_type, SG_IMAGESAMPLETYPE_FLOAT); + } + } + 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_attachments_desc _sg_attachments_desc_defaults(const sg_attachments_desc* desc) { + sg_attachments_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); + } 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); + } 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); + } 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); + } 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); + } else { + res.id = SG_INVALID_ID; + _SG_ERROR(PIPELINE_POOL_EXHAUSTED); + } + return res; +} + +_SOKOL_PRIVATE sg_attachments _sg_alloc_attachments(void) { + sg_attachments res; + int slot_index = _sg_pool_alloc_index(&_sg.pools.attachments_pool); + if (_SG_INVALID_SLOT_INDEX != slot_index) { + res.id = _sg_slot_alloc(&_sg.pools.attachments_pool, &_sg.pools.attachments[slot_index].slot, slot_index); + } else { + res.id = SG_INVALID_ID; + _SG_ERROR(PASS_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); +} + +_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); +} + +_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); +} + +_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); +} + +_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); +} + +_SOKOL_PRIVATE void _sg_dealloc_attachments(_sg_attachments_t* atts) { + SOKOL_ASSERT(atts && (atts->slot.state == SG_RESOURCESTATE_ALLOC) && (atts->slot.id != SG_INVALID_ID)); + _sg_pool_free_index(&_sg.pools.attachments_pool, _sg_slot_index(atts->slot.id)); + _sg_slot_reset(&atts->slot); +} + +_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)); +} + +_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)); +} + +_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)); +} + +_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)) { + _sg_shader_common_init(&shd->cmn, desc); + shd->slot.state = _sg_create_shader(shd, desc); + } else { + shd->slot.state = SG_RESOURCESTATE_FAILED; + } + SOKOL_ASSERT((shd->slot.state == SG_RESOURCESTATE_VALID)||(shd->slot.state == SG_RESOURCESTATE_FAILED)); +} + +_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)); +} + +_SOKOL_PRIVATE void _sg_init_attachments(_sg_attachments_t* atts, const sg_attachments_desc* desc) { + SOKOL_ASSERT(atts && atts->slot.state == SG_RESOURCESTATE_ALLOC); + SOKOL_ASSERT(desc); + if (_sg_validate_attachments_desc(desc)) { + // resolve image pointers, track width and height of render attachments, + // the validation layer already ensured that render attachments have the + // same width and height (which doesn't matter for storage attachments) + _sg_attachments_ptrs_t atts_ptrs; + _sg_clear(&atts_ptrs, sizeof(atts_ptrs)); + int width = 0; + int height = 0; + for (size_t i = 0; i < SG_MAX_COLOR_ATTACHMENTS; i++) { + if (desc->colors[i].image.id) { + _sg_image_t* img = _sg_lookup_image(desc->colors[i].image.id); + if (!(img && img->slot.state == SG_RESOURCESTATE_VALID)) { + atts->slot.state = SG_RESOURCESTATE_FAILED; + return; + } + const int mip_level = desc->colors[i].mip_level; + width = _sg_miplevel_dim(img->cmn.width, mip_level); + height = _sg_miplevel_dim(img->cmn.height, mip_level); + atts_ptrs.color_images[i] = img; + } + if (desc->resolves[i].image.id) { + _sg_image_t* img = _sg_lookup_image(desc->resolves[i].image.id); + if (!(img && img->slot.state == SG_RESOURCESTATE_VALID)) { + atts->slot.state = SG_RESOURCESTATE_FAILED; + return; + } + atts_ptrs.resolve_images[i] = img; + } + } + if (desc->depth_stencil.image.id) { + _sg_image_t* img = _sg_lookup_image(desc->depth_stencil.image.id); + if (!(img && img->slot.state == SG_RESOURCESTATE_VALID)) { + atts->slot.state = SG_RESOURCESTATE_FAILED; + return; + } + const int mip_level = desc->depth_stencil.mip_level; + width = _sg_miplevel_dim(img->cmn.width, mip_level); + height = _sg_miplevel_dim(img->cmn.height, mip_level); + atts_ptrs.ds_image = img; + } + for (size_t i = 0; i < SG_MAX_STORAGE_ATTACHMENTS; i++) { + if (desc->storages[i].image.id) { + _sg_image_t* img = _sg_lookup_image(desc->storages[i].image.id); + if (!(img && img->slot.state == SG_RESOURCESTATE_VALID)) { + atts->slot.state = SG_RESOURCESTATE_FAILED; + return; + } + atts_ptrs.storage_images[i] = img; + } + } + _sg_attachments_common_init(&atts->cmn, desc, &atts_ptrs, width, height); + atts->slot.state = _sg_create_attachments(atts, desc); + } else { + atts->slot.state = SG_RESOURCESTATE_FAILED; + } + SOKOL_ASSERT((atts->slot.state == SG_RESOURCESTATE_VALID)||(atts->slot.state == SG_RESOURCESTATE_FAILED)); +} + +_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); +} + +_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); +} + +_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); +} + +_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); +} + +_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); +} + +_SOKOL_PRIVATE void _sg_uninit_attachments(_sg_attachments_t* atts) { + SOKOL_ASSERT(atts && ((atts->slot.state == SG_RESOURCESTATE_VALID) || (atts->slot.state == SG_RESOURCESTATE_FAILED))); + _sg_discard_attachments(atts); + _sg_reset_attachments_to_alloc_state(atts); +} + +_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 void _sg_setup_compute(const sg_desc* desc) { + SOKOL_ASSERT(desc && (desc->max_dispatch_calls_per_pass > 0)); + const uint32_t max_tracked_sbufs = (uint32_t)desc->max_dispatch_calls_per_pass * SG_MAX_STORAGEBUFFER_BINDSLOTS; + _sg_tracker_init(&_sg.compute.readwrite_sbufs, max_tracked_sbufs); +} + +_SOKOL_PRIVATE void _sg_discard_compute(void) { + _sg_tracker_discard(&_sg.compute.readwrite_sbufs); +} + +_SOKOL_PRIVATE void _sg_compute_pass_track_storage_buffer(_sg_buffer_t* sbuf, bool readonly) { + SOKOL_ASSERT(sbuf); + if (!readonly) { + _sg_tracker_add(&_sg.compute.readwrite_sbufs, sbuf->slot.id); + } +} + +_SOKOL_PRIVATE void _sg_compute_on_endpass(void) { + SOKOL_ASSERT(_sg.cur_pass.in_pass); + SOKOL_ASSERT(_sg.cur_pass.is_compute); + _sg_tracker_reset(&_sg.compute.readwrite_sbufs); +} + +_SOKOL_PRIVATE sg_desc _sg_desc_defaults(const sg_desc* desc) { + /* + NOTE: on WebGPU, the default color pixel format MUST be provided, + 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.attachments_pool_size = _sg_def(res.attachments_pool_size, _SG_DEFAULT_ATTACHMENTS_POOL_SIZE); + res.uniform_buffer_size = _sg_def(res.uniform_buffer_size, _SG_DEFAULT_UB_SIZE); + res.max_dispatch_calls_per_pass = _sg_def(res.max_dispatch_calls_per_pass, _SG_DEFAULT_MAX_DISPATCH_CALLS_PER_PASS); + 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 (res.attachments.id == SG_INVALID_ID) { + // 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.attachments_pool.size; i++) { + sg_resource_state state = _sg.pools.attachments[i].slot.state; + if ((state == SG_RESOURCESTATE_VALID) || (state == SG_RESOURCESTATE_FAILED)) { + _sg_discard_attachments(&_sg.pools.attachments[i]); + } + } +} + +// ██████ ██ ██ ██████ ██ ██ ██████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██████ ██ ██ ██████ ██ ██ ██ +// ██ ██ ██ ██ ██ ██ ██ ██ +// ██ ██████ ██████ ███████ ██ ██████ +// +// >>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_compute(&_sg.desc); + _sg_setup_commit_listeners(&_sg.desc); + _sg.frame_index = 1; + _sg.stats_enabled = true; + _sg_setup_backend(&_sg.desc); + _sg.valid = true; +} + +SOKOL_API_IMPL void sg_shutdown(void) { + _sg_discard_all_resources(); + _sg_discard_backend(); + _sg_discard_commit_listeners(); + _sg_discard_compute(); + _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_attachments sg_alloc_attachments(void) { + SOKOL_ASSERT(_sg.valid); + sg_attachments res = _sg_alloc_attachments(); + _SG_TRACE_ARGS(alloc_attachments, 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_attachments(sg_attachments atts_id) { + SOKOL_ASSERT(_sg.valid); + _sg_attachments_t* atts = _sg_lookup_attachments(atts_id.id); + if (atts) { + if (atts->slot.state == SG_RESOURCESTATE_ALLOC) { + _sg_dealloc_attachments(atts); + } else { + _SG_ERROR(DEALLOC_ATTACHMENTS_INVALID_STATE); + } + } + _SG_TRACE_ARGS(dealloc_attachments, atts_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_attachments(sg_attachments atts_id, const sg_attachments_desc* desc) { + SOKOL_ASSERT(_sg.valid); + sg_attachments_desc desc_def = _sg_attachments_desc_defaults(desc); + _sg_attachments_t* atts = _sg_lookup_attachments(atts_id.id); + if (atts) { + if (atts->slot.state == SG_RESOURCESTATE_ALLOC) { + _sg_init_attachments(atts, &desc_def); + SOKOL_ASSERT((atts->slot.state == SG_RESOURCESTATE_VALID) || (atts->slot.state == SG_RESOURCESTATE_FAILED)); + } else { + _SG_ERROR(INIT_ATTACHMENTS_INVALID_STATE); + } + } + _SG_TRACE_ARGS(init_attachments, atts_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 { + _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 { + _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 { + _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 { + _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 { + _SG_ERROR(UNINIT_PIPELINE_INVALID_STATE); + } + } + _SG_TRACE_ARGS(uninit_pipeline, pip_id); +} + +SOKOL_API_IMPL void sg_uninit_attachments(sg_attachments atts_id) { + SOKOL_ASSERT(_sg.valid); + _sg_attachments_t* atts = _sg_lookup_attachments(atts_id.id); + if (atts) { + if ((atts->slot.state == SG_RESOURCESTATE_VALID) || (atts->slot.state == SG_RESOURCESTATE_FAILED)) { + _sg_uninit_attachments(atts); + SOKOL_ASSERT(atts->slot.state == SG_RESOURCESTATE_ALLOC); + } else { + _SG_ERROR(UNINIT_ATTACHMENTS_INVALID_STATE); + } + } + _SG_TRACE_ARGS(uninit_attachments, atts_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_attachments(sg_attachments atts_id) { + SOKOL_ASSERT(_sg.valid); + _sg_attachments_t* atts = _sg_lookup_attachments(atts_id.id); + if (atts) { + if (atts->slot.state == SG_RESOURCESTATE_ALLOC) { + atts->slot.state = SG_RESOURCESTATE_FAILED; + } else { + _SG_ERROR(FAIL_ATTACHMENTS_INVALID_STATE); + } + } + _SG_TRACE_ARGS(fail_attachments, atts_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_attachments_state(sg_attachments atts_id) { + SOKOL_ASSERT(_sg.valid); + _sg_attachments_t* atts = _sg_lookup_attachments(atts_id.id); + sg_resource_state res = atts ? atts->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_attachments sg_make_attachments(const sg_attachments_desc* desc) { + SOKOL_ASSERT(_sg.valid); + SOKOL_ASSERT(desc); + sg_attachments_desc desc_def = _sg_attachments_desc_defaults(desc); + sg_attachments atts_id = _sg_alloc_attachments(); + if (atts_id.id != SG_INVALID_ID) { + _sg_attachments_t* atts = _sg_attachments_at(atts_id.id); + SOKOL_ASSERT(atts && (atts->slot.state == SG_RESOURCESTATE_ALLOC)); + _sg_init_attachments(atts, &desc_def); + SOKOL_ASSERT((atts->slot.state == SG_RESOURCESTATE_VALID) || (atts->slot.state == SG_RESOURCESTATE_FAILED)); + } + _SG_TRACE_ARGS(make_attachments, &desc_def, atts_id); + return atts_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_attachments(sg_attachments atts_id) { + SOKOL_ASSERT(_sg.valid); + _SG_TRACE_ARGS(destroy_attachments, atts_id); + _sg_attachments_t* atts = _sg_lookup_attachments(atts_id.id); + if (atts) { + if ((atts->slot.state == SG_RESOURCESTATE_VALID) || (atts->slot.state == SG_RESOURCESTATE_FAILED)) { + _sg_uninit_attachments(atts); + SOKOL_ASSERT(atts->slot.state == SG_RESOURCESTATE_ALLOC); + } + if (atts->slot.state == SG_RESOURCESTATE_ALLOC) { + _sg_dealloc_attachments(atts); + SOKOL_ASSERT(atts->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_ref_null(&_sg.cur_pass.atts)); + SOKOL_ASSERT(pass); + SOKOL_ASSERT((pass->_start_canary == 0) && (pass->_end_canary == 0)); + const sg_pass pass_def = _sg_pass_defaults(pass); + if (!_sg_validate_begin_pass(&pass_def)) { + return; + } + _sg.cur_pass.atts = _sg_attachments_ref(0); + if (pass_def.attachments.id != SG_INVALID_ID) { + _sg_attachments_t* atts = _sg_lookup_attachments(pass_def.attachments.id); + if (0 == atts) { + _SG_ERROR(BEGINPASS_ATTACHMENT_INVALID); + return; + } + SOKOL_ASSERT(atts); + _sg.cur_pass.atts = _sg_attachments_ref(atts); + _sg.cur_pass.width = atts->cmn.width; + _sg.cur_pass.height = atts->cmn.height; + } 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.width = pass_def.swapchain.width; + _sg.cur_pass.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.in_pass = true; + _sg.cur_pass.is_compute = pass_def.compute; + _sg_begin_pass(&pass_def); + _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_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); + SOKOL_ASSERT((bindings->_start_canary == 0) && (bindings->_end_canary==0)); + _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; + } + 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]; + if (bnd.vbs[i]) { + _sg.next_draw_valid &= (SG_RESOURCESTATE_VALID == bnd.vbs[i]->slot.state); + _sg.next_draw_valid &= !bnd.vbs[i]->cmn.append_overflow; + } else { + _sg.next_draw_valid = false; + } + } + } + if (bindings->index_buffer.id) { + bnd.ib = _sg_lookup_buffer(bindings->index_buffer.id); + bnd.ib_offset = bindings->index_buffer_offset; + if (bnd.ib) { + _sg.next_draw_valid &= (SG_RESOURCESTATE_VALID == bnd.ib->slot.state); + _sg.next_draw_valid &= !bnd.ib->cmn.append_overflow; + } else { + _sg.next_draw_valid = false; + } + } + } + + for (int i = 0; i < SG_MAX_IMAGE_BINDSLOTS; i++) { + if (shd->cmn.images[i].stage != SG_SHADERSTAGE_NONE) { + SOKOL_ASSERT(bindings->images[i].id != SG_INVALID_ID); + bnd.imgs[i] = _sg_lookup_image(bindings->images[i].id); + if (bnd.imgs[i]) { + _sg.next_draw_valid &= (SG_RESOURCESTATE_VALID == bnd.imgs[i]->slot.state); + } 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); + if (bnd.smps[i]) { + _sg.next_draw_valid &= (SG_RESOURCESTATE_VALID == bnd.smps[i]->slot.state); + } else { + _sg.next_draw_valid = false; + } + } + } + + for (size_t i = 0; i < SG_MAX_STORAGEBUFFER_BINDSLOTS; i++) { + if (shd->cmn.storage_buffers[i].stage != SG_SHADERSTAGE_NONE) { + SOKOL_ASSERT(bindings->storage_buffers[i].id != SG_INVALID_ID); + bnd.sbufs[i] = _sg_lookup_buffer(bindings->storage_buffers[i].id); + if (bnd.sbufs[i]) { + _sg.next_draw_valid &= (SG_RESOURCESTATE_VALID == bnd.sbufs[i]->slot.state); + if (_sg.cur_pass.is_compute) { + _sg_compute_pass_track_storage_buffer(bnd.sbufs[i], shd->cmn.storage_buffers[i].readonly); + } + } else { + _sg.next_draw_valid = false; + } + } + } + + 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_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.cur_pass.valid) { + return; + } + if (!_sg.next_draw_valid) { + return; + } + // skip no-op draws + if ((0 == num_elements) || (0 == num_instances)) { + return; + } + _sg_draw(base_element, num_elements, num_instances); + _SG_TRACE_ARGS(draw, base_element, num_elements, num_instances); +} + +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 + _sg_end_pass(); + _sg.cur_pip = _sg_pipeline_ref(0); + if (_sg.cur_pass.is_compute) { + _sg_compute_on_endpass(); + } + _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.stats.frame_index = _sg.frame_index; + _sg.prev_stats = _sg.stats; + _sg_clear(&_sg.stats, sizeof(_sg.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 face_index = 0; face_index < SG_CUBEFACE_NUM; face_index++) { + for (int mip_index = 0; mip_index < SG_MAX_MIPMAPS; mip_index++) { + if (data->subimage[face_index][mip_index].size == 0) { + break; + } + _sg_stats_add(size_update_image, (uint32_t)data->subimage[face_index][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_attachments_info sg_query_attachments_info(sg_attachments atts_id) { + SOKOL_ASSERT(_sg.valid); + sg_attachments_info info; + _sg_clear(&info, sizeof(info)); + const _sg_attachments_t* atts = _sg_lookup_attachments(atts_id.id); + if (atts) { + info.slot.state = atts->slot.state; + info.slot.res_id = atts->slot.id; + info.slot.uninit_count = atts->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); + 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_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 sbuf_idx = 0; sbuf_idx < SG_MAX_STORAGEBUFFER_BINDSLOTS; sbuf_idx++) { + sg_shader_storage_buffer* sbuf_desc = &desc.storage_buffers[sbuf_idx]; + const _sg_shader_storage_buffer_t* sbuf = &shd->cmn.storage_buffers[sbuf_idx]; + sbuf_desc->stage = sbuf->stage; + sbuf_desc->readonly = sbuf->readonly; + } + for (size_t simg_idx = 0; simg_idx < SG_MAX_STORAGE_ATTACHMENTS; simg_idx++) { + sg_shader_storage_image* simg_desc = &desc.storage_images[simg_idx]; + const _sg_shader_storage_image_t* simg = &shd->cmn.storage_images[simg_idx]; + simg_desc->stage = simg->stage; + simg_desc->access_format = simg->access_format; + simg_desc->image_type = simg->image_type; + simg_desc->writeonly = simg->writeonly; + } + for (size_t img_idx = 0; img_idx < SG_MAX_IMAGE_BINDSLOTS; img_idx++) { + sg_shader_image* img_desc = &desc.images[img_idx]; + const _sg_shader_image_t* img = &shd->cmn.images[img_idx]; + img_desc->stage = img->stage; + img_desc->image_type = img->image_type; + img_desc->sample_type = img->sample_type; + img_desc->multisampled = img->multisampled; + } + 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 img_smp_idx = 0; img_smp_idx < SG_MAX_IMAGE_SAMPLER_PAIRS; img_smp_idx++) { + sg_shader_image_sampler_pair* img_smp_desc = &desc.image_sampler_pairs[img_smp_idx]; + const _sg_shader_image_sampler_t* img_smp = &shd->cmn.image_samplers[img_smp_idx]; + img_smp_desc->stage = img_smp->stage; + img_smp_desc->image_slot = img_smp->image_slot; + img_smp_desc->sampler_slot = img_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_attachments_desc sg_query_attachments_desc(sg_attachments atts_id) { + SOKOL_ASSERT(_sg.valid); + sg_attachments_desc desc; + _sg_clear(&desc, sizeof(desc)); + const _sg_attachments_t* atts = _sg_lookup_attachments(atts_id.id); + if (atts) { + for (int i = 0; i < atts->cmn.num_colors; i++) { + desc.colors[i].image.id = atts->cmn.colors[i].image.sref.id; + desc.colors[i].mip_level = atts->cmn.colors[i].mip_level; + desc.colors[i].slice = atts->cmn.colors[i].slice; + } + desc.depth_stencil.image.id = atts->cmn.depth_stencil.image.sref.id; + desc.depth_stencil.mip_level = atts->cmn.depth_stencil.mip_level; + desc.depth_stencil.slice = atts->cmn.depth_stencil.slice; + } + 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_attachments_desc sg_query_attachments_defaults(const sg_attachments_desc* desc) { + SOKOL_ASSERT(_sg.valid && desc); + return _sg_attachments_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; + res.srv = (const void*) img->d3d11.srv; + } + #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_attachments_info sg_d3d11_query_attachments_info(sg_attachments atts_id) { + SOKOL_ASSERT(_sg.valid); + sg_d3d11_attachments_info res; + _sg_clear(&res, sizeof(res)); + #if defined(SOKOL_D3D11) + const _sg_attachments_t* atts = _sg_lookup_attachments(atts_id.id); + if (atts) { + for (int i = 0; i < SG_MAX_COLOR_ATTACHMENTS; i++) { + res.color_rtv[i] = (const void*) atts->d3d11.colors[i].view.rtv; + } + res.dsv = (const void*) atts->d3d11.depth_stencil.view.dsv; + } + #else + _SOKOL_UNUSED(atts_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; + res.view = (const void*) img->wgpu.view; + } + #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_img_smp_sbuf; + } + #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_attachments_info sg_wgpu_query_attachments_info(sg_attachments atts_id) { + SOKOL_ASSERT(_sg.valid); + sg_wgpu_attachments_info res; + _sg_clear(&res, sizeof(res)); + #if defined(SOKOL_WGPU) + const _sg_attachments_t* atts = _sg_lookup_attachments(atts_id.id); + if (atts) { + for (int i = 0; i < SG_MAX_COLOR_ATTACHMENTS; i++) { + res.color_view[i] = (const void*) atts->wgpu.colors[i].view; + res.resolve_view[i] = (const void*) atts->wgpu.resolves[i].view; + } + res.ds_view = (const void*) atts->wgpu.depth_stencil.view; + } + #else + _SOKOL_UNUSED(atts_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.msaa_render_buffer = img->gl.msaa_render_buffer; + 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_attachments_info sg_gl_query_attachments_info(sg_attachments atts_id) { + SOKOL_ASSERT(_sg.valid); + sg_gl_attachments_info res; + _sg_clear(&res, sizeof(res)); + #if defined(_SOKOL_ANY_GL) + const _sg_attachments_t* atts = _sg_lookup_attachments(atts_id.id); + if (atts) { + res.framebuffer = atts->gl.fb; + for (int i = 0; i < SG_MAX_COLOR_ATTACHMENTS; i++) { + res.msaa_resolve_framebuffer[i] = atts->gl.msaa_resolve_framebuffer[i]; + } + } + #else + _SOKOL_UNUSED(atts_id); + #endif + return res; +} + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#endif // SOKOL_GFX_IMPL diff --git a/inc/sokol/sokol_glue.h b/inc/sokol/sokol_glue.h new file mode 100644 index 0000000..a715b17 --- /dev/null +++ b/inc/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/inc/sokol/sokol_gp.h b/inc/sokol/sokol_gp.h new file mode 100644 index 0000000..2023ba6 --- /dev/null +++ b/inc/sokol/sokol_gp.h @@ -0,0 +1,3044 @@ +/* +Minimal efficient cross platform 2D graphics painter for Sokol GFX. +sokol_gp - v0.7.0 - 06/Dec/2024 +Eduardo Bart - edub4rt@gmail.com +https://github.com/edubart/sokol_gp + +# Sokol GP + +Minimal efficient cross platform 2D graphics painter in pure C +using modern graphics API through the excellent [Sokol GFX](https://github.com/floooh/sokol) library. + +Sokol GP, or in short SGP, stands for Sokol Graphics Painter. + +![sample-primitives](https://raw.githubusercontent.com/edubart/sokol_gp/master/screenshots/sample-primitives.png) + +## Features + +* Made and optimized only for **2D rendering only**, no 3D support. +* Minimal, in a pure single C header. +* Use modern unfixed pipeline graphics APIs for more efficiency. +* Cross platform (backed by Sokol GFX). +* D3D11/OpenGL 3.3/Metal/WebGPU graphics backends (through Sokol GFX). +* **Automatic batching** (merge recent draw calls into batches automatically). +* **Batch optimizer** (rearranges the ordering of draw calls to batch more). +* Uses preallocated memory (no allocations at runtime). +* Supports drawing basic 2D primitives (rectangles, triangles, lines and points). +* Supports the classic 2D color blending modes (color blend, add, modulate, multiply). +* Supports 2D space transformations and changing 2D space coordinate systems. +* Supports drawing the basic primitives (rectangles, triangles, lines and points). +* Supports multiple texture bindings. +* Supports custom fragment shaders with 2D primitives. +* Can be mixed with projects that are already using Sokol GFX. + +## Why? + +Sokol GFX is an excellent library for rendering using unfixed pipelines +of modern graphics cards, but it is too complex to use for simple 2D drawing, +and it's API is too generic and specialized for 3D rendering. To draw 2D stuff, the programmer +usually needs to setup custom shaders when using Sokol GFX, or use its Sokol GL +extra library, but Sokol GL also has an API with 3D design in mind, which +incurs some costs and limitations. + +This library was created to draw 2D primitives through Sokol GFX with ease, +and by not considering 3D usage it is optimized for 2D rendering only, +furthermore it features an **automatic batch optimizer**, more details of it will be described below. + +## Automatic batch optimizer + +When drawing the library creates a draw command queue of all primitives yet to be drawn, +every time a new draw command is added the batch optimizer looks back up to the last +8 recent draw commands (this is adjustable), and try to rearrange and merge drawing commands +if it finds a previous draw command that meets the following criteria: + +* The new draw command and previous command uses the *same primitive pipeline* +* The new draw command and previous command uses the *same shader uniforms* +* The new draw command and previous command uses the *same texture bindings* +* The new draw command and previous command does not have another intermediary +draw command *that overlaps* in-between them. + +By doing this the batch optimizer is able for example to merge textured draw calls, +even if they were drawn with other intermediary different textures draws between them. +The effect is more efficiency when drawing, because less draw calls will be dispatched +to the GPU, + +This library can avoid a lot of work of making an efficient 2D drawing batching system, +by automatically merging draw calls behind the scenes at runtime, +thus the programmer does not need to manage batched draw calls manually, +nor he needs to sort batched texture draw calls, +the library will do this seamlessly behind the scenes. + +The batching algorithm is fast, but it has `O(n)` CPU complexity for every new draw command added, +where `n` is the `SGP_BATCH_OPTIMIZER_DEPTH` configuration. +In experiments using `8` as the default is a good default, +but you may want to try out different values depending on your case. +Using values that are too high is not recommended, because the algorithm may take too long +scanning previous draw commands, and that may consume more CPU resources. + +The batch optimizer can be disabled by setting `SGP_BATCH_OPTIMIZER_DEPTH` to 0, +you can use that to measure its impact. + +In the samples directory of this repository there is a +benchmark example that tests drawing with the bath optimizer enabled/disabled. +On my machine that benchmark was able to increase performance in a 2.2x factor when it is enabled. +In some private game projects the gains of the batch optimizer proved to increase FPS performance +above 1.5x by just replacing the graphics backend with this library, with no internal +changes to the game itself. + +## Design choices + +The library has some design choices with performance in mind that will be discussed briefly here. + +Like Sokol GFX, Sokol GP will never do any allocation in the draw loop, +so when initializing you must configure beforehand the maximum size of the +draw command queue buffer and the vertices buffer. + +All the 2D space transformation (functions like `sgp_rotate`) are done by the CPU and not by the GPU, +this is intentionally to avoid adding extra overhead in the GPU, because typically the number +of vertices of 2D applications are not that large, and it is more efficient to perform +all the transformation with the CPU right away rather than pushing extra buffers to the GPU +that ends up using more bandwidth of the CPU<->GPU bus. +In contrast 3D applications usually dispatches vertex transformations to the GPU using a vertex shader, +they do this because the amount of vertices of 3D objects can be very large +and it is usually the best choice, but this is not true for 2D rendering. + +Many APIs to transform the 2D space before drawing a primitive are available, such as +translate, rotate and scale. They can be used as similarly as the ones available in 3D graphics APIs, +but they are crafted for 2D only, for example when using 2D we don't need to use a 4x4 or 3x3 matrix +to perform vertex transformation, instead the code is specialized for 2D and can use a 2x3 matrix, +saving extra CPU float computations. + +All pipelines always use a texture associated with it, even when drawing non textured primitives, +because this minimizes graphics pipeline changes when mixing textured calls and non textured calls, +improving efficiency. + +The library is coded in the style of Sokol GFX headers, reusing many macros from there, +you can change some of its semantics such as custom allocator, custom log function, and some +other details, read `sokol_gfx.h` documentation for more on that. + +## Usage + +Copy `sokol_gp.h` along with other Sokol headers to the same folder. Setup Sokol GFX +as you usually would, then add call to `sgp_setup(desc)` just after `sg_setup(desc)`, and +call to `sgp_shutdown()` just before `sg_shutdown()`. Note that you should usually check if +SGP is valid after its creation with `sgp_is_valid()` and exit gracefully with an error if not. + +In your frame draw function add `sgp_begin(width, height)` before calling any SGP +draw function, then draw your primitives. At the end of the frame (or framebuffer) you +should **ALWAYS call** `sgp_flush()` between a Sokol GFX begin/end render pass, +the `sgp_flush()` will dispatch all draw commands to Sokol GFX. Then call `sgp_end()` immediately +to discard the draw command queue. + +An actual example of this setup will be shown below. + +## Quick usage example + +The following is a quick example on how to this library with Sokol GFX and Sokol APP: + +```c +// This is an example on how to set up and use Sokol GP to draw a filled rectangle. + +// Includes Sokol GFX, Sokol GP and Sokol APP, doing all implementations. +#define SOKOL_IMPL +#include "sokol_gfx.h" +#include "sokol_gp.h" +#include "sokol_app.h" +#include "sokol_glue.h" +#include "sokol_log.h" + +#include // for fprintf() +#include // for exit() +#include // for sinf() and cosf() + +// Called on every frame of the application. +static void frame(void) { + // Get current window size. + int width = sapp_width(), height = sapp_height(); + float ratio = width/(float)height; + + // Begin recording draw commands for a frame buffer of size (width, height). + sgp_begin(width, height); + // Set frame buffer drawing region to (0,0,width,height). + sgp_viewport(0, 0, width, height); + // Set drawing coordinate space to (left=-ratio, right=ratio, top=1, bottom=-1). + sgp_project(-ratio, ratio, 1.0f, -1.0f); + + // Clear the frame buffer. + sgp_set_color(0.1f, 0.1f, 0.1f, 1.0f); + sgp_clear(); + + // Draw an animated rectangle that rotates and changes its colors. + float time = sapp_frame_count() * sapp_frame_duration(); + float r = sinf(time)*0.5+0.5, g = cosf(time)*0.5+0.5; + sgp_set_color(r, g, 0.3f, 1.0f); + sgp_rotate_at(time, 0.0f, 0.0f); + sgp_draw_filled_rect(-0.5f, -0.5f, 1.0f, 1.0f); + + // Begin a render pass. + sg_pass pass = {.swapchain = sglue_swapchain()}; + sg_begin_pass(&pass); + // Dispatch all draw commands to Sokol GFX. + sgp_flush(); + // Finish a draw command queue, clearing it. + sgp_end(); + // End render pass. + sg_end_pass(); + // Commit Sokol render. + sg_commit(); +} + +// Called when the application is initializing. +static void init(void) { + // Initialize Sokol GFX. + sg_desc sgdesc = { + .environment = sglue_environment(), + .logger.func = slog_func + }; + sg_setup(&sgdesc); + if (!sg_isvalid()) { + fprintf(stderr, "Failed to create Sokol GFX context!\n"); + exit(-1); + } + + // Initialize Sokol GP, adjust the size of command buffers for your own use. + sgp_desc sgpdesc = {0}; + sgp_setup(&sgpdesc); + if (!sgp_is_valid()) { + fprintf(stderr, "Failed to create Sokol GP context: %s\n", sgp_get_error_message(sgp_get_last_error())); + exit(-1); + } +} + +// Called when the application is shutting down. +static void cleanup(void) { + // Cleanup Sokol GP and Sokol GFX resources. + sgp_shutdown(); + sg_shutdown(); +} + +// Implement application main through Sokol APP. +sapp_desc sokol_main(int argc, char* argv[]) { + (void)argc; + (void)argv; + return (sapp_desc){ + .init_cb = init, + .frame_cb = frame, + .cleanup_cb = cleanup, + .window_title = "Rectangle (Sokol GP)", + .logger.func = slog_func, + }; +} +``` + +To run this example, first copy the `sokol_gp.h` header alongside with other Sokol headers +to the same folder, then compile with any C compiler using the proper linking flags (read `sokol_gfx.h`). + +## Complete Examples + +In folder `samples` you can find the following complete examples covering all APIs of the library: + +* [sample-primitives.c](https://github.com/edubart/sokol_gp/blob/master/samples/sample-primitives.c): This is an example showing all drawing primitives and transformations APIs. +* [sample-blend.c](https://github.com/edubart/sokol_gp/blob/master/samples/sample-blend.c): This is an example showing all blend modes between 3 rectangles. +* [sample-framebuffer.c](https://github.com/edubart/sokol_gp/blob/master/samples/sample-framebuffer.c): This is an example showing how to use multiple `sgp_begin()` with frame buffers. +* [sample-sdf.c](https://github.com/edubart/sokol_gp/blob/master/samples/sample-sdf.c): This is an example on how to create custom shaders. +* [sample-effect.c](https://github.com/edubart/sokol_gp/blob/master/samples/sample-effect.c): This is an example on how to use custom shaders for 2D drawing. +* [sample-bench.c](https://github.com/edubart/sokol_gp/blob/master/samples/sample-bench.c): This is a heavy example used for benchmarking purposes. + +These examples are used as the test suite for the library, you can build them by typing `make`. + +## Error handling + +It is possible that after many draw calls the command or vertex buffer may overflow, +in that case the library will set an error error state and will continue to operate normally, +but when flushing the drawing command queue with `sgp_flush()` no draw command will be dispatched. +This can happen because the library uses pre allocated buffers, in such +cases the issue can be fixed by increasing the prefixed command queue buffer and the vertices buffer +when calling `sgp_setup()`. + +Making invalid number of push/pops of `sgp_push_transform()` and `sgp_pop_transform()`, +or nesting too many `sgp_begin()` and `sgp_end()` may also lead to errors, that +is a usage mistake. + +You can enable the `SOKOL_DEBUG` macro in such cases to debug, or handle +the error programmatically by reading `sgp_get_last_error()` after calling `sgp_end()`. +It is also advised to leave `SOKOL_DEBUG` enabled when developing with Sokol, so you can +catch mistakes early. + +## Blend modes + +The library supports the most usual blend modes used in 2D, which are the following: + +- `SGP_BLENDMODE_NONE` - No blending (`dstRGBA = srcRGBA`). +- `SGP_BLENDMODE_BLEND` - Alpha blending (`dstRGB = (srcRGB * srcA) + (dstRGB * (1-srcA))` and `dstA = srcA + (dstA * (1-srcA))`) +- `SGP_BLENDMODE_BLEND_PREMULTIPLIED` - Pre-multiplied alpha blending (`dstRGBA = srcRGBA + (dstRGBA * (1-srcA))`) +- `SGP_BLENDMODE_ADD` - Additive blending (`dstRGB = (srcRGB * srcA) + dstRGB` and `dstA = dstA`) +- `SGP_BLENDMODE_ADD_PREMULTIPLIED` - Pre-multiplied additive blending (`dstRGB = srcRGB + dstRGB` and `dstA = dstA`) +- `SGP_BLENDMODE_MOD` - Color modulate (`dstRGB = srcRGB * dstRGB` and `dstA = dstA`) +- `SGP_BLENDMODE_MUL` - Color multiply (`dstRGB = (srcRGB * dstRGB) + (dstRGB * (1-srcA))` and `dstA = (srcA * dstA) + (dstA * (1-srcA))`) + +## Changing 2D coordinate system + +You can change the screen area to draw by calling `sgp_viewport(x, y, width, height)`. +You can change the coordinate system of the 2D space by calling `sgp_project(left, right, top, bottom)`, +with it. + +## Transforming 2D space + +You can translate, rotate or scale the 2D space before a draw call, by using the transformation +functions the library provides, such as `sgp_translate(x, y)`, `sgp_rotate(theta)`, etc. +Check the cheat sheet or the header for more. + +To save and restore the transformation state you should call `sgp_push_transform()` and +later `sgp_pop_transform()`. + +## Drawing primitives + +The library provides drawing functions for all the basic primitives, that is, +for points, lines, triangles and rectangles, such as `sgp_draw_line()` and `sgp_draw_filled_rect()`. +Check the cheat sheet or the header for more. +All of them have batched variations. + +## Drawing textured primitives + +To draw textured rectangles you can use `sgp_set_image(0, img)` and then sgp_draw_filled_rect()`, +this will draw an entire texture into a rectangle. +You should later reset the image with `sgp_reset_image(0)` to restore the bound image to default white image, +otherwise you will have glitches when drawing a solid color. + +In case you want to draw a specific source from the texture, +you should use `sgp_draw_textured_rect()` instead. + +By default textures are drawn using a simple nearest filter sampler, +you can change the sampler with `sgp_set_sampler(0, smp)` before drawing a texture, +it's recommended to restore the default sampler using `sgp_reset_sampler(0)`. + +## Color modulation + +All common pipelines have color modulation, and you can modulate +a color before a draw by setting the current state color with `sgp_set_color(r,g,b,a)`, +later you should reset the color to default (white) with `sgp_reset_color()`. + +## Custom shaders + +When using a custom shader, you must create a pipeline for it with `sgp_make_pipeline(desc)`, +using shader, blend mode and a draw primitive associated with it. Then you should +call `sgp_set_pipeline()` before the shader draw call. You are responsible for using +the same blend mode and drawing primitive as the created pipeline. + +Custom uniforms can be passed to the shader with `sgp_set_uniform(vs_data, vs_size, fs_data, fs_size)`, +where you should always pass a pointer to a struct with exactly the same schema and size +as the one defined in the vertex and fragment shaders. + +Although you can create custom shaders for each graphics backend manually, +it is advised should use the Sokol shader compiler [SHDC](https://github.com/floooh/sokol-tools/blob/master/docs/sokol-shdc.md), +because it can generate shaders for multiple backends from a single `.glsl` file, +and this usually works well. + +By default the library uniform buffer per draw call has just 8 float uniforms +(`SGP_UNIFORM_CONTENT_SLOTS` configuration), and that may be too low to use with custom shaders. +This is the default because typically newcomers may not want to use custom 2D shaders, +and increasing a larger value means more overhead. +If you are using custom shaders please increase this value to be large enough to hold +the number of uniforms of your largest shader. + +## Library configuration + +The following macros can be defined before including to change the library behavior: + +- `SGP_BATCH_OPTIMIZER_DEPTH` - Number of draw commands that the batch optimizer looks back at. Default is 8. +- `SGP_UNIFORM_CONTENT_SLOTS` - Maximum number of floats that can be stored in each draw call uniform buffer. Default is 8. +- `SGP_TEXTURE_SLOTS` - Maximum number of textures that can be bound per draw call. Default is 4. + +## License + +MIT, see LICENSE file or the end of `sokol_gp.h` file. +*/ + +#if defined(SOKOL_IMPL) && !defined(SOKOL_GP_IMPL) +#define SOKOL_GP_IMPL +#endif + +#ifndef SOKOL_GP_INCLUDED +#define SOKOL_GP_INCLUDED 1 + +#ifndef SOKOL_GFX_INCLUDED +#error "Please include sokol_gfx.h before sokol_gp.h" +#endif + +/* Number of draw commands that the batch optimizer looks back at. +8 is a fair default value, but could be tuned per application. +1 makes the batch optimizer try to merge only the very last draw call. +0 disables the batch optimizer +*/ +#ifndef SGP_BATCH_OPTIMIZER_DEPTH +#define SGP_BATCH_OPTIMIZER_DEPTH 0 +#endif + +/* Number of uniform floats (4-bytes) slots that can be set in a shader. +Increase this value if you need to use shader with many uniforms. +*/ +#ifndef SGP_UNIFORM_CONTENT_SLOTS +#define SGP_UNIFORM_CONTENT_SLOTS 8 +#endif + +/* Number of texture slots that can be bound in a pipeline. */ +#ifndef SGP_TEXTURE_SLOTS +#define SGP_TEXTURE_SLOTS 4 +#endif + +#if defined(SOKOL_API_DECL) && !defined(SOKOL_GP_API_DECL) +#define SOKOL_GP_API_DECL SOKOL_API_DECL +#endif +#ifndef SOKOL_GP_API_DECL +#if defined(_WIN32) && defined(SOKOL_DLL) && defined(SOKOL_GP_IMPL) +#define SOKOL_GP_API_DECL __declspec(dllexport) +#elif defined(_WIN32) && defined(SOKOL_DLL) +#define SOKOL_GP_API_DECL __declspec(dllimport) +#else +#define SOKOL_GP_API_DECL extern +#endif +#endif + +#ifndef SOKOL_LOG + #ifdef SOKOL_DEBUG + #include + #define SOKOL_LOG(s) { SOKOL_ASSERT(s); puts(s); } + #else + #define SOKOL_LOG(s) + #endif +#endif + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* List of possible error codes. */ +typedef enum sgp_error { + SGP_NO_ERROR = 0, + SGP_ERROR_SOKOL_INVALID, + SGP_ERROR_VERTICES_FULL, + SGP_ERROR_UNIFORMS_FULL, + SGP_ERROR_COMMANDS_FULL, + SGP_ERROR_VERTICES_OVERFLOW, + SGP_ERROR_TRANSFORM_STACK_OVERFLOW, + SGP_ERROR_TRANSFORM_STACK_UNDERFLOW, + SGP_ERROR_STATE_STACK_OVERFLOW, + SGP_ERROR_STATE_STACK_UNDERFLOW, + SGP_ERROR_ALLOC_FAILED, + SGP_ERROR_MAKE_VERTEX_BUFFER_FAILED, + SGP_ERROR_MAKE_WHITE_IMAGE_FAILED, + SGP_ERROR_MAKE_NEAREST_SAMPLER_FAILED, + SGP_ERROR_MAKE_COMMON_SHADER_FAILED, + SGP_ERROR_MAKE_COMMON_PIPELINE_FAILED, +} sgp_error; + +/* Blend modes. */ +typedef enum sgp_blend_mode { + SGP_BLENDMODE_NONE = 0, /* No blending + dstRGBA = srcRGBA */ + SGP_BLENDMODE_BLEND, /* Alpha blending. + dstRGB = (srcRGB * srcA) + (dstRGB * (1-srcA)) + dstA = srcA + (dstA * (1-srcA)) */ + SGP_BLENDMODE_BLEND_PREMULTIPLIED, /* Pre-multiplied alpha blending. + dstRGBA = srcRGBA + (dstRGBA * (1-srcA)) */ + SGP_BLENDMODE_ADD, /* Additive blending. + dstRGB = (srcRGB * srcA) + dstRGB + dstA = dstA */ + SGP_BLENDMODE_ADD_PREMULTIPLIED, /* Pre-multiplied additive blending. + dstRGB = srcRGB + dstRGB + dstA = dstA */ + SGP_BLENDMODE_MOD, /* Color modulate. + dstRGB = srcRGB * dstRGB + dstA = dstA */ + SGP_BLENDMODE_MUL, /* Color multiply. + dstRGB = (srcRGB * dstRGB) + (dstRGB * (1-srcA)) + dstA = (srcA * dstA) + (dstA * (1-srcA)) */ + _SGP_BLENDMODE_NUM +} sgp_blend_mode; + +typedef enum sgp_vs_attr_location { + SGP_VS_ATTR_COORD = 0, + SGP_VS_ATTR_COLOR = 1 +} sgp_vs_attr_location; + +typedef enum sgp_uniform_slot { + SGP_UNIFORM_SLOT_VERTEX = 0, + SGP_UNIFORM_SLOT_FRAGMENT = 1 +} sgp_uniform_slot; + +typedef struct sgp_isize { + int w, h; +} sgp_isize; + +typedef struct sgp_irect { + int x, y, w, h; +} sgp_irect; + +typedef struct sgp_rect { + float x, y, w, h; +} sgp_rect; + +typedef struct sgp_textured_rect { + sgp_rect dst; + sgp_rect src; +} sgp_textured_rect; + +typedef struct sgp_vec2 { + float x, y; +} sgp_vec2; + +typedef sgp_vec2 sgp_point; + +typedef struct sgp_line { + sgp_point a, b; +} sgp_line; + +typedef struct sgp_triangle { + sgp_point a, b, c; +} sgp_triangle; + +typedef struct sgp_mat2x3 { + float v[2][3]; +} sgp_mat2x3; + +typedef struct sgp_color { + float r, g, b, a; +} sgp_color; + +typedef struct sgp_color_ub4 { + uint8_t r, g, b, a; +} sgp_color_ub4; + +typedef struct sgp_vertex { + sgp_vec2 position; + sgp_vec2 texcoord; + sgp_color_ub4 color; +} sgp_vertex; + +typedef union sgp_uniform_data { + float floats[SGP_UNIFORM_CONTENT_SLOTS]; + uint8_t bytes[SGP_UNIFORM_CONTENT_SLOTS * sizeof(float)]; +} sgp_uniform_data; + +typedef struct sgp_uniform { + uint16_t vs_size; + uint16_t fs_size; + sgp_uniform_data data; +} sgp_uniform; + +typedef struct sgp_textures_uniform { + uint32_t count; + sg_image images[SGP_TEXTURE_SLOTS]; + sg_sampler samplers[SGP_TEXTURE_SLOTS]; +} sgp_textures_uniform; + +/* SGP draw state. */ +typedef struct sgp_state { + sgp_isize frame_size; + sgp_irect viewport; + sgp_irect scissor; + sgp_mat2x3 proj; + sgp_mat2x3 transform; + sgp_mat2x3 mvp; + float thickness; + sgp_color_ub4 color; + sgp_textures_uniform textures; + sgp_uniform uniform; + sgp_blend_mode blend_mode; + sg_pipeline pipeline; + uint32_t _base_vertex; + uint32_t _base_uniform; + uint32_t _base_command; +} sgp_state; + +/* Structure that defines SGP setup parameters. */ +typedef struct sgp_desc { + uint32_t max_vertices; + uint32_t max_commands; + sg_pixel_format color_format; /* Color format for creating pipelines, defaults to the same as the Sokol GFX context. */ + sg_pixel_format depth_format; /* Depth format for creating pipelines, defaults to the same as the Sokol GFX context. */ + int sample_count; /* Sample count for creating pipelines, defaults to the same as the Sokol GFX context. */ +} sgp_desc; + +/* Structure that defines SGP custom pipeline creation parameters. */ +typedef struct sgp_pipeline_desc { + sg_shader shader; /* Sokol shader. */ + sg_primitive_type primitive_type; /* Draw primitive type (triangles, lines, points, etc). Default is triangles. */ + sgp_blend_mode blend_mode; /* Color blend mode. Default is no blend. */ + sg_pixel_format color_format; /* Color format, defaults to the value used when creating Sokol GP context. */ + sg_pixel_format depth_format; /* Depth format, defaults to the value used when creating Sokol GP context. */ + int sample_count; /* Sample count, defaults to the value used when creating Sokol GP context. */ + bool has_vs_color; /* If true, the current color state will be passed as an attribute to the vertex shader. */ +} sgp_pipeline_desc; + +/* Initialization and de-initialization. */ +SOKOL_GP_API_DECL void sgp_setup(const sgp_desc* desc); /* Initializes the SGP context, and should be called after `sg_setup`. */ +SOKOL_GP_API_DECL void sgp_shutdown(void); /* Destroys the SGP context. */ +SOKOL_GP_API_DECL bool sgp_is_valid(void); /* Checks if SGP context is valid, should be checked after `sgp_setup`. */ + +/* Error handling. */ +SOKOL_GP_API_DECL sgp_error sgp_get_last_error(void); /* Returns last SGP error. */ +SOKOL_GP_API_DECL const char* sgp_get_error_message(sgp_error error); /* Returns a message with SGP error description. */ + +/* Custom pipeline creation. */ +SOKOL_GP_API_DECL sg_pipeline sgp_make_pipeline(const sgp_pipeline_desc* desc); /* Creates a custom shader pipeline to be used with SGP. */ + +/* Draw command queue management. */ +SOKOL_GP_API_DECL void sgp_begin(int width, int height); /* Begins a new SGP draw command queue. */ +SOKOL_GP_API_DECL void sgp_flush(void); /* Dispatch current Sokol GFX draw commands. */ +SOKOL_GP_API_DECL void sgp_end(void); /* End current draw command queue, discarding it. */ + +/* 2D coordinate space projection */ +SOKOL_GP_API_DECL void sgp_project(float left, float right, float top, float bottom); /* Set the coordinate space boundary in the current viewport. */ +SOKOL_GP_API_DECL void sgp_reset_project(void); /* Resets the coordinate space to default (coordinate of the viewport). */ + +/* 2D coordinate space transformation. */ +SOKOL_GP_API_DECL void sgp_push_transform(void); /* Saves current transform matrix, to be restored later with a pop. */ +SOKOL_GP_API_DECL void sgp_pop_transform(void); /* Restore transform matrix to the same value of the last push. */ +SOKOL_GP_API_DECL void sgp_reset_transform(void); /* Resets the transform matrix to identity (no transform). */ +SOKOL_GP_API_DECL void sgp_translate(float x, float y); /* Translates the 2D coordinate space. */ +SOKOL_GP_API_DECL void sgp_rotate(float theta); /* Rotates the 2D coordinate space around the origin. */ +SOKOL_GP_API_DECL void sgp_rotate_at(float theta, float x, float y); /* Rotates the 2D coordinate space around a point. */ +SOKOL_GP_API_DECL void sgp_scale(float sx, float sy); /* Scales the 2D coordinate space around the origin. */ +SOKOL_GP_API_DECL void sgp_scale_at(float sx, float sy, float x, float y); /* Scales the 2D coordinate space around a point. */ + +/* State change for custom pipelines. */ +SOKOL_GP_API_DECL void sgp_set_pipeline(sg_pipeline pipeline); /* Sets current draw pipeline. */ +SOKOL_GP_API_DECL void sgp_reset_pipeline(void); /* Resets to the current draw pipeline to default (builtin pipelines). */ +SOKOL_GP_API_DECL void sgp_set_uniform(const void* vs_data, uint32_t vs_size, const void *fs_data, uint32_t fs_size); /* Sets uniform buffer for a custom pipeline. */ +SOKOL_GP_API_DECL void sgp_reset_uniform(void); /* Resets uniform buffer to default (current state color). */ + +/* State change functions for the common pipelines. */ +SOKOL_GP_API_DECL void sgp_set_blend_mode(sgp_blend_mode blend_mode); /* Sets current blend mode. */ +SOKOL_GP_API_DECL void sgp_reset_blend_mode(void); /* Resets current blend mode to default (no blending). */ +SOKOL_GP_API_DECL void sgp_set_color(float r, float g, float b, float a); /* Sets current color modulation. */ +SOKOL_GP_API_DECL void sgp_reset_color(void); /* Resets current color modulation to default (white). */ +SOKOL_GP_API_DECL void sgp_set_image(int channel, sg_image image); /* Sets current bound image in a texture channel. */ +SOKOL_GP_API_DECL void sgp_unset_image(int channel); /* Remove current bound image in a texture channel (no texture). */ +SOKOL_GP_API_DECL void sgp_reset_image(int channel); /* Resets current bound image in a texture channel to default (white texture). */ +SOKOL_GP_API_DECL void sgp_set_sampler(int channel, sg_sampler sampler); /* Sets current bound sampler in a texture channel. */ +SOKOL_GP_API_DECL void sgp_reset_sampler(int channel); /* Resets current bound sampler in a texture channel to default (nearest sampler). */ + +/* State change functions for all pipelines. */ +SOKOL_GP_API_DECL void sgp_viewport(int x, int y, int w, int h); /* Sets the screen area to draw into. */ +SOKOL_GP_API_DECL void sgp_reset_viewport(void); /* Reset viewport to default values (0, 0, width, height). */ +SOKOL_GP_API_DECL void sgp_scissor(int x, int y, int w, int h); /* Set clip rectangle in the viewport. */ +SOKOL_GP_API_DECL void sgp_reset_scissor(void); /* Resets clip rectangle to default (viewport bounds). */ +SOKOL_GP_API_DECL void sgp_reset_state(void); /* Reset all state to default values. */ + +/* Drawing functions. */ +SOKOL_GP_API_DECL void sgp_clear(void); /* Clears the current viewport using the current state color. */ +SOKOL_GP_API_DECL void sgp_draw(sg_primitive_type primitive_type, const sgp_vertex* vertices, uint32_t count); /* Low level drawing function, capable of drawing any primitive. */ +SOKOL_GP_API_DECL void sgp_draw_points(const sgp_point* points, uint32_t count); /* Draws points in a batch. */ +SOKOL_GP_API_DECL void sgp_draw_point(float x, float y); /* Draws a single point. */ +SOKOL_GP_API_DECL void sgp_draw_lines(const sgp_line* lines, uint32_t count); /* Draws lines in a batch. */ +SOKOL_GP_API_DECL void sgp_draw_line(float ax, float ay, float bx, float by); /* Draws a single line. */ +SOKOL_GP_API_DECL void sgp_draw_lines_strip(const sgp_point* points, uint32_t count); /* Draws a strip of lines. */ +SOKOL_GP_API_DECL void sgp_draw_filled_triangles(const sgp_triangle* triangles, uint32_t count); /* Draws triangles in a batch. */ +SOKOL_GP_API_DECL void sgp_draw_filled_triangle(float ax, float ay, float bx, float by, float cx, float cy); /* Draws a single triangle. */ +SOKOL_GP_API_DECL void sgp_draw_filled_triangles_strip(const sgp_point* points, uint32_t count); /* Draws strip of triangles. */ +SOKOL_GP_API_DECL void sgp_draw_filled_rects(const sgp_rect* rects, uint32_t count); /* Draws a batch of rectangles. */ +SOKOL_GP_API_DECL void sgp_draw_filled_rect(float x, float y, float w, float h); /* Draws a single rectangle. */ +SOKOL_GP_API_DECL void sgp_draw_textured_rects(int channel, const sgp_textured_rect* rects, uint32_t count); /* Draws a batch textured rectangle, each from a source region. */ +SOKOL_GP_API_DECL void sgp_draw_textured_rect(int channel, sgp_rect dest_rect, sgp_rect src_rect); /* Draws a single textured rectangle from a source region. */ + +/* Querying functions. */ +SOKOL_GP_API_DECL sgp_state* sgp_query_state(void); /* Returns the current draw state. */ +SOKOL_GP_API_DECL sgp_desc sgp_query_desc(void); /* Returns description of the current SGP context. */ + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // SOKOL_GP_INCLUDED + +#ifdef SOKOL_GP_IMPL +#ifndef SOKOL_GP_IMPL_INCLUDED +#define SOKOL_GP_IMPL_INCLUDED + +#ifndef SOKOL_GFX_IMPL_INCLUDED +#error "Please include sokol_gfx.h implementation before sokol_gp.h implementation" +#endif + +#include +#include +#include + +#ifndef SOKOL_LIKELY +#ifdef __GNUC__ +#define SOKOL_LIKELY(x) __builtin_expect(x, 1) +#define SOKOL_UNLIKELY(x) __builtin_expect(x, 0) +#else +#define SOKOL_LIKELY(x) (x) +#define SOKOL_UNLIKELY(x) (x) +#endif +#endif + +#define _SGP_IMPOSSIBLE_ID 0xffffffffU + +enum { + _SGP_INIT_COOKIE = 0xCAFED0D, + _SGP_DEFAULT_MAX_VERTICES = 65536, + _SGP_DEFAULT_MAX_COMMANDS = 16384, + _SGP_MAX_MOVE_VERTICES = 96, + _SGP_MAX_STACK_DEPTH = 64 +}; + +typedef struct _sgp_region { + float x1, y1, x2, y2; +} _sgp_region; + +typedef struct _sgp_draw_args { + sg_pipeline pip; + sgp_textures_uniform textures; + _sgp_region region; + uint32_t uniform_index; + uint32_t vertex_index; + uint32_t num_vertices; +} _sgp_draw_args; + +typedef union _sgp_command_args { + _sgp_draw_args draw; + sgp_irect viewport; + sgp_irect scissor; +} _sgp_command_args; + +typedef enum _sgp_command_type { + SGP_COMMAND_NONE = 0, + SGP_COMMAND_DRAW, + SGP_COMMAND_VIEWPORT, + SGP_COMMAND_SCISSOR +} _sgp_command_type; + +typedef struct _sgp_command { + _sgp_command_type cmd; + _sgp_command_args args; +} _sgp_command; + +typedef struct _sgp_context { + uint32_t init_cookie; + sgp_error last_error; + sgp_desc desc; + + // resources + sg_shader shader; + sg_buffer vertex_buf; + sg_image white_img; + sg_sampler nearest_smp; + sg_pipeline pipelines[_SG_PRIMITIVETYPE_NUM * _SGP_BLENDMODE_NUM]; + + // command queue + uint32_t cur_vertex; + uint32_t cur_uniform; + uint32_t cur_command; + uint32_t num_vertices; + uint32_t num_uniforms; + uint32_t num_commands; + sgp_vertex* vertices; + sgp_uniform* uniforms; + _sgp_command* commands; + + // state tracking + sgp_state state; + + // matrix stack + uint32_t cur_transform; + uint32_t cur_state; + sgp_mat2x3 transform_stack[_SGP_MAX_STACK_DEPTH]; + sgp_state state_stack[_SGP_MAX_STACK_DEPTH]; +} _sgp_context; + +static _sgp_context _sgp; + +static const sgp_mat2x3 _sgp_mat3_identity = {{ + {1.0f, 0.0f, 0.0f}, + {0.0f, 1.0f, 0.0f} +}}; + +static const sgp_color_ub4 _sgp_white_color = {255, 255, 255, 255}; + +//////////////////////////////////////////////////////////////////////////////// +// Shaders + +/* + #version 410 + + layout(location = 0) in vec4 coord; + layout(location = 0) out vec2 texUV; + layout(location = 1) out vec4 iColor; + layout(location = 1) in vec4 color; + + void main() + { + gl_Position = vec4(coord.xy, 0.0, 1.0); + gl_PointSize = 1.0; + texUV = coord.zw; + iColor = color; + } + +*/ +static const uint8_t sgp_vs_source_glsl410[290] = { + 0x23,0x76,0x65,0x72,0x73,0x69,0x6f,0x6e,0x20,0x34,0x31,0x30,0x0a,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,0x63,0x6f,0x6f,0x72,0x64, + 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,0x32,0x20, + 0x74,0x65,0x78,0x55,0x56,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,0x69,0x43,0x6f,0x6c,0x6f,0x72,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,0x67,0x6c,0x5f,0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x20, + 0x3d,0x20,0x76,0x65,0x63,0x34,0x28,0x63,0x6f,0x6f,0x72,0x64,0x2e,0x78,0x79,0x2c, + 0x20,0x30,0x2e,0x30,0x2c,0x20,0x31,0x2e,0x30,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20, + 0x67,0x6c,0x5f,0x50,0x6f,0x69,0x6e,0x74,0x53,0x69,0x7a,0x65,0x20,0x3d,0x20,0x31, + 0x2e,0x30,0x3b,0x0a,0x20,0x20,0x20,0x20,0x74,0x65,0x78,0x55,0x56,0x20,0x3d,0x20, + 0x63,0x6f,0x6f,0x72,0x64,0x2e,0x7a,0x77,0x3b,0x0a,0x20,0x20,0x20,0x20,0x69,0x43, + 0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x7d,0x0a, + 0x0a,0x00, +}; +/* + #version 410 + + uniform sampler2D iTexChannel0_iSmpChannel0; + + layout(location = 0) out vec4 fragColor; + layout(location = 0) in vec2 texUV; + layout(location = 1) in vec4 iColor; + + void main() + { + fragColor = texture(iTexChannel0_iSmpChannel0, texUV) * iColor; + } + +*/ +static const uint8_t sgp_fs_source_glsl410[261] = { + 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, + 0x69,0x54,0x65,0x78,0x43,0x68,0x61,0x6e,0x6e,0x65,0x6c,0x30,0x5f,0x69,0x53,0x6d, + 0x70,0x43,0x68,0x61,0x6e,0x6e,0x65,0x6c,0x30,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,0x43,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, + 0x32,0x20,0x74,0x65,0x78,0x55,0x56,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,0x69,0x43,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,0x43,0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20,0x74,0x65,0x78, + 0x74,0x75,0x72,0x65,0x28,0x69,0x54,0x65,0x78,0x43,0x68,0x61,0x6e,0x6e,0x65,0x6c, + 0x30,0x5f,0x69,0x53,0x6d,0x70,0x43,0x68,0x61,0x6e,0x6e,0x65,0x6c,0x30,0x2c,0x20, + 0x74,0x65,0x78,0x55,0x56,0x29,0x20,0x2a,0x20,0x69,0x43,0x6f,0x6c,0x6f,0x72,0x3b, + 0x0a,0x7d,0x0a,0x0a,0x00, +}; +/* + #version 300 es + + layout(location = 0) in vec4 coord; + out vec2 texUV; + out vec4 iColor; + layout(location = 1) in vec4 color; + + void main() + { + gl_Position = vec4(coord.xy, 0.0, 1.0); + gl_PointSize = 1.0; + texUV = coord.zw; + iColor = color; + } + +*/ +static const uint8_t sgp_vs_source_glsl300es[251] = { + 0x23,0x76,0x65,0x72,0x73,0x69,0x6f,0x6e,0x20,0x33,0x30,0x30,0x20,0x65,0x73,0x0a, + 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,0x63,0x6f, + 0x6f,0x72,0x64,0x3b,0x0a,0x6f,0x75,0x74,0x20,0x76,0x65,0x63,0x32,0x20,0x74,0x65, + 0x78,0x55,0x56,0x3b,0x0a,0x6f,0x75,0x74,0x20,0x76,0x65,0x63,0x34,0x20,0x69,0x43, + 0x6f,0x6c,0x6f,0x72,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,0x67,0x6c,0x5f, + 0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x76,0x65,0x63,0x34,0x28, + 0x63,0x6f,0x6f,0x72,0x64,0x2e,0x78,0x79,0x2c,0x20,0x30,0x2e,0x30,0x2c,0x20,0x31, + 0x2e,0x30,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x67,0x6c,0x5f,0x50,0x6f,0x69,0x6e, + 0x74,0x53,0x69,0x7a,0x65,0x20,0x3d,0x20,0x31,0x2e,0x30,0x3b,0x0a,0x20,0x20,0x20, + 0x20,0x74,0x65,0x78,0x55,0x56,0x20,0x3d,0x20,0x63,0x6f,0x6f,0x72,0x64,0x2e,0x7a, + 0x77,0x3b,0x0a,0x20,0x20,0x20,0x20,0x69,0x43,0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20, + 0x63,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x7d,0x0a,0x0a,0x00, +}; +/* + #version 300 es + precision mediump float; + precision highp int; + + uniform highp sampler2D iTexChannel0_iSmpChannel0; + + layout(location = 0) out highp vec4 fragColor; + in highp vec2 texUV; + in highp vec4 iColor; + + void main() + { + fragColor = texture(iTexChannel0_iSmpChannel0, texUV) * iColor; + } + +*/ +static const uint8_t sgp_fs_source_glsl300es[292] = { + 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,0x69,0x54,0x65,0x78,0x43,0x68,0x61,0x6e,0x6e, + 0x65,0x6c,0x30,0x5f,0x69,0x53,0x6d,0x70,0x43,0x68,0x61,0x6e,0x6e,0x65,0x6c,0x30, + 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,0x43,0x6f,0x6c,0x6f,0x72, + 0x3b,0x0a,0x69,0x6e,0x20,0x68,0x69,0x67,0x68,0x70,0x20,0x76,0x65,0x63,0x32,0x20, + 0x74,0x65,0x78,0x55,0x56,0x3b,0x0a,0x69,0x6e,0x20,0x68,0x69,0x67,0x68,0x70,0x20, + 0x76,0x65,0x63,0x34,0x20,0x69,0x43,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,0x43,0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20,0x74,0x65,0x78,0x74, + 0x75,0x72,0x65,0x28,0x69,0x54,0x65,0x78,0x43,0x68,0x61,0x6e,0x6e,0x65,0x6c,0x30, + 0x5f,0x69,0x53,0x6d,0x70,0x43,0x68,0x61,0x6e,0x6e,0x65,0x6c,0x30,0x2c,0x20,0x74, + 0x65,0x78,0x55,0x56,0x29,0x20,0x2a,0x20,0x69,0x43,0x6f,0x6c,0x6f,0x72,0x3b,0x0a, + 0x7d,0x0a,0x0a,0x00, +}; +/* + static float4 gl_Position; + static float gl_PointSize; + static float4 coord; + static float2 texUV; + static float4 iColor; + static float4 color; + + struct SPIRV_Cross_Input + { + float4 coord : TEXCOORD0; + float4 color : TEXCOORD1; + }; + + struct SPIRV_Cross_Output + { + float2 texUV : TEXCOORD0; + float4 iColor : TEXCOORD1; + float4 gl_Position : SV_Position; + }; + + void vert_main() + { + gl_Position = float4(coord.xy, 0.0f, 1.0f); + gl_PointSize = 1.0f; + texUV = coord.zw; + iColor = color; + } + + SPIRV_Cross_Output main(SPIRV_Cross_Input stage_input) + { + coord = stage_input.coord; + color = stage_input.color; + vert_main(); + SPIRV_Cross_Output stage_output; + stage_output.gl_Position = gl_Position; + stage_output.texUV = texUV; + stage_output.iColor = iColor; + return stage_output; + } +*/ +static const uint8_t sgp_vs_source_hlsl4[810] = { + 0x73,0x74,0x61,0x74,0x69,0x63,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20,0x67,0x6c, + 0x5f,0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x3b,0x0a,0x73,0x74,0x61,0x74,0x69, + 0x63,0x20,0x66,0x6c,0x6f,0x61,0x74,0x20,0x67,0x6c,0x5f,0x50,0x6f,0x69,0x6e,0x74, + 0x53,0x69,0x7a,0x65,0x3b,0x0a,0x73,0x74,0x61,0x74,0x69,0x63,0x20,0x66,0x6c,0x6f, + 0x61,0x74,0x34,0x20,0x63,0x6f,0x6f,0x72,0x64,0x3b,0x0a,0x73,0x74,0x61,0x74,0x69, + 0x63,0x20,0x66,0x6c,0x6f,0x61,0x74,0x32,0x20,0x74,0x65,0x78,0x55,0x56,0x3b,0x0a, + 0x73,0x74,0x61,0x74,0x69,0x63,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20,0x69,0x43, + 0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x73,0x74,0x61,0x74,0x69,0x63,0x20,0x66,0x6c,0x6f, + 0x61,0x74,0x34,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x0a,0x73,0x74,0x72,0x75, + 0x63,0x74,0x20,0x53,0x50,0x49,0x52,0x56,0x5f,0x43,0x72,0x6f,0x73,0x73,0x5f,0x49, + 0x6e,0x70,0x75,0x74,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74, + 0x34,0x20,0x63,0x6f,0x6f,0x72,0x64,0x20,0x3a,0x20,0x54,0x45,0x58,0x43,0x4f,0x4f, + 0x52,0x44,0x30,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20, + 0x63,0x6f,0x6c,0x6f,0x72,0x20,0x3a,0x20,0x54,0x45,0x58,0x43,0x4f,0x4f,0x52,0x44, + 0x31,0x3b,0x0a,0x7d,0x3b,0x0a,0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x53,0x50, + 0x49,0x52,0x56,0x5f,0x43,0x72,0x6f,0x73,0x73,0x5f,0x4f,0x75,0x74,0x70,0x75,0x74, + 0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x32,0x20,0x74,0x65, + 0x78,0x55,0x56,0x20,0x3a,0x20,0x54,0x45,0x58,0x43,0x4f,0x4f,0x52,0x44,0x30,0x3b, + 0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20,0x69,0x43,0x6f,0x6c, + 0x6f,0x72,0x20,0x3a,0x20,0x54,0x45,0x58,0x43,0x4f,0x4f,0x52,0x44,0x31,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,0x3a,0x20,0x53,0x56,0x5f,0x50,0x6f,0x73,0x69, + 0x74,0x69,0x6f,0x6e,0x3b,0x0a,0x7d,0x3b,0x0a,0x0a,0x76,0x6f,0x69,0x64,0x20,0x76, + 0x65,0x72,0x74,0x5f,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,0x66, + 0x6c,0x6f,0x61,0x74,0x34,0x28,0x63,0x6f,0x6f,0x72,0x64,0x2e,0x78,0x79,0x2c,0x20, + 0x30,0x2e,0x30,0x66,0x2c,0x20,0x31,0x2e,0x30,0x66,0x29,0x3b,0x0a,0x20,0x20,0x20, + 0x20,0x67,0x6c,0x5f,0x50,0x6f,0x69,0x6e,0x74,0x53,0x69,0x7a,0x65,0x20,0x3d,0x20, + 0x31,0x2e,0x30,0x66,0x3b,0x0a,0x20,0x20,0x20,0x20,0x74,0x65,0x78,0x55,0x56,0x20, + 0x3d,0x20,0x63,0x6f,0x6f,0x72,0x64,0x2e,0x7a,0x77,0x3b,0x0a,0x20,0x20,0x20,0x20, + 0x69,0x43,0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x3b,0x0a, + 0x7d,0x0a,0x0a,0x53,0x50,0x49,0x52,0x56,0x5f,0x43,0x72,0x6f,0x73,0x73,0x5f,0x4f, + 0x75,0x74,0x70,0x75,0x74,0x20,0x6d,0x61,0x69,0x6e,0x28,0x53,0x50,0x49,0x52,0x56, + 0x5f,0x43,0x72,0x6f,0x73,0x73,0x5f,0x49,0x6e,0x70,0x75,0x74,0x20,0x73,0x74,0x61, + 0x67,0x65,0x5f,0x69,0x6e,0x70,0x75,0x74,0x29,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20, + 0x63,0x6f,0x6f,0x72,0x64,0x20,0x3d,0x20,0x73,0x74,0x61,0x67,0x65,0x5f,0x69,0x6e, + 0x70,0x75,0x74,0x2e,0x63,0x6f,0x6f,0x72,0x64,0x3b,0x0a,0x20,0x20,0x20,0x20,0x63, + 0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20,0x73,0x74,0x61,0x67,0x65,0x5f,0x69,0x6e,0x70, + 0x75,0x74,0x2e,0x63,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x20,0x20,0x20,0x20,0x76,0x65, + 0x72,0x74,0x5f,0x6d,0x61,0x69,0x6e,0x28,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x53, + 0x50,0x49,0x52,0x56,0x5f,0x43,0x72,0x6f,0x73,0x73,0x5f,0x4f,0x75,0x74,0x70,0x75, + 0x74,0x20,0x73,0x74,0x61,0x67,0x65,0x5f,0x6f,0x75,0x74,0x70,0x75,0x74,0x3b,0x0a, + 0x20,0x20,0x20,0x20,0x73,0x74,0x61,0x67,0x65,0x5f,0x6f,0x75,0x74,0x70,0x75,0x74, + 0x2e,0x67,0x6c,0x5f,0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x67, + 0x6c,0x5f,0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x3b,0x0a,0x20,0x20,0x20,0x20, + 0x73,0x74,0x61,0x67,0x65,0x5f,0x6f,0x75,0x74,0x70,0x75,0x74,0x2e,0x74,0x65,0x78, + 0x55,0x56,0x20,0x3d,0x20,0x74,0x65,0x78,0x55,0x56,0x3b,0x0a,0x20,0x20,0x20,0x20, + 0x73,0x74,0x61,0x67,0x65,0x5f,0x6f,0x75,0x74,0x70,0x75,0x74,0x2e,0x69,0x43,0x6f, + 0x6c,0x6f,0x72,0x20,0x3d,0x20,0x69,0x43,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x20,0x20, + 0x20,0x20,0x72,0x65,0x74,0x75,0x72,0x6e,0x20,0x73,0x74,0x61,0x67,0x65,0x5f,0x6f, + 0x75,0x74,0x70,0x75,0x74,0x3b,0x0a,0x7d,0x0a,0x00, +}; +/* + Texture2D iTexChannel0 : register(t0); + SamplerState iSmpChannel0 : register(s0); + + static float4 fragColor; + static float2 texUV; + static float4 iColor; + + struct SPIRV_Cross_Input + { + float2 texUV : TEXCOORD0; + float4 iColor : TEXCOORD1; + }; + + struct SPIRV_Cross_Output + { + float4 fragColor : SV_Target0; + }; + + void frag_main() + { + fragColor = iTexChannel0.Sample(iSmpChannel0, texUV) * iColor; + } + + SPIRV_Cross_Output main(SPIRV_Cross_Input stage_input) + { + texUV = stage_input.texUV; + iColor = stage_input.iColor; + frag_main(); + SPIRV_Cross_Output stage_output; + stage_output.fragColor = fragColor; + return stage_output; + } +*/ +static const uint8_t sgp_fs_source_hlsl4[650] = { + 0x54,0x65,0x78,0x74,0x75,0x72,0x65,0x32,0x44,0x3c,0x66,0x6c,0x6f,0x61,0x74,0x34, + 0x3e,0x20,0x69,0x54,0x65,0x78,0x43,0x68,0x61,0x6e,0x6e,0x65,0x6c,0x30,0x20,0x3a, + 0x20,0x72,0x65,0x67,0x69,0x73,0x74,0x65,0x72,0x28,0x74,0x30,0x29,0x3b,0x0a,0x53, + 0x61,0x6d,0x70,0x6c,0x65,0x72,0x53,0x74,0x61,0x74,0x65,0x20,0x69,0x53,0x6d,0x70, + 0x43,0x68,0x61,0x6e,0x6e,0x65,0x6c,0x30,0x20,0x3a,0x20,0x72,0x65,0x67,0x69,0x73, + 0x74,0x65,0x72,0x28,0x73,0x30,0x29,0x3b,0x0a,0x0a,0x73,0x74,0x61,0x74,0x69,0x63, + 0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20,0x66,0x72,0x61,0x67,0x43,0x6f,0x6c,0x6f, + 0x72,0x3b,0x0a,0x73,0x74,0x61,0x74,0x69,0x63,0x20,0x66,0x6c,0x6f,0x61,0x74,0x32, + 0x20,0x74,0x65,0x78,0x55,0x56,0x3b,0x0a,0x73,0x74,0x61,0x74,0x69,0x63,0x20,0x66, + 0x6c,0x6f,0x61,0x74,0x34,0x20,0x69,0x43,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x0a,0x73, + 0x74,0x72,0x75,0x63,0x74,0x20,0x53,0x50,0x49,0x52,0x56,0x5f,0x43,0x72,0x6f,0x73, + 0x73,0x5f,0x49,0x6e,0x70,0x75,0x74,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c, + 0x6f,0x61,0x74,0x32,0x20,0x74,0x65,0x78,0x55,0x56,0x20,0x3a,0x20,0x54,0x45,0x58, + 0x43,0x4f,0x4f,0x52,0x44,0x30,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61, + 0x74,0x34,0x20,0x69,0x43,0x6f,0x6c,0x6f,0x72,0x20,0x3a,0x20,0x54,0x45,0x58,0x43, + 0x4f,0x4f,0x52,0x44,0x31,0x3b,0x0a,0x7d,0x3b,0x0a,0x0a,0x73,0x74,0x72,0x75,0x63, + 0x74,0x20,0x53,0x50,0x49,0x52,0x56,0x5f,0x43,0x72,0x6f,0x73,0x73,0x5f,0x4f,0x75, + 0x74,0x70,0x75,0x74,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74, + 0x34,0x20,0x66,0x72,0x61,0x67,0x43,0x6f,0x6c,0x6f,0x72,0x20,0x3a,0x20,0x53,0x56, + 0x5f,0x54,0x61,0x72,0x67,0x65,0x74,0x30,0x3b,0x0a,0x7d,0x3b,0x0a,0x0a,0x76,0x6f, + 0x69,0x64,0x20,0x66,0x72,0x61,0x67,0x5f,0x6d,0x61,0x69,0x6e,0x28,0x29,0x0a,0x7b, + 0x0a,0x20,0x20,0x20,0x20,0x66,0x72,0x61,0x67,0x43,0x6f,0x6c,0x6f,0x72,0x20,0x3d, + 0x20,0x69,0x54,0x65,0x78,0x43,0x68,0x61,0x6e,0x6e,0x65,0x6c,0x30,0x2e,0x53,0x61, + 0x6d,0x70,0x6c,0x65,0x28,0x69,0x53,0x6d,0x70,0x43,0x68,0x61,0x6e,0x6e,0x65,0x6c, + 0x30,0x2c,0x20,0x74,0x65,0x78,0x55,0x56,0x29,0x20,0x2a,0x20,0x69,0x43,0x6f,0x6c, + 0x6f,0x72,0x3b,0x0a,0x7d,0x0a,0x0a,0x53,0x50,0x49,0x52,0x56,0x5f,0x43,0x72,0x6f, + 0x73,0x73,0x5f,0x4f,0x75,0x74,0x70,0x75,0x74,0x20,0x6d,0x61,0x69,0x6e,0x28,0x53, + 0x50,0x49,0x52,0x56,0x5f,0x43,0x72,0x6f,0x73,0x73,0x5f,0x49,0x6e,0x70,0x75,0x74, + 0x20,0x73,0x74,0x61,0x67,0x65,0x5f,0x69,0x6e,0x70,0x75,0x74,0x29,0x0a,0x7b,0x0a, + 0x20,0x20,0x20,0x20,0x74,0x65,0x78,0x55,0x56,0x20,0x3d,0x20,0x73,0x74,0x61,0x67, + 0x65,0x5f,0x69,0x6e,0x70,0x75,0x74,0x2e,0x74,0x65,0x78,0x55,0x56,0x3b,0x0a,0x20, + 0x20,0x20,0x20,0x69,0x43,0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20,0x73,0x74,0x61,0x67, + 0x65,0x5f,0x69,0x6e,0x70,0x75,0x74,0x2e,0x69,0x43,0x6f,0x6c,0x6f,0x72,0x3b,0x0a, + 0x20,0x20,0x20,0x20,0x66,0x72,0x61,0x67,0x5f,0x6d,0x61,0x69,0x6e,0x28,0x29,0x3b, + 0x0a,0x20,0x20,0x20,0x20,0x53,0x50,0x49,0x52,0x56,0x5f,0x43,0x72,0x6f,0x73,0x73, + 0x5f,0x4f,0x75,0x74,0x70,0x75,0x74,0x20,0x73,0x74,0x61,0x67,0x65,0x5f,0x6f,0x75, + 0x74,0x70,0x75,0x74,0x3b,0x0a,0x20,0x20,0x20,0x20,0x73,0x74,0x61,0x67,0x65,0x5f, + 0x6f,0x75,0x74,0x70,0x75,0x74,0x2e,0x66,0x72,0x61,0x67,0x43,0x6f,0x6c,0x6f,0x72, + 0x20,0x3d,0x20,0x66,0x72,0x61,0x67,0x43,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x20,0x20, + 0x20,0x20,0x72,0x65,0x74,0x75,0x72,0x6e,0x20,0x73,0x74,0x61,0x67,0x65,0x5f,0x6f, + 0x75,0x74,0x70,0x75,0x74,0x3b,0x0a,0x7d,0x0a,0x00, +}; +/* + #include + #include + + using namespace metal; + + struct main0_out + { + float2 texUV [[user(locn0)]]; + float4 iColor [[user(locn1)]]; + float4 gl_Position [[position]]; + float gl_PointSize [[point_size]]; + }; + + struct main0_in + { + float4 coord [[attribute(0)]]; + float4 color [[attribute(1)]]; + }; + + vertex main0_out main0(main0_in in [[stage_in]]) + { + main0_out out = {}; + out.gl_Position = float4(in.coord.xy, 0.0, 1.0); + out.gl_PointSize = 1.0; + out.texUV = in.coord.zw; + out.iColor = in.color; + return out; + } + +*/ +static const uint8_t sgp_vs_source_metal_macos[564] = { + 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,0x32,0x20,0x74,0x65,0x78,0x55,0x56,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,0x69,0x43,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,0x63,0x6f,0x6f, + 0x72,0x64,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,0x34,0x20, + 0x63,0x6f,0x6c,0x6f,0x72,0x20,0x5b,0x5b,0x61,0x74,0x74,0x72,0x69,0x62,0x75,0x74, + 0x65,0x28,0x31,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,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,0x66,0x6c,0x6f, + 0x61,0x74,0x34,0x28,0x69,0x6e,0x2e,0x63,0x6f,0x6f,0x72,0x64,0x2e,0x78,0x79,0x2c, + 0x20,0x30,0x2e,0x30,0x2c,0x20,0x31,0x2e,0x30,0x29,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,0x31,0x2e,0x30,0x3b,0x0a,0x20,0x20,0x20,0x20,0x6f,0x75,0x74,0x2e, + 0x74,0x65,0x78,0x55,0x56,0x20,0x3d,0x20,0x69,0x6e,0x2e,0x63,0x6f,0x6f,0x72,0x64, + 0x2e,0x7a,0x77,0x3b,0x0a,0x20,0x20,0x20,0x20,0x6f,0x75,0x74,0x2e,0x69,0x43,0x6f, + 0x6c,0x6f,0x72,0x20,0x3d,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, +}; +/* + #include + #include + + using namespace metal; + + struct main0_out + { + float4 fragColor [[color(0)]]; + }; + + struct main0_in + { + float2 texUV [[user(locn0)]]; + float4 iColor [[user(locn1)]]; + }; + + fragment main0_out main0(main0_in in [[stage_in]], texture2d iTexChannel0 [[texture(0)]], sampler iSmpChannel0 [[sampler(0)]]) + { + main0_out out = {}; + out.fragColor = iTexChannel0.sample(iSmpChannel0, in.texUV) * in.iColor; + return out; + } + +*/ +static const uint8_t sgp_fs_source_metal_macos[478] = { + 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,0x43,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,0x32,0x20,0x74, + 0x65,0x78,0x55,0x56,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,0x69,0x43,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,0x69,0x54,0x65,0x78,0x43,0x68,0x61,0x6e,0x6e,0x65,0x6c,0x30,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,0x69,0x53,0x6d,0x70,0x43,0x68,0x61,0x6e,0x6e,0x65, + 0x6c,0x30,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,0x43,0x6f,0x6c,0x6f,0x72,0x20,0x3d, + 0x20,0x69,0x54,0x65,0x78,0x43,0x68,0x61,0x6e,0x6e,0x65,0x6c,0x30,0x2e,0x73,0x61, + 0x6d,0x70,0x6c,0x65,0x28,0x69,0x53,0x6d,0x70,0x43,0x68,0x61,0x6e,0x6e,0x65,0x6c, + 0x30,0x2c,0x20,0x69,0x6e,0x2e,0x74,0x65,0x78,0x55,0x56,0x29,0x20,0x2a,0x20,0x69, + 0x6e,0x2e,0x69,0x43,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, +}; +/* + #include + #include + + using namespace metal; + + struct main0_out + { + float2 texUV [[user(locn0)]]; + float4 iColor [[user(locn1)]]; + float4 gl_Position [[position]]; + float gl_PointSize [[point_size]]; + }; + + struct main0_in + { + float4 coord [[attribute(0)]]; + float4 color [[attribute(1)]]; + }; + + vertex main0_out main0(main0_in in [[stage_in]]) + { + main0_out out = {}; + out.gl_Position = float4(in.coord.xy, 0.0, 1.0); + out.gl_PointSize = 1.0; + out.texUV = in.coord.zw; + out.iColor = in.color; + return out; + } + +*/ +static const uint8_t sgp_vs_source_metal_ios[564] = { + 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,0x32,0x20,0x74,0x65,0x78,0x55,0x56,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,0x69,0x43,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,0x63,0x6f,0x6f, + 0x72,0x64,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,0x34,0x20, + 0x63,0x6f,0x6c,0x6f,0x72,0x20,0x5b,0x5b,0x61,0x74,0x74,0x72,0x69,0x62,0x75,0x74, + 0x65,0x28,0x31,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,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,0x66,0x6c,0x6f, + 0x61,0x74,0x34,0x28,0x69,0x6e,0x2e,0x63,0x6f,0x6f,0x72,0x64,0x2e,0x78,0x79,0x2c, + 0x20,0x30,0x2e,0x30,0x2c,0x20,0x31,0x2e,0x30,0x29,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,0x31,0x2e,0x30,0x3b,0x0a,0x20,0x20,0x20,0x20,0x6f,0x75,0x74,0x2e, + 0x74,0x65,0x78,0x55,0x56,0x20,0x3d,0x20,0x69,0x6e,0x2e,0x63,0x6f,0x6f,0x72,0x64, + 0x2e,0x7a,0x77,0x3b,0x0a,0x20,0x20,0x20,0x20,0x6f,0x75,0x74,0x2e,0x69,0x43,0x6f, + 0x6c,0x6f,0x72,0x20,0x3d,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, +}; +/* + #include + #include + + using namespace metal; + + struct main0_out + { + float4 fragColor [[color(0)]]; + }; + + struct main0_in + { + float2 texUV [[user(locn0)]]; + float4 iColor [[user(locn1)]]; + }; + + fragment main0_out main0(main0_in in [[stage_in]], texture2d iTexChannel0 [[texture(0)]], sampler iSmpChannel0 [[sampler(0)]]) + { + main0_out out = {}; + out.fragColor = iTexChannel0.sample(iSmpChannel0, in.texUV) * in.iColor; + return out; + } + +*/ +static const uint8_t sgp_fs_source_metal_ios[478] = { + 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,0x43,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,0x32,0x20,0x74, + 0x65,0x78,0x55,0x56,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,0x69,0x43,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,0x69,0x54,0x65,0x78,0x43,0x68,0x61,0x6e,0x6e,0x65,0x6c,0x30,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,0x69,0x53,0x6d,0x70,0x43,0x68,0x61,0x6e,0x6e,0x65, + 0x6c,0x30,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,0x43,0x6f,0x6c,0x6f,0x72,0x20,0x3d, + 0x20,0x69,0x54,0x65,0x78,0x43,0x68,0x61,0x6e,0x6e,0x65,0x6c,0x30,0x2e,0x73,0x61, + 0x6d,0x70,0x6c,0x65,0x28,0x69,0x53,0x6d,0x70,0x43,0x68,0x61,0x6e,0x6e,0x65,0x6c, + 0x30,0x2c,0x20,0x69,0x6e,0x2e,0x74,0x65,0x78,0x55,0x56,0x29,0x20,0x2a,0x20,0x69, + 0x6e,0x2e,0x69,0x43,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, +}; +/* + diagnostic(off, derivative_uniformity); + + var coord : vec4f; + + var texUV : vec2f; + + var iColor : vec4f; + + var color : vec4f; + + var gl_Position : vec4f; + + fn main_1() { + let x_19 : vec4f = coord; + let x_20 : vec2f = vec2f(x_19.x, x_19.y); + gl_Position = vec4f(x_20.x, x_20.y, 0.0f, 1.0f); + let x_33 : vec4f = coord; + texUV = vec2f(x_33.z, x_33.w); + let x_37 : vec4f = color; + iColor = x_37; + return; + } + + struct main_out { + @builtin(position) + gl_Position : vec4f, + @location(0) + texUV_1 : vec2f, + @location(1) + iColor_1 : vec4f, + } + + @vertex + fn main(@location(0) coord_param : vec4f, @location(1) color_param : vec4f) -> main_out { + coord = coord_param; + color = color_param; + main_1(); + return main_out(gl_Position, texUV, iColor); + } + +*/ +static const uint8_t sgp_vs_source_wgsl[790] = { + 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,0x63,0x6f,0x6f,0x72,0x64,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,0x55,0x56,0x20,0x3a,0x20,0x76,0x65,0x63,0x32,0x66, + 0x3b,0x0a,0x0a,0x76,0x61,0x72,0x3c,0x70,0x72,0x69,0x76,0x61,0x74,0x65,0x3e,0x20, + 0x69,0x43,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,0x20,0x3a,0x20,0x76,0x65,0x63,0x34,0x66,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,0x31,0x39,0x20,0x3a,0x20,0x76,0x65,0x63,0x34, + 0x66,0x20,0x3d,0x20,0x63,0x6f,0x6f,0x72,0x64,0x3b,0x0a,0x20,0x20,0x6c,0x65,0x74, + 0x20,0x78,0x5f,0x32,0x30,0x20,0x3a,0x20,0x76,0x65,0x63,0x32,0x66,0x20,0x3d,0x20, + 0x76,0x65,0x63,0x32,0x66,0x28,0x78,0x5f,0x31,0x39,0x2e,0x78,0x2c,0x20,0x78,0x5f, + 0x31,0x39,0x2e,0x79,0x29,0x3b,0x0a,0x20,0x20,0x67,0x6c,0x5f,0x50,0x6f,0x73,0x69, + 0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x76,0x65,0x63,0x34,0x66,0x28,0x78,0x5f,0x32, + 0x30,0x2e,0x78,0x2c,0x20,0x78,0x5f,0x32,0x30,0x2e,0x79,0x2c,0x20,0x30,0x2e,0x30, + 0x66,0x2c,0x20,0x31,0x2e,0x30,0x66,0x29,0x3b,0x0a,0x20,0x20,0x6c,0x65,0x74,0x20, + 0x78,0x5f,0x33,0x33,0x20,0x3a,0x20,0x76,0x65,0x63,0x34,0x66,0x20,0x3d,0x20,0x63, + 0x6f,0x6f,0x72,0x64,0x3b,0x0a,0x20,0x20,0x74,0x65,0x78,0x55,0x56,0x20,0x3d,0x20, + 0x76,0x65,0x63,0x32,0x66,0x28,0x78,0x5f,0x33,0x33,0x2e,0x7a,0x2c,0x20,0x78,0x5f, + 0x33,0x33,0x2e,0x77,0x29,0x3b,0x0a,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x33, + 0x37,0x20,0x3a,0x20,0x76,0x65,0x63,0x34,0x66,0x20,0x3d,0x20,0x63,0x6f,0x6c,0x6f, + 0x72,0x3b,0x0a,0x20,0x20,0x69,0x43,0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20,0x78,0x5f, + 0x33,0x37,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, + 0x74,0x65,0x78,0x55,0x56,0x5f,0x31,0x20,0x3a,0x20,0x76,0x65,0x63,0x32,0x66,0x2c, + 0x0a,0x20,0x20,0x40,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x28,0x31,0x29,0x0a, + 0x20,0x20,0x69,0x43,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,0x63,0x6f,0x6f,0x72,0x64,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,0x63,0x6f,0x6f,0x72,0x64, + 0x20,0x3d,0x20,0x63,0x6f,0x6f,0x72,0x64,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,0x67,0x6c,0x5f,0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e, + 0x2c,0x20,0x74,0x65,0x78,0x55,0x56,0x2c,0x20,0x69,0x43,0x6f,0x6c,0x6f,0x72,0x29, + 0x3b,0x0a,0x7d,0x0a,0x0a,0x00, +}; +/* + diagnostic(off, derivative_uniformity); + + var fragColor : vec4f; + + @group(1) @binding(64) var iTexChannel0 : texture_2d; + + @group(1) @binding(80) var iSmpChannel0 : sampler; + + var texUV : vec2f; + + var iColor : vec4f; + + fn main_1() { + let x_23 : vec2f = texUV; + let x_24 : vec4f = textureSample(iTexChannel0, iSmpChannel0, x_23); + let x_27 : vec4f = iColor; + fragColor = (x_24 * x_27); + return; + } + + struct main_out { + @location(0) + fragColor_1 : vec4f, + } + + @fragment + fn main(@location(0) texUV_param : vec2f, @location(1) iColor_param : vec4f) -> main_out { + texUV = texUV_param; + iColor = iColor_param; + main_1(); + return main_out(fragColor); + } + +*/ +static const uint8_t sgp_fs_source_wgsl[682] = { + 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,0x43,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,0x69,0x54,0x65,0x78,0x43,0x68,0x61,0x6e,0x6e,0x65,0x6c, + 0x30,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,0x69,0x53,0x6d,0x70,0x43,0x68,0x61,0x6e,0x6e,0x65,0x6c,0x30,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,0x74,0x65,0x78,0x55,0x56,0x20,0x3a,0x20,0x76, + 0x65,0x63,0x32,0x66,0x3b,0x0a,0x0a,0x76,0x61,0x72,0x3c,0x70,0x72,0x69,0x76,0x61, + 0x74,0x65,0x3e,0x20,0x69,0x43,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,0x32,0x66,0x20,0x3d,0x20,0x74,0x65,0x78,0x55,0x56,0x3b,0x0a,0x20, + 0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x32,0x34,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,0x69,0x54,0x65,0x78,0x43,0x68,0x61,0x6e,0x6e,0x65,0x6c,0x30,0x2c,0x20, + 0x69,0x53,0x6d,0x70,0x43,0x68,0x61,0x6e,0x6e,0x65,0x6c,0x30,0x2c,0x20,0x78,0x5f, + 0x32,0x33,0x29,0x3b,0x0a,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x32,0x37,0x20, + 0x3a,0x20,0x76,0x65,0x63,0x34,0x66,0x20,0x3d,0x20,0x69,0x43,0x6f,0x6c,0x6f,0x72, + 0x3b,0x0a,0x20,0x20,0x66,0x72,0x61,0x67,0x43,0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20, + 0x28,0x78,0x5f,0x32,0x34,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,0x43,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,0x74,0x65,0x78,0x55,0x56,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,0x31,0x29,0x20,0x69,0x43,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,0x74,0x65, + 0x78,0x55,0x56,0x20,0x3d,0x20,0x74,0x65,0x78,0x55,0x56,0x5f,0x70,0x61,0x72,0x61, + 0x6d,0x3b,0x0a,0x20,0x20,0x69,0x43,0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20,0x69,0x43, + 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,0x43,0x6f, + 0x6c,0x6f,0x72,0x29,0x3b,0x0a,0x7d,0x0a,0x0a,0x00, +}; + +//////////////////////////////////////////////////////////////////////////////// + +static void _sgp_set_error(sgp_error error) { + _sgp.last_error = error; + SOKOL_LOG(sgp_get_error_message(error)); +} + +static sg_blend_state _sgp_blend_state(sgp_blend_mode blend_mode) { + sg_blend_state blend; + memset(&blend, 0, sizeof(sg_blend_state)); + switch (blend_mode) { + case SGP_BLENDMODE_NONE: + blend.enabled = false; + blend.src_factor_rgb = SG_BLENDFACTOR_ONE; + blend.dst_factor_rgb = SG_BLENDFACTOR_ZERO; + blend.op_rgb = SG_BLENDOP_ADD; + blend.src_factor_alpha = SG_BLENDFACTOR_ONE; + blend.dst_factor_alpha = SG_BLENDFACTOR_ZERO; + blend.op_alpha = SG_BLENDOP_ADD; + break; + case SGP_BLENDMODE_BLEND: + blend.enabled = true; + blend.src_factor_rgb = SG_BLENDFACTOR_SRC_ALPHA; + blend.dst_factor_rgb = SG_BLENDFACTOR_ONE_MINUS_SRC_ALPHA; + blend.op_rgb = SG_BLENDOP_ADD; + blend.src_factor_alpha = SG_BLENDFACTOR_ONE; + blend.dst_factor_alpha = SG_BLENDFACTOR_ONE_MINUS_SRC_ALPHA; + blend.op_alpha = SG_BLENDOP_ADD; + break; + case SGP_BLENDMODE_BLEND_PREMULTIPLIED: + blend.enabled = true; + blend.src_factor_rgb = SG_BLENDFACTOR_ONE; + blend.dst_factor_rgb = SG_BLENDFACTOR_ONE_MINUS_SRC_ALPHA; + blend.op_rgb = SG_BLENDOP_ADD; + blend.src_factor_alpha = SG_BLENDFACTOR_ONE; + blend.dst_factor_alpha = SG_BLENDFACTOR_ONE_MINUS_SRC_ALPHA; + blend.op_alpha = SG_BLENDOP_ADD; + break; + case SGP_BLENDMODE_ADD: + blend.enabled = true; + blend.src_factor_rgb = SG_BLENDFACTOR_SRC_ALPHA; + blend.dst_factor_rgb = SG_BLENDFACTOR_ONE; + blend.op_rgb = SG_BLENDOP_ADD; + blend.src_factor_alpha = SG_BLENDFACTOR_ZERO; + blend.dst_factor_alpha = SG_BLENDFACTOR_ONE; + blend.op_alpha = SG_BLENDOP_ADD; + break; + case SGP_BLENDMODE_ADD_PREMULTIPLIED: + blend.enabled = true; + blend.src_factor_rgb = SG_BLENDFACTOR_ONE; + blend.dst_factor_rgb = SG_BLENDFACTOR_ONE; + blend.op_rgb = SG_BLENDOP_ADD; + blend.src_factor_alpha = SG_BLENDFACTOR_ZERO; + blend.dst_factor_alpha = SG_BLENDFACTOR_ONE; + blend.op_alpha = SG_BLENDOP_ADD; + break; + case SGP_BLENDMODE_MOD: + blend.enabled = true; + blend.src_factor_rgb = SG_BLENDFACTOR_DST_COLOR; + blend.dst_factor_rgb = SG_BLENDFACTOR_ZERO; + blend.op_rgb = SG_BLENDOP_ADD; + blend.src_factor_alpha = SG_BLENDFACTOR_ZERO; + blend.dst_factor_alpha = SG_BLENDFACTOR_ONE; + blend.op_alpha = SG_BLENDOP_ADD; + break; + case SGP_BLENDMODE_MUL: + blend.enabled = true; + blend.src_factor_rgb = SG_BLENDFACTOR_DST_COLOR; + blend.dst_factor_rgb = SG_BLENDFACTOR_ONE_MINUS_SRC_ALPHA; + blend.op_rgb = SG_BLENDOP_ADD; + blend.src_factor_alpha = SG_BLENDFACTOR_DST_ALPHA; + blend.dst_factor_alpha = SG_BLENDFACTOR_ONE_MINUS_SRC_ALPHA; + blend.op_alpha = SG_BLENDOP_ADD; + break; + default: + blend.enabled = false; + blend.src_factor_rgb = SG_BLENDFACTOR_ONE; + blend.dst_factor_rgb = SG_BLENDFACTOR_ZERO; + blend.op_rgb = SG_BLENDOP_ADD; + blend.src_factor_alpha = SG_BLENDFACTOR_ONE; + blend.dst_factor_alpha = SG_BLENDFACTOR_ZERO; + blend.op_alpha = SG_BLENDOP_ADD; + SOKOL_UNREACHABLE; + break; + } + return blend; +} + +static sg_pipeline _sgp_make_pipeline(sg_shader shader, sg_primitive_type primitive_type, sgp_blend_mode blend_mode, + sg_pixel_format color_format, sg_pixel_format depth_format, int sample_count, bool has_vs_color) { + // create pipeline + sg_pipeline_desc pip_desc; + memset(&pip_desc, 0, sizeof(sg_pipeline_desc)); + pip_desc.shader = shader; + pip_desc.layout.buffers[0].stride = sizeof(sgp_vertex); + pip_desc.layout.attrs[SGP_VS_ATTR_COORD].offset = offsetof(sgp_vertex, position); + pip_desc.layout.attrs[SGP_VS_ATTR_COORD].format = SG_VERTEXFORMAT_FLOAT4; + if (has_vs_color) { + pip_desc.layout.attrs[SGP_VS_ATTR_COLOR].offset = offsetof(sgp_vertex, color); + pip_desc.layout.attrs[SGP_VS_ATTR_COLOR].format = SG_VERTEXFORMAT_UBYTE4N; + } + pip_desc.sample_count = sample_count; + pip_desc.depth.pixel_format = depth_format; + pip_desc.colors[0].pixel_format = color_format; + pip_desc.colors[0].blend = _sgp_blend_state(blend_mode); + pip_desc.primitive_type = primitive_type; + + sg_pipeline pip = sg_make_pipeline(&pip_desc); + if (pip.id != SG_INVALID_ID && sg_query_pipeline_state(pip) != SG_RESOURCESTATE_VALID) { + sg_destroy_pipeline(pip); + pip.id = SG_INVALID_ID; + } + return pip; +} + +static sg_pipeline _sgp_lookup_pipeline(sg_primitive_type primitive_type, sgp_blend_mode blend_mode) { + uint32_t pip_index = (primitive_type * _SGP_BLENDMODE_NUM) + blend_mode; + if (_sgp.pipelines[pip_index].id != SG_INVALID_ID) { + return _sgp.pipelines[pip_index]; + } + + sg_pipeline pip = _sgp_make_pipeline(_sgp.shader, primitive_type, blend_mode, _sgp.desc.color_format, _sgp.desc.depth_format, _sgp.desc.sample_count, true); + if (pip.id != SG_INVALID_ID) { + _sgp.pipelines[pip_index] = pip; + } + return pip; +} + +static sg_shader _sgp_make_common_shader(void) { + sg_backend backend = sg_query_backend(); + sg_shader_desc desc; + memset(&desc, 0, sizeof(desc)); + desc.images[0].stage = SG_SHADERSTAGE_FRAGMENT; + desc.images[0].multisampled = false; + desc.images[0].image_type = SG_IMAGETYPE_2D; + desc.images[0].sample_type = SG_IMAGESAMPLETYPE_FLOAT; + desc.samplers[0].stage = SG_SHADERSTAGE_FRAGMENT; + desc.samplers[0].sampler_type = SG_SAMPLERTYPE_FILTERING; + desc.image_sampler_pairs[0].stage = SG_SHADERSTAGE_FRAGMENT; + desc.image_sampler_pairs[0].image_slot = 0; + desc.image_sampler_pairs[0].sampler_slot = 0; + + // GLCORE / GLES3 only + desc.attrs[SGP_VS_ATTR_COORD].glsl_name = "coord"; + desc.attrs[SGP_VS_ATTR_COLOR].glsl_name = "color"; + desc.image_sampler_pairs[0].glsl_name = "iTexChannel0_iSmpChannel0"; + + // D3D11 only + desc.attrs[SGP_VS_ATTR_COORD].hlsl_sem_name = "TEXCOORD"; + desc.attrs[SGP_VS_ATTR_COORD].hlsl_sem_index = 0; + desc.attrs[SGP_VS_ATTR_COLOR].hlsl_sem_name = "TEXCOORD"; + desc.attrs[SGP_VS_ATTR_COLOR].hlsl_sem_index = 1; + desc.vertex_func.d3d11_target = "vs_4_0"; + desc.fragment_func.d3d11_target = "ps_4_0"; + + // entry + switch (backend) { + case SG_BACKEND_METAL_MACOS: + case SG_BACKEND_METAL_IOS: + case SG_BACKEND_METAL_SIMULATOR: + desc.vertex_func.entry = "main0"; + desc.fragment_func.entry = "main0"; + break; + default: + desc.vertex_func.entry = "main"; + desc.fragment_func.entry = "main"; + break; + } + + // source + switch (backend) { + case SG_BACKEND_GLCORE: + desc.vertex_func.source = (const char*)sgp_vs_source_glsl410; + desc.fragment_func.source = (const char*)sgp_fs_source_glsl410; + break; + case SG_BACKEND_GLES3: + desc.vertex_func.source = (const char*)sgp_vs_source_glsl300es; + desc.fragment_func.source = (const char*)sgp_fs_source_glsl300es; + break; + case SG_BACKEND_D3D11: + desc.vertex_func.source = (const char*)sgp_vs_source_hlsl4; + desc.fragment_func.source = (const char*)sgp_fs_source_hlsl4; + break; + case SG_BACKEND_METAL_MACOS: + desc.vertex_func.source = (const char*)sgp_vs_source_metal_macos; + desc.fragment_func.source = (const char*)sgp_fs_source_metal_macos; + break; + case SG_BACKEND_METAL_IOS: + case SG_BACKEND_METAL_SIMULATOR: + desc.vertex_func.source = (const char*)sgp_vs_source_metal_ios; + desc.fragment_func.source = (const char*)sgp_fs_source_metal_ios; + break; + case SG_BACKEND_WGPU: + desc.vertex_func.source = (const char*)sgp_vs_source_wgsl; + desc.fragment_func.source = (const char*)sgp_fs_source_wgsl; + break; + case SG_BACKEND_DUMMY: + desc.vertex_func.source = ""; + desc.fragment_func.source = ""; + break; + default: { + // Unsupported backend + sg_shader shd; + shd.id = SG_INVALID_ID; + return shd; + } + } + + return sg_make_shader(&desc); +} + +void sgp_setup(const sgp_desc* desc) { + SOKOL_ASSERT(_sgp.init_cookie == 0); + + if (!sg_isvalid()) { + _sgp_set_error(SGP_ERROR_SOKOL_INVALID); + return; + } + + // init + _sgp.init_cookie = _SGP_INIT_COOKIE; + _sgp.last_error = SGP_NO_ERROR; + + // set desc default values + _sgp.desc = *desc; + _sgp.desc.max_vertices = _sg_def(desc->max_vertices, _SGP_DEFAULT_MAX_VERTICES); + _sgp.desc.max_commands = _sg_def(desc->max_commands, _SGP_DEFAULT_MAX_COMMANDS); + _sgp.desc.color_format = _sg_def(desc->color_format, _sg.desc.environment.defaults.color_format); + _sgp.desc.depth_format = _sg_def(desc->depth_format, _sg.desc.environment.defaults.depth_format); + _sgp.desc.sample_count = _sg_def(desc->sample_count, _sg.desc.environment.defaults.sample_count); + + // allocate buffers + _sgp.num_vertices = _sgp.desc.max_vertices; + _sgp.num_commands = _sgp.desc.max_commands; + _sgp.num_uniforms = _sgp.desc.max_commands; + _sgp.vertices = (sgp_vertex*) _sg_malloc(_sgp.num_vertices * sizeof(sgp_vertex)); + _sgp.uniforms = (sgp_uniform*) _sg_malloc(_sgp.num_uniforms * sizeof(sgp_uniform)); + _sgp.commands = (_sgp_command*) _sg_malloc(_sgp.num_commands * sizeof(_sgp_command)); + if (!_sgp.commands || !_sgp.uniforms || !_sgp.commands) { + sgp_shutdown(); + _sgp_set_error(SGP_ERROR_ALLOC_FAILED); + return; + } + memset(_sgp.vertices, 0, _sgp.num_vertices * sizeof(sgp_vertex)); + memset(_sgp.uniforms, 0, _sgp.num_uniforms * sizeof(sgp_uniform)); + memset(_sgp.commands, 0, _sgp.num_commands * sizeof(_sgp_command)); + + // create vertex buffer + sg_buffer_desc vertex_buf_desc; + memset(&vertex_buf_desc, 0, sizeof(sg_buffer_desc)); + vertex_buf_desc.size = (size_t)(_sgp.num_vertices * sizeof(sgp_vertex)); + vertex_buf_desc.usage = (sg_buffer_usage){ + .vertex_buffer = true, + .stream_update = true, + }; + + _sgp.vertex_buf = sg_make_buffer(&vertex_buf_desc); + if (sg_query_buffer_state(_sgp.vertex_buf) != SG_RESOURCESTATE_VALID) { + sgp_shutdown(); + _sgp_set_error(SGP_ERROR_MAKE_VERTEX_BUFFER_FAILED); + return; + } + + // create white texture + uint32_t pixels[4]; + memset(pixels, 0xFF, sizeof(pixels)); + sg_image_desc white_img_desc; + memset(&white_img_desc, 0, sizeof(sg_image_desc)); + white_img_desc.type = SG_IMAGETYPE_2D; + white_img_desc.width = 2; + white_img_desc.height = 2; + white_img_desc.pixel_format = SG_PIXELFORMAT_RGBA8; + white_img_desc.data.subimage[0][0].ptr = pixels; + white_img_desc.data.subimage[0][0].size = sizeof(pixels); + white_img_desc.label = "sgp-white-texture"; + _sgp.white_img = sg_make_image(&white_img_desc); + if (sg_query_image_state(_sgp.white_img) != SG_RESOURCESTATE_VALID) { + sgp_shutdown(); + _sgp_set_error(SGP_ERROR_MAKE_WHITE_IMAGE_FAILED); + return; + } + + // create nearest sampler + sg_sampler_desc nearest_smp_desc; + memset(&nearest_smp_desc, 0, sizeof(sg_sampler_desc)); + nearest_smp_desc.label = "sgp-nearest-sampler"; + _sgp.nearest_smp = sg_make_sampler(&nearest_smp_desc); + if (sg_query_sampler_state(_sgp.nearest_smp) != SG_RESOURCESTATE_VALID) { + sgp_shutdown(); + _sgp_set_error(SGP_ERROR_MAKE_NEAREST_SAMPLER_FAILED); + return; + } + + // create common shader + _sgp.shader = _sgp_make_common_shader(); + if (sg_query_shader_state(_sgp.shader) != SG_RESOURCESTATE_VALID) { + sgp_shutdown(); + _sgp_set_error(SGP_ERROR_MAKE_COMMON_SHADER_FAILED); + return; + } + + // create common pipelines + bool pips_ok = true; + pips_ok = pips_ok && _sgp_lookup_pipeline(SG_PRIMITIVETYPE_TRIANGLES, SGP_BLENDMODE_NONE).id != SG_INVALID_ID; + pips_ok = pips_ok && _sgp_lookup_pipeline(SG_PRIMITIVETYPE_TRIANGLES, SGP_BLENDMODE_BLEND).id != SG_INVALID_ID; + pips_ok = pips_ok && _sgp_lookup_pipeline(SG_PRIMITIVETYPE_POINTS, SGP_BLENDMODE_NONE).id != SG_INVALID_ID; + pips_ok = pips_ok && _sgp_lookup_pipeline(SG_PRIMITIVETYPE_POINTS, SGP_BLENDMODE_BLEND).id != SG_INVALID_ID; + pips_ok = pips_ok && _sgp_lookup_pipeline(SG_PRIMITIVETYPE_LINES, SGP_BLENDMODE_NONE).id != SG_INVALID_ID; + pips_ok = pips_ok && _sgp_lookup_pipeline(SG_PRIMITIVETYPE_LINES, SGP_BLENDMODE_BLEND).id != SG_INVALID_ID; + pips_ok = pips_ok && _sgp_lookup_pipeline(SG_PRIMITIVETYPE_TRIANGLE_STRIP, SGP_BLENDMODE_NONE).id != SG_INVALID_ID; + pips_ok = pips_ok && _sgp_lookup_pipeline(SG_PRIMITIVETYPE_TRIANGLE_STRIP, SGP_BLENDMODE_BLEND).id != SG_INVALID_ID; + pips_ok = pips_ok && _sgp_lookup_pipeline(SG_PRIMITIVETYPE_LINE_STRIP, SGP_BLENDMODE_NONE).id != SG_INVALID_ID; + pips_ok = pips_ok && _sgp_lookup_pipeline(SG_PRIMITIVETYPE_LINE_STRIP, SGP_BLENDMODE_BLEND).id != SG_INVALID_ID; + if (!pips_ok) { + sgp_shutdown(); + _sgp_set_error(SGP_ERROR_MAKE_COMMON_PIPELINE_FAILED); + return; + } +} + +void sgp_shutdown(void) { + if (_sgp.init_cookie == 0) { + return; // not initialized + } + SOKOL_ASSERT(_sgp.init_cookie == _SGP_INIT_COOKIE); + SOKOL_ASSERT(_sgp.cur_state == 0); + if (_sgp.vertices) { + _sg_free(_sgp.vertices); + } + if (_sgp.uniforms) { + _sg_free(_sgp.uniforms); + } + if (_sgp.commands) { + _sg_free(_sgp.commands); + } + for (uint32_t i=0;i<_SG_PRIMITIVETYPE_NUM*_SGP_BLENDMODE_NUM;++i) { + sg_pipeline pip = _sgp.pipelines[i]; + if (pip.id != SG_INVALID_ID) { + sg_destroy_pipeline(pip); + } + } + if (_sgp.shader.id != SG_INVALID_ID) { + sg_destroy_shader(_sgp.shader); + } + if (_sgp.vertex_buf.id != SG_INVALID_ID) { + sg_destroy_buffer(_sgp.vertex_buf); + } + if (_sgp.white_img.id != SG_INVALID_ID) { + sg_destroy_image(_sgp.white_img); + } + if (_sgp.nearest_smp.id != SG_INVALID_ID) { + sg_destroy_sampler(_sgp.nearest_smp); + } + memset(&_sgp, 0, sizeof(_sgp_context)); +} + +bool sgp_is_valid(void) { + return _sgp.init_cookie == _SGP_INIT_COOKIE; +} + +sgp_error sgp_get_last_error(void) { + return _sgp.last_error; +} + +const char* sgp_get_error_message(sgp_error error_code) { + switch (error_code) { + case SGP_NO_ERROR: + return "No error"; + case SGP_ERROR_SOKOL_INVALID: + return "Sokol is not initialized"; + case SGP_ERROR_VERTICES_FULL: + return "SGP vertices buffer is full"; + case SGP_ERROR_UNIFORMS_FULL: + return "SGP uniform buffer is full"; + case SGP_ERROR_COMMANDS_FULL: + return "SGP command buffer is full"; + case SGP_ERROR_VERTICES_OVERFLOW: + return "SGP vertices buffer overflow"; + case SGP_ERROR_TRANSFORM_STACK_OVERFLOW: + return "SGP transform stack overflow"; + case SGP_ERROR_TRANSFORM_STACK_UNDERFLOW: + return "SGP transform stack underflow"; + case SGP_ERROR_STATE_STACK_OVERFLOW: + return "SGP state stack overflow"; + case SGP_ERROR_STATE_STACK_UNDERFLOW: + return "SGP state stack underflow"; + case SGP_ERROR_ALLOC_FAILED: + return "SGP failed to allocate buffers"; + case SGP_ERROR_MAKE_VERTEX_BUFFER_FAILED: + return "SGP failed to create vertex buffer"; + case SGP_ERROR_MAKE_WHITE_IMAGE_FAILED: + return "SGP failed to create white image"; + case SGP_ERROR_MAKE_NEAREST_SAMPLER_FAILED: + return "SGP failed to create nearest sampler"; + case SGP_ERROR_MAKE_COMMON_SHADER_FAILED: + return "SGP failed to create the common shader"; + case SGP_ERROR_MAKE_COMMON_PIPELINE_FAILED: + return "SGP failed to create the common pipeline"; + default: + return "Invalid error code"; + } +} + +sg_pipeline sgp_make_pipeline(const sgp_pipeline_desc* desc) { + sg_primitive_type primitive_type = _sg_def(desc->primitive_type, SG_PRIMITIVETYPE_TRIANGLES); + sgp_blend_mode blend_mode = _sg_def(desc->blend_mode, SGP_BLENDMODE_NONE); + sg_pixel_format color_format = _sg_def(desc->color_format, _sgp.desc.color_format); + sg_pixel_format depth_format = _sg_def(desc->depth_format, _sgp.desc.depth_format); + int sample_count = _sg_def(desc->sample_count, _sgp.desc.sample_count); + return _sgp_make_pipeline(desc->shader, primitive_type, blend_mode, color_format, depth_format, sample_count, desc->has_vs_color); +} + +static inline sgp_mat2x3 _sgp_default_proj(int width, int height) { + // matrix to convert screen coordinate system + // to the usual the coordinate system used on the backends + sgp_mat2x3 mat = {{ + {2.0f/(float)width, 0.0f, -1.0f}, + { 0.0f, -2.0f/(float)height, 1.0f} + }}; + return mat; +} + +void sgp_begin(int width, int height) { + SOKOL_ASSERT(_sgp.init_cookie == _SGP_INIT_COOKIE); + if (SOKOL_UNLIKELY(_sgp.cur_state >= _SGP_MAX_STACK_DEPTH)) { + _sgp_set_error(SGP_ERROR_STATE_STACK_OVERFLOW); + return; + } + + // begin reset last error + _sgp.last_error = SGP_NO_ERROR; + + // save current state + _sgp.state_stack[_sgp.cur_state++] = _sgp.state; + + // reset to default state + _sgp.state.frame_size.w = width; _sgp.state.frame_size.h = height; + _sgp.state.viewport.x = 0; _sgp.state.viewport.y = 0; + _sgp.state.viewport.w = width; _sgp.state.viewport.h = height; + _sgp.state.scissor.x = 0; _sgp.state.scissor.y = 0; + _sgp.state.scissor.w = -1; _sgp.state.scissor.h = -1; + _sgp.state.proj = _sgp_default_proj(width, height); + _sgp.state.transform = _sgp_mat3_identity; + _sgp.state.mvp = _sgp.state.proj; + _sgp.state.thickness = _sg_max(1.0f / width, 1.0f / height); + _sgp.state.color = _sgp_white_color; + memset(&_sgp.state.uniform, 0, sizeof(sgp_uniform)); + _sgp.state.uniform.vs_size = 0; + _sgp.state.uniform.fs_size = 0; + _sgp.state.blend_mode = SGP_BLENDMODE_NONE; + _sgp.state._base_vertex = _sgp.cur_vertex; + _sgp.state._base_uniform = _sgp.cur_uniform; + _sgp.state._base_command = _sgp.cur_command; + + _sgp.state.textures.count = 1; + _sgp.state.textures.images[0] = _sgp.white_img; + _sgp.state.textures.samplers[0] = _sgp.nearest_smp; + sg_image img = {SG_INVALID_ID}; + for (int i=1;i 0); + + uint32_t end_command = _sgp.cur_command; + uint32_t end_vertex = _sgp.cur_vertex; + + // rewind indexes + _sgp.cur_vertex = _sgp.state._base_vertex; + _sgp.cur_uniform = _sgp.state._base_uniform; + _sgp.cur_command = _sgp.state._base_command; + + // draw nothing on errors + if (_sgp.last_error != SGP_NO_ERROR) { + return; + } + + // nothing to be drawn + if (end_command <= _sgp.state._base_command) { + return; + } + + // upload vertices + uint32_t base_vertex = _sgp.state._base_vertex; + uint32_t num_vertices = (end_vertex - base_vertex) * sizeof(sgp_vertex); + sg_range vertex_range = {&_sgp.vertices[base_vertex], num_vertices}; + int offset = sg_append_buffer(_sgp.vertex_buf, &vertex_range); + if (sg_query_buffer_overflow(_sgp.vertex_buf)) { + _sgp_set_error(SGP_ERROR_VERTICES_OVERFLOW); + return; + } + + uint32_t cur_pip_id = _SGP_IMPOSSIBLE_ID; + uint32_t cur_uniform_index = _SGP_IMPOSSIBLE_ID; + uint32_t cur_imgs_id[SGP_TEXTURE_SLOTS]; + for (int i=0;icmd) { + case SGP_COMMAND_VIEWPORT: { + sgp_irect* args = &cmd->args.viewport; + sg_apply_viewport(args->x, args->y, args->w, args->h, true); + break; + } + case SGP_COMMAND_SCISSOR: { + sgp_irect* args = &cmd->args.scissor; + sg_apply_scissor_rect(args->x, args->y, args->w, args->h, true); + break; + } + case SGP_COMMAND_DRAW: { + _sgp_draw_args* args = &cmd->args.draw; + if (args->num_vertices == 0) { + break; + } + bool apply_bindings = false; + bool apply_uniforms = false; + // pipeline + if (args->pip.id != cur_pip_id) { + // when pipeline changes we need to re-apply uniforms and bindings + cur_uniform_index = _SGP_IMPOSSIBLE_ID; + apply_bindings = true; + cur_pip_id = args->pip.id; + sg_apply_pipeline(args->pip); + } + // bindings + for (uint32_t j=0;jtextures.count) { + img_id = args->textures.images[j].id; + if (img_id != SG_INVALID_ID) { + smp_id = args->textures.samplers[j].id; + } + } + if (cur_imgs_id[j] != img_id) { + // when an image binding change we need to re-apply bindings + cur_imgs_id[j] = img_id; + bind.images[j].id = img_id; + bind.samplers[j].id = smp_id; + apply_bindings = true; + } + } + if (apply_bindings) { + sg_apply_bindings(&bind); + apply_uniforms = true; + } + // uniforms + if (cur_uniform_index != args->uniform_index) { + cur_uniform_index = args->uniform_index; + apply_uniforms = true; + } + if (apply_uniforms && cur_uniform_index != _SGP_IMPOSSIBLE_ID) { + sgp_uniform* uniform = &_sgp.uniforms[cur_uniform_index]; + if (uniform->vs_size > 0) { + sg_range uniform_range = {&uniform->data.bytes[0], uniform->vs_size}; + sg_apply_uniforms(SGP_UNIFORM_SLOT_VERTEX, &uniform_range); + } + if (uniform->fs_size > 0) { + sg_range uniform_range = {&uniform->data.bytes[uniform->vs_size], uniform->fs_size}; + sg_apply_uniforms(SGP_UNIFORM_SLOT_FRAGMENT, &uniform_range); + } + } + // draw + sg_draw((int)(args->vertex_index - base_vertex), (int)args->num_vertices, 1); + break; + } + case SGP_COMMAND_NONE: { + // this command was optimized away + break; + } + } + } +} + +void sgp_end(void) { + SOKOL_ASSERT(_sgp.init_cookie == _SGP_INIT_COOKIE); + if (SOKOL_UNLIKELY(_sgp.cur_state <= 0)) { + _sgp_set_error(SGP_ERROR_STATE_STACK_UNDERFLOW); + return; + } + + // restore old state + _sgp.state = _sgp.state_stack[--_sgp.cur_state]; +} + +static inline sgp_mat2x3 _sgp_mul_proj_transform(sgp_mat2x3* proj, sgp_mat2x3* transform) { + // this actually multiply matrix projection and transform matrix in an optimized way + float x = proj->v[0][0], y = proj->v[1][1]; + sgp_mat2x3 m = {{ + {x*transform->v[0][0], x*transform->v[0][1], x*transform->v[0][2]+proj->v[0][2]}, + {y*transform->v[1][0], y*transform->v[1][1], y*transform->v[1][2]+proj->v[1][2]} + }}; + return m; +} + +void sgp_project(float left, float right, float top, float bottom) { + SOKOL_ASSERT(_sgp.init_cookie == _SGP_INIT_COOKIE); + SOKOL_ASSERT(_sgp.cur_state > 0); + float w = right - left; + float h = top - bottom; + sgp_mat2x3 proj = {{ + {2.0f/w, 0.0f, -(right+left)/w}, + {0.0f, 2.0f/h, -(top+bottom)/h} + }}; + _sgp.state.proj = proj; + _sgp.state.mvp = _sgp_mul_proj_transform(&_sgp.state.proj, &_sgp.state.transform); +} + +void sgp_reset_project(void) { + SOKOL_ASSERT(_sgp.init_cookie == _SGP_INIT_COOKIE); + SOKOL_ASSERT(_sgp.cur_state > 0); + _sgp.state.proj = _sgp_default_proj(_sgp.state.viewport.w, _sgp.state.viewport.h); + _sgp.state.mvp = _sgp_mul_proj_transform(&_sgp.state.proj, &_sgp.state.transform); +} + +void sgp_push_transform(void) { + SOKOL_ASSERT(_sgp.init_cookie == _SGP_INIT_COOKIE); + SOKOL_ASSERT(_sgp.cur_state > 0); + if (SOKOL_UNLIKELY(_sgp.cur_transform >= _SGP_MAX_STACK_DEPTH)) { + _sgp_set_error(SGP_ERROR_TRANSFORM_STACK_OVERFLOW); + return; + } + _sgp.transform_stack[_sgp.cur_transform++] = _sgp.state.transform; +} + +void sgp_pop_transform(void) { + SOKOL_ASSERT(_sgp.init_cookie == _SGP_INIT_COOKIE); + SOKOL_ASSERT(_sgp.cur_state > 0); + if (SOKOL_UNLIKELY(_sgp.cur_transform <= 0)) { + _sgp_set_error(SGP_ERROR_TRANSFORM_STACK_UNDERFLOW); + return; + } + _sgp.state.transform = _sgp.transform_stack[--_sgp.cur_transform]; + _sgp.state.mvp = _sgp_mul_proj_transform(&_sgp.state.proj, &_sgp.state.transform); +} + +void sgp_reset_transform(void) { + SOKOL_ASSERT(_sgp.init_cookie == _SGP_INIT_COOKIE); + SOKOL_ASSERT(_sgp.cur_state > 0); + _sgp.state.transform = _sgp_mat3_identity; + _sgp.state.mvp = _sgp_mul_proj_transform(&_sgp.state.proj, &_sgp.state.transform); +} + +void sgp_translate(float x, float y) { + SOKOL_ASSERT(_sgp.init_cookie == _SGP_INIT_COOKIE); + SOKOL_ASSERT(_sgp.cur_state > 0); + // multiply by translate matrix: + // 1.0f, 0.0f, x, + // 0.0f, 1.0f, y, + // 0.0f, 0.0f, 1.0f, + _sgp.state.transform.v[0][2] += x*_sgp.state.transform.v[0][0] + y*_sgp.state.transform.v[0][1]; + _sgp.state.transform.v[1][2] += x*_sgp.state.transform.v[1][0] + y*_sgp.state.transform.v[1][1]; + _sgp.state.mvp = _sgp_mul_proj_transform(&_sgp.state.proj, &_sgp.state.transform); +} + +void sgp_rotate(float theta) { + SOKOL_ASSERT(_sgp.init_cookie == _SGP_INIT_COOKIE); + SOKOL_ASSERT(_sgp.cur_state > 0); + float sint = sinf(theta), cost = cosf(theta); + // multiply by rotation matrix: + // cost, -sint, 0.0f, + // sint, cost, 0.0f, + // 0.0f, 0.0f, 1.0f, + sgp_mat2x3 transform = {{ + {cost*_sgp.state.transform.v[0][0]+sint*_sgp.state.transform.v[0][1], -sint*_sgp.state.transform.v[0][0]+cost*_sgp.state.transform.v[0][1], _sgp.state.transform.v[0][2]}, + {cost*_sgp.state.transform.v[1][0]+sint*_sgp.state.transform.v[1][1], -sint*_sgp.state.transform.v[1][0]+cost*_sgp.state.transform.v[1][1], _sgp.state.transform.v[1][2]} + }}; + _sgp.state.transform = transform; + _sgp.state.mvp = _sgp_mul_proj_transform(&_sgp.state.proj, &_sgp.state.transform); +} + +void sgp_rotate_at(float theta, float x, float y) { + SOKOL_ASSERT(_sgp.init_cookie == _SGP_INIT_COOKIE); + SOKOL_ASSERT(_sgp.cur_state > 0); + sgp_translate(x, y); + sgp_rotate(theta); + sgp_translate(-x, -y); +} + +void sgp_scale(float sx, float sy) { + SOKOL_ASSERT(_sgp.init_cookie == _SGP_INIT_COOKIE); + SOKOL_ASSERT(_sgp.cur_state > 0); + // multiply by scale matrix: + // sx, 0.0f, 0.0f, + // 0.0f, sy, 0.0f, + // 0.0f, 0.0f, 1.0f, + _sgp.state.transform.v[0][0] *= sx; + _sgp.state.transform.v[1][0] *= sx; + _sgp.state.transform.v[0][1] *= sy; + _sgp.state.transform.v[1][1] *= sy; + _sgp.state.mvp = _sgp_mul_proj_transform(&_sgp.state.proj, &_sgp.state.transform); +} + +void sgp_scale_at(float sx, float sy, float x, float y) { + SOKOL_ASSERT(_sgp.init_cookie == _SGP_INIT_COOKIE); + SOKOL_ASSERT(_sgp.cur_state > 0); + sgp_translate(x, y); + sgp_scale(sx, sy); + sgp_translate(-x, -y); +} + +void sgp_set_pipeline(sg_pipeline pipeline) { + SOKOL_ASSERT(_sgp.init_cookie == _SGP_INIT_COOKIE); + _sgp.state.pipeline = pipeline; + + // reset uniforms + memset(&_sgp.state.uniform, 0, sizeof(sgp_uniform)); +} + +void sgp_reset_pipeline(void) { + SOKOL_ASSERT(_sgp.init_cookie == _SGP_INIT_COOKIE); + sg_pipeline pip = {SG_INVALID_ID}; + sgp_set_pipeline(pip); +} + +void sgp_set_uniform(const void* vs_data, uint32_t vs_size, const void *fs_data, uint32_t fs_size) { + SOKOL_ASSERT(_sgp.init_cookie == _SGP_INIT_COOKIE); + SOKOL_ASSERT(_sgp.state.pipeline.id != SG_INVALID_ID); + uint32_t size = vs_size + fs_size; + SOKOL_ASSERT(size <= sizeof(float) * SGP_UNIFORM_CONTENT_SLOTS); + if (vs_size > 0) { + SOKOL_ASSERT(vs_data); + memcpy(&_sgp.state.uniform.data.bytes[0], vs_data, vs_size); + } + if (fs_size > 0) { + SOKOL_ASSERT(fs_data); + memcpy(&_sgp.state.uniform.data.bytes[vs_size], fs_data, fs_size); + } + uint32_t old_size = _sgp.state.uniform.vs_size + _sgp.state.uniform.fs_size; + if (size < old_size) { + // zero old uniform data + memset((uint8_t*)(&_sgp.state.uniform) + size, 0, old_size - size); + } + _sgp.state.uniform.vs_size = vs_size; + _sgp.state.uniform.fs_size = fs_size; +} + +void sgp_reset_uniform(void) { + SOKOL_ASSERT(_sgp.init_cookie == _SGP_INIT_COOKIE); + SOKOL_ASSERT(_sgp.state.pipeline.id != SG_INVALID_ID); + sgp_set_uniform(NULL, 0, NULL, 0); +} + +void sgp_set_blend_mode(sgp_blend_mode blend_mode) { + SOKOL_ASSERT(_sgp.init_cookie == _SGP_INIT_COOKIE); + _sgp.state.blend_mode = blend_mode; +} + +void sgp_reset_blend_mode(void) { + SOKOL_ASSERT(_sgp.init_cookie == _SGP_INIT_COOKIE); + sgp_set_blend_mode(SGP_BLENDMODE_NONE); +} + +void sgp_set_color(float r, float g, float b, float a) { + SOKOL_ASSERT(_sgp.init_cookie == _SGP_INIT_COOKIE); + SOKOL_ASSERT(_sgp.cur_state > 0); + _sgp.state.color = (sgp_color_ub4){ + (uint8_t)_sg_clamp(r*255.0f, 0.0f, 255.0f), + (uint8_t)_sg_clamp(g*255.0f, 0.0f, 255.0f), + (uint8_t)_sg_clamp(b*255.0f, 0.0f, 255.0f), + (uint8_t)_sg_clamp(a*255.0f, 0.0f, 255.0f) + }; +} + +void sgp_reset_color(void) { + SOKOL_ASSERT(_sgp.init_cookie == _SGP_INIT_COOKIE); + SOKOL_ASSERT(_sgp.cur_state > 0); + _sgp.state.color = _sgp_white_color; +} + +void sgp_set_image(int channel, sg_image image) { + SOKOL_ASSERT(_sgp.init_cookie == _SGP_INIT_COOKIE); + SOKOL_ASSERT(_sgp.cur_state > 0); + SOKOL_ASSERT(channel >= 0 && channel < SGP_TEXTURE_SLOTS); + if (_sgp.state.textures.images[channel].id == image.id) { + return; + } + + _sgp.state.textures.images[channel] = image; + + // recalculate textures count + int textures_count = (int)_sgp.state.textures.count; + for (int i=_sg_max(channel, textures_count-1);i>=0;--i) { + if (_sgp.state.textures.images[i].id != SG_INVALID_ID) { + textures_count = i + 1; + break; + } + } + _sgp.state.textures.count = (uint32_t)textures_count; +} + +void sgp_unset_image(int channel) { + SOKOL_ASSERT(_sgp.init_cookie == _SGP_INIT_COOKIE); + sg_image img = {SG_INVALID_ID}; + sgp_set_image(channel, img); +} + +void sgp_reset_image(int channel) { + SOKOL_ASSERT(_sgp.init_cookie == _SGP_INIT_COOKIE); + if (channel == 0) { + // channel 0 always use white image + sgp_set_image(channel, _sgp.white_img); + } else { + sg_image img = {SG_INVALID_ID}; + sgp_set_image(channel, img); + } +} + +void sgp_set_sampler(int channel, sg_sampler sampler) { + SOKOL_ASSERT(_sgp.init_cookie == _SGP_INIT_COOKIE); + SOKOL_ASSERT(_sgp.cur_state > 0); + SOKOL_ASSERT(channel >= 0 && channel < SGP_TEXTURE_SLOTS); + _sgp.state.textures.samplers[channel] = sampler; +} + +void sgp_reset_sampler(int channel) { + SOKOL_ASSERT(_sgp.init_cookie == _SGP_INIT_COOKIE); + sgp_set_sampler(channel, _sgp.nearest_smp); +} + +static sgp_vertex* _sgp_next_vertices(uint32_t count) { + if (SOKOL_LIKELY(_sgp.cur_vertex + count <= _sgp.num_vertices)) { + sgp_vertex *vertices = &_sgp.vertices[_sgp.cur_vertex]; + _sgp.cur_vertex += count; + return vertices; + } else { + _sgp_set_error(SGP_ERROR_VERTICES_FULL); + return NULL; + } +} + +static sgp_uniform* _sgp_prev_uniform(void) { + if (SOKOL_LIKELY(_sgp.cur_uniform > 0)) { + return &_sgp.uniforms[_sgp.cur_uniform-1]; + } else { + return NULL; + } +} + +static sgp_uniform* _sgp_next_uniform(void) { + if (SOKOL_LIKELY(_sgp.cur_uniform < _sgp.num_uniforms)) { + return &_sgp.uniforms[_sgp.cur_uniform++]; + } else { + _sgp_set_error(SGP_ERROR_UNIFORMS_FULL); + return NULL; + } +} + +static _sgp_command* _sgp_prev_command(uint32_t count) { + if (SOKOL_LIKELY((_sgp.cur_command - _sgp.state._base_command) >= count)) { + return &_sgp.commands[_sgp.cur_command-count]; + } else { + return NULL; + } +} + +static _sgp_command* _sgp_next_command(void) { + if (SOKOL_LIKELY(_sgp.cur_command < _sgp.num_commands)) { + return &_sgp.commands[_sgp.cur_command++]; + } else { + _sgp_set_error(SGP_ERROR_COMMANDS_FULL); + return NULL; + } +} + +void sgp_viewport(int x, int y, int w, int h) { + SOKOL_ASSERT(_sgp.init_cookie == _SGP_INIT_COOKIE); + SOKOL_ASSERT(_sgp.cur_state > 0); + + // skip in case of the same viewport + if (_sgp.state.viewport.x == x && _sgp.state.viewport.y == y && + _sgp.state.viewport.w == w && _sgp.state.viewport.h == h) { + return; + } + + // try to reuse last command otherwise use the next one + _sgp_command* cmd = _sgp_prev_command(1); + if (!cmd || cmd->cmd != SGP_COMMAND_VIEWPORT) { + cmd = _sgp_next_command(); + } + if (SOKOL_UNLIKELY(!cmd)) { + return; + } + + sgp_irect viewport = {x, y, w, h}; + + memset(cmd, 0, sizeof(_sgp_command)); + cmd->cmd = SGP_COMMAND_VIEWPORT; + cmd->args.viewport = viewport; + + // adjust current scissor relative offset + if (!(_sgp.state.scissor.w < 0 && _sgp.state.scissor.h < 0)) { + _sgp.state.scissor.x += x - _sgp.state.viewport.x; + _sgp.state.scissor.y += y - _sgp.state.viewport.y; + } + + _sgp.state.viewport = viewport; + _sgp.state.thickness = _sg_max(1.0f / w, 1.0f / h); + _sgp.state.proj = _sgp_default_proj(w, h); + _sgp.state.mvp = _sgp_mul_proj_transform(&_sgp.state.proj, &_sgp.state.transform); +} + +void sgp_reset_viewport(void) { + SOKOL_ASSERT(_sgp.init_cookie == _SGP_INIT_COOKIE); + SOKOL_ASSERT(_sgp.cur_state > 0); + sgp_viewport(0, 0, _sgp.state.frame_size.w, _sgp.state.frame_size.h); +} + +void sgp_scissor(int x, int y, int w, int h) { + SOKOL_ASSERT(_sgp.init_cookie == _SGP_INIT_COOKIE); + SOKOL_ASSERT(_sgp.cur_state > 0); + + // skip in case of the same scissor + if (_sgp.state.scissor.x == x && _sgp.state.scissor.y == y && + _sgp.state.scissor.w == w && _sgp.state.scissor.h == h) { + return; + } + + // try to reuse last command otherwise use the next one + _sgp_command* cmd = _sgp_prev_command(1); + if (!cmd || cmd->cmd != SGP_COMMAND_SCISSOR) { + cmd = _sgp_next_command(); + } + if (SOKOL_UNLIKELY(!cmd)) { + return; + } + + // coordinate scissor in viewport subspace + sgp_irect viewport_scissor = {_sgp.state.viewport.x + x, _sgp.state.viewport.y + y, w, h}; + + // reset scissor + if (w < 0 && h < 0) { + viewport_scissor.x = 0; viewport_scissor.y = 0; + viewport_scissor.w = _sgp.state.frame_size.w; viewport_scissor.h = _sgp.state.frame_size.h; + } + + memset(cmd, 0, sizeof(_sgp_command)); + cmd->cmd = SGP_COMMAND_SCISSOR; + cmd->args.scissor = viewport_scissor; + + sgp_irect scissor = {x, y, w, h}; + _sgp.state.scissor = scissor; +} + +void sgp_reset_scissor(void) { + SOKOL_ASSERT(_sgp.init_cookie == _SGP_INIT_COOKIE); + SOKOL_ASSERT(_sgp.cur_state > 0); + sgp_scissor(0, 0, -1, -1); +} + +void sgp_reset_state(void) { + SOKOL_ASSERT(_sgp.init_cookie == _SGP_INIT_COOKIE); + SOKOL_ASSERT(_sgp.cur_state > 0); + sgp_reset_viewport(); + sgp_reset_scissor(); + sgp_reset_project(); + sgp_reset_transform(); + sgp_reset_blend_mode(); + sgp_reset_color(); + sgp_reset_uniform(); + sgp_reset_pipeline(); +} + +static inline bool _sgp_region_overlaps(_sgp_region a, _sgp_region b) { + return !(a.x2 <= b.x1 || b.x2 <= a.x1 || a.y2 <= b.y1 || b.y2 <= a.y1); +} + +static bool _sgp_merge_batch_command(sg_pipeline pip, sgp_textures_uniform textures, sgp_uniform* uniform, _sgp_region region, uint32_t vertex_index, uint32_t num_vertices) { +#if SGP_BATCH_OPTIMIZER_DEPTH > 0 + _sgp_command* prev_cmd = NULL; + _sgp_command* inter_cmds[SGP_BATCH_OPTIMIZER_DEPTH]; + uint32_t inter_cmd_count = 0; + + // find a command that is a good candidate to batch + uint32_t lookup_depth = SGP_BATCH_OPTIMIZER_DEPTH; + for (uint32_t depth=0;depthcmd == SGP_COMMAND_NONE) { + lookup_depth++; + continue; + } + + // stop on scissor/viewport + if (cmd->cmd != SGP_COMMAND_DRAW) { + break; + } + + // can only batch commands with the same bindings and uniforms + if (cmd->args.draw.pip.id == pip.id && + memcmp(&textures, &cmd->args.draw.textures, sizeof(sgp_textures_uniform)) == 0 && + (!uniform || memcmp(uniform, &_sgp.uniforms[cmd->args.draw.uniform_index], sizeof(sgp_uniform)) == 0)) { + prev_cmd = cmd; + break; + } else { + inter_cmds[inter_cmd_count] = cmd; + inter_cmd_count++; + } + } + if (!prev_cmd) { + return false; + } + + // allow batching only if the region of the current or previous draw + // is not touched by intermediate commands + bool overlaps_next = false; + bool overlaps_prev = false; + _sgp_region prev_region = prev_cmd->args.draw.region; + for (uint32_t i=0;iargs.draw.region; + if (_sgp_region_overlaps(region, inter_region)) { + overlaps_next = true; + if (overlaps_prev) { + return false; + } + } + if (_sgp_region_overlaps(prev_region, inter_region)) { + overlaps_prev = true; + if (overlaps_next) { + return false; + } + } + } + + if (!overlaps_next) { // batch in the previous draw command + if (inter_cmd_count > 0) { + // not enough vertices space, can't do this batch + if (SOKOL_UNLIKELY(_sgp.cur_vertex + num_vertices > _sgp.num_vertices)) { + return false; + } + + uint32_t prev_end_vertex = prev_cmd->args.draw.vertex_index + prev_cmd->args.draw.num_vertices; + uint32_t prev_num_vertices = _sgp.cur_vertex - prev_end_vertex; + + // avoid moving too much memory, to not downgrade performance + if (prev_num_vertices > _SGP_MAX_MOVE_VERTICES) { + return false; + } + + // rearrange vertices memory for the batch + memmove(&_sgp.vertices[prev_end_vertex + num_vertices], &_sgp.vertices[prev_end_vertex], prev_num_vertices * sizeof(sgp_vertex)); + memcpy(&_sgp.vertices[prev_end_vertex], &_sgp.vertices[vertex_index + num_vertices], num_vertices * sizeof(sgp_vertex)); + + // offset vertices of intermediate draw commands + for (uint32_t i=0;iargs.draw.vertex_index += num_vertices; + } + } + + // update draw region and vertices + prev_region.x1 = _sg_min(prev_region.x1, region.x1); + prev_region.y1 = _sg_min(prev_region.y1, region.y1); + prev_region.x2 = _sg_max(prev_region.x2, region.x2); + prev_region.y2 = _sg_max(prev_region.y2, region.y2); + prev_cmd->args.draw.num_vertices += num_vertices; + prev_cmd->args.draw.region = prev_region; + } else { // batch in the next draw command + SOKOL_ASSERT(inter_cmd_count > 0); + + // append new draw command + _sgp_command* cmd = _sgp_next_command(); + if (SOKOL_UNLIKELY(!cmd)) { + return false; + } + + uint32_t prev_num_vertices = prev_cmd->args.draw.num_vertices; + + // not enough vertices space, can't do this batch + if (SOKOL_UNLIKELY(_sgp.cur_vertex + prev_num_vertices > _sgp.num_vertices)) { + return false; + } + + // avoid moving too much memory, to not downgrade performance + if (num_vertices > _SGP_MAX_MOVE_VERTICES) { + return false; + } + + // rearrange vertices memory for the batch + memmove(&_sgp.vertices[vertex_index + prev_num_vertices], &_sgp.vertices[vertex_index], num_vertices * sizeof(sgp_vertex)); + memcpy(&_sgp.vertices[vertex_index], &_sgp.vertices[prev_cmd->args.draw.vertex_index], prev_num_vertices * sizeof(sgp_vertex)); + + // update draw region and vertices + prev_region.x1 = _sg_min(prev_region.x1, region.x1); + prev_region.y1 = _sg_min(prev_region.y1, region.y1); + prev_region.x2 = _sg_max(prev_region.x2, region.x2); + prev_region.y2 = _sg_max(prev_region.y2, region.y2); + _sgp.cur_vertex += prev_num_vertices; + num_vertices += prev_num_vertices; + + // configure the draw command + cmd->cmd = SGP_COMMAND_DRAW; + cmd->args.draw.pip = pip; + cmd->args.draw.textures = textures; + cmd->args.draw.region = prev_region; + cmd->args.draw.uniform_index = prev_cmd->args.draw.uniform_index; + cmd->args.draw.vertex_index = vertex_index; + cmd->args.draw.num_vertices = num_vertices; + + // force skipping the previous draw command + prev_cmd->cmd = SGP_COMMAND_NONE; + } + return true; +#else + _SOKOL_UNUSED(pip); + _SOKOL_UNUSED(textures); + _SOKOL_UNUSED(uniform); + _SOKOL_UNUSED(region); + _SOKOL_UNUSED(vertex_index); + _SOKOL_UNUSED(num_vertices); + return false; +#endif // SGP_BATCH_OPTIMIZER_DEPTH > 0 +} + +static void _sgp_queue_draw(sg_pipeline pip, _sgp_region region, uint32_t vertex_index, uint32_t num_vertices, sg_primitive_type primitive_type) { + // override pipeline + sgp_uniform* uniform = NULL; + if (_sgp.state.pipeline.id != SG_INVALID_ID) { + pip = _sgp.state.pipeline; + uniform = &_sgp.state.uniform; + } + + // invalid pipeline + if (SOKOL_UNLIKELY(pip.id == SG_INVALID_ID)) { + _sgp.cur_vertex -= num_vertices; // rollback allocated vertices + return; + } + + // region is out of screen bounds + if (region.x1 > 1.0f || region.y1 > 1.0f || region.x2 < -1.0f || region.y2 < -1.0f) { + _sgp.cur_vertex -= num_vertices; // rollback allocated vertices + return; + } + + // try to merge on previous command to draw in a batch + if (primitive_type != SG_PRIMITIVETYPE_TRIANGLE_STRIP && primitive_type != SG_PRIMITIVETYPE_LINE_STRIP && + _sgp_merge_batch_command(pip, _sgp.state.textures, uniform, region, vertex_index, num_vertices)) { + return; + } + + // setup uniform, try to reuse previous uniform when possible + uint32_t uniform_index = _SGP_IMPOSSIBLE_ID; + if (uniform) { + sgp_uniform *prev_uniform = _sgp_prev_uniform(); + bool reuse_uniform = prev_uniform && (memcmp(prev_uniform, uniform, sizeof(sgp_uniform)) == 0); + if (!reuse_uniform) { + // append new uniform + sgp_uniform *next_uniform = _sgp_next_uniform(); + if (SOKOL_UNLIKELY(!next_uniform)) { + _sgp.cur_vertex -= num_vertices; // rollback allocated vertices + return; + } + *next_uniform = _sgp.state.uniform; + } + uniform_index = _sgp.cur_uniform - 1; + } + + // append new draw command + _sgp_command* cmd = _sgp_next_command(); + if (SOKOL_UNLIKELY(!cmd)) { + _sgp.cur_vertex -= num_vertices; // rollback allocated vertices + return; + } + cmd->cmd = SGP_COMMAND_DRAW; + cmd->args.draw.pip = pip; + cmd->args.draw.textures = _sgp.state.textures; + cmd->args.draw.region = region; + cmd->args.draw.uniform_index = uniform_index; + cmd->args.draw.vertex_index = vertex_index; + cmd->args.draw.num_vertices = num_vertices; +} + +static inline sgp_vec2 _sgp_mat3_vec2_mul(const sgp_mat2x3* m, const sgp_vec2* v) { + sgp_vec2 u = { + m->v[0][0]*v->x + m->v[0][1]*v->y + m->v[0][2], + m->v[1][0]*v->x + m->v[1][1]*v->y + m->v[1][2] + }; + return u; +} + +static void _sgp_transform_vec2(sgp_mat2x3* matrix, sgp_vec2* dst, const sgp_vec2 *src, uint32_t count) { + for (uint32_t i=0;i 0); + + // setup vertices + uint32_t num_vertices = 6; + uint32_t vertex_index = _sgp.cur_vertex; + sgp_vertex* vertices = _sgp_next_vertices(num_vertices); + if (SOKOL_UNLIKELY(!vertices)) { + return; + } + + // compute vertices + sgp_vertex* v = vertices; + const sgp_vec2 quad[4] = { + {-1.0f, -1.0f}, // bottom left + { 1.0f, -1.0f}, // bottom right + { 1.0f, 1.0f}, // top right + {-1.0f, 1.0f}, // top left + }; + const sgp_vec2 texcoord = {0.0f, 0.0f}; + sgp_color_ub4 color = _sgp.state.color; + + // make a quad composed of 2 triangles + v[0].position = quad[0]; v[0].texcoord = texcoord; v[0].color = color; + v[1].position = quad[1]; v[1].texcoord = texcoord; v[1].color = color; + v[2].position = quad[2]; v[2].texcoord = texcoord; v[2].color = color; + v[3].position = quad[3]; v[3].texcoord = texcoord; v[3].color = color; + v[4].position = quad[0]; v[4].texcoord = texcoord; v[4].color = color; + v[5].position = quad[2]; v[5].texcoord = texcoord; v[5].color = color; + + _sgp_region region = {-1.0f, -1.0f, 1.0f, 1.0f}; + + sg_pipeline pip = _sgp_lookup_pipeline(SG_PRIMITIVETYPE_TRIANGLES, SGP_BLENDMODE_NONE); + _sgp_queue_draw(pip, region, vertex_index, num_vertices, SG_PRIMITIVETYPE_TRIANGLES); +} + +void sgp_draw(sg_primitive_type primitive_type, const sgp_vertex* vertices, uint32_t count) { + SOKOL_ASSERT(_sgp.init_cookie == _SGP_INIT_COOKIE); + SOKOL_ASSERT(_sgp.cur_state > 0); + if (SOKOL_UNLIKELY(count == 0)) { + return; + } + + // setup vertices + uint32_t vertex_index = _sgp.cur_vertex; + sgp_vertex* v = _sgp_next_vertices(count); + if (SOKOL_UNLIKELY(!v)) { + return; + } + + // fill vertices + float thickness = (primitive_type == SG_PRIMITIVETYPE_POINTS || primitive_type == SG_PRIMITIVETYPE_LINES || primitive_type == SG_PRIMITIVETYPE_LINE_STRIP) ? _sgp.state.thickness : 0.0f; + sgp_mat2x3 mvp = _sgp.state.mvp; // copy to stack for more efficiency + _sgp_region region = {FLT_MAX, FLT_MAX, -FLT_MAX, -FLT_MAX}; + for (uint32_t i=0;i 0); + if (SOKOL_UNLIKELY(num_vertices == 0)) { + return; + } + + // setup vertices + uint32_t vertex_index = _sgp.cur_vertex; + sgp_vertex* v = _sgp_next_vertices(num_vertices); + if (SOKOL_UNLIKELY(!v)) { + return; + } + + // fill vertices + float thickness = (primitive_type == SG_PRIMITIVETYPE_POINTS || primitive_type == SG_PRIMITIVETYPE_LINES || primitive_type == SG_PRIMITIVETYPE_LINE_STRIP) ? _sgp.state.thickness : 0.0f; + sgp_color_ub4 color = _sgp.state.color; + sgp_mat2x3 mvp = _sgp.state.mvp; // copy to stack for more efficiency + _sgp_region region = {FLT_MAX, FLT_MAX, -FLT_MAX, -FLT_MAX}; + for (uint32_t i=0;i 0); + if (SOKOL_UNLIKELY(count == 0)) { + return; + } + + // setup vertices + uint32_t num_vertices = count * 6; + uint32_t vertex_index = _sgp.cur_vertex; + sgp_vertex* vertices = _sgp_next_vertices(num_vertices); + if (SOKOL_UNLIKELY(!vertices)) { + return; + } + + // compute vertices + sgp_vertex* v = vertices; + const sgp_rect* rect = rects; + sgp_color_ub4 color = _sgp.state.color; + sgp_mat2x3 mvp = _sgp.state.mvp; // copy to stack for more efficiency + _sgp_region region = {FLT_MAX, FLT_MAX, -FLT_MAX, -FLT_MAX}; + for (uint32_t i=0;ix, rect->y + rect->h}, // bottom left + {rect->x + rect->w, rect->y + rect->h}, // bottom right + {rect->x + rect->w, rect->y}, // top right + {rect->x, rect->y}, // top left + }; + _sgp_transform_vec2(&mvp, quad, quad, 4); + + for (uint32_t j=0;j<4;++j) { + region.x1 = _sg_min(region.x1, quad[j].x); + region.y1 = _sg_min(region.y1, quad[j].y); + region.x2 = _sg_max(region.x2, quad[j].x); + region.y2 = _sg_max(region.y2, quad[j].y); + } + + const sgp_vec2 vtexquad[4] = { + {0.0f, 1.0f}, // bottom left + {1.0f, 1.0f}, // bottom right + {1.0f, 0.0f}, // top right + {0.0f, 0.0f}, // top left + }; + + // make a quad composed of 2 triangles + v[0].position = quad[0]; v[0].texcoord = vtexquad[0]; v[0].color = color; + v[1].position = quad[1]; v[1].texcoord = vtexquad[1]; v[1].color = color; + v[2].position = quad[2]; v[2].texcoord = vtexquad[2]; v[2].color = color; + v[3].position = quad[3]; v[3].texcoord = vtexquad[3]; v[3].color = color; + v[4].position = quad[0]; v[4].texcoord = vtexquad[0]; v[4].color = color; + v[5].position = quad[2]; v[5].texcoord = vtexquad[2]; v[5].color = color; + } + + // queue draw + sg_pipeline pip = _sgp_lookup_pipeline(SG_PRIMITIVETYPE_TRIANGLES, _sgp.state.blend_mode); + _sgp_queue_draw(pip, region, vertex_index, num_vertices, SG_PRIMITIVETYPE_TRIANGLES); +} + +void sgp_draw_filled_rect(float x, float y, float w, float h) { + SOKOL_ASSERT(_sgp.init_cookie == _SGP_INIT_COOKIE); + SOKOL_ASSERT(_sgp.cur_state > 0); + sgp_rect rect = {x,y,w,h}; + sgp_draw_filled_rects(&rect, 1); +} + +static sgp_isize _sgp_query_image_size(sg_image img_id) { + const _sg_image_t* img = _sg_lookup_image(img_id.id); + SOKOL_ASSERT(img); + sgp_isize size = {img ? img->cmn.width : 0, img ? img->cmn.height : 0}; + return size; +} + +void sgp_draw_textured_rects(int channel, const sgp_textured_rect* rects, uint32_t count) { + SOKOL_ASSERT(_sgp.init_cookie == _SGP_INIT_COOKIE); + SOKOL_ASSERT(_sgp.cur_state > 0); + SOKOL_ASSERT(channel >= 0 && channel < SGP_TEXTURE_SLOTS); + sg_image image = _sgp.state.textures.images[channel]; + if (SOKOL_UNLIKELY(count == 0 || image.id == SG_INVALID_ID)) { + return; + } + + // setup vertices + uint32_t num_vertices = count * 6; + uint32_t vertex_index = _sgp.cur_vertex; + sgp_vertex* vertices = _sgp_next_vertices(num_vertices); + if (SOKOL_UNLIKELY(!vertices)) { + return; + } + + // compute image values used for texture coords transform + sgp_isize image_size = _sgp_query_image_size(image); + if (SOKOL_UNLIKELY(image_size.w == 0 || image_size.h == 0)) { + return; + } + float iw = 1.0f/(float)image_size.w, ih = 1.0f/(float)image_size.h; + + // compute vertices + sgp_mat2x3 mvp = _sgp.state.mvp; // copy to stack for more efficiency + _sgp_region region = {FLT_MAX, FLT_MAX, -FLT_MAX, -FLT_MAX}; + for (uint32_t i=0;i 0); + sgp_textured_rect rect = {dest_rect, src_rect}; + sgp_draw_textured_rects(channel, &rect, 1); +} + +sgp_desc sgp_query_desc(void) { + return _sgp.desc; +} + +sgp_state* sgp_query_state(void) { + return &_sgp.state; +} + +#endif // SOKOL_GP_IMPL_INCLUDED +#endif // SOKOL_GP_IMPL + +/* +Copyright (c) 2020-2024 Eduardo Bart (https://github.com/edubart/sokol_gp) + +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. + +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. +*/ diff --git a/inc/sokol/sokol_log.h b/inc/sokol/sokol_log.h new file mode 100644 index 0000000..58ff30b --- /dev/null +++ b/inc/sokol/sokol_log.h @@ -0,0 +1,343 @@ +#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 _DEBUG is 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 loom 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); + #elif defined(_SLOG_LINUX) || defined(_SLOG_APPLE) + int prio; + switch (log_level) { + case 0: prio = LOG_CRIT; break; + case 1: prio = LOG_ERR; break; + case 2: prio = LOG_WARNING; break; + default: prio = LOG_INFO; break; + } + syslog(prio, "%s", line_buf); + #endif + if (0 == log_level) { + abort(); + } +} +#endif // SOKOL_LOG_IMPL diff --git a/inc/sokol/sokol_time.h b/inc/sokol/sokol_time.h new file mode 100644 index 0000000..fd766d8 --- /dev/null +++ b/inc/sokol/sokol_time.h @@ -0,0 +1,319 @@ +#if defined(SOKOL_IMPL) && !defined(SOKOL_TIME_IMPL) +#define SOKOL_TIME_IMPL +#endif +#ifndef SOKOL_TIME_INCLUDED +/* + sokol_time.h -- simple cross-platform time measurement + + Project URL: https://github.com/floooh/sokol + + Do this: + #define SOKOL_IMPL or + #define SOKOL_TIME_IMPL + before you include this file in *one* C or C++ file to create the + implementation. + + Optionally provide the following defines with your own implementations: + SOKOL_ASSERT(c) - your own assert macro (default: assert(c)) + SOKOL_TIME_API_DECL - public function declaration prefix (default: extern) + SOKOL_API_DECL - same as SOKOL_TIME_API_DECL + SOKOL_API_IMPL - public function implementation prefix (default: -) + + If sokol_time.h is compiled as a DLL, define the following before + including the declaration or implementation: + + SOKOL_DLL + + On Windows, SOKOL_DLL will define SOKOL_TIME_API_DECL as __declspec(dllexport) + or __declspec(dllimport) as needed. + + void stm_setup(); + Call once before any other functions to initialize sokol_time + (this calls for instance QueryPerformanceFrequency on Windows) + + uint64_t stm_now(); + Get current point in time in unspecified 'ticks'. The value that + is returned has no relation to the 'wall-clock' time and is + not in a specific time unit, it is only useful to compute + time differences. + + uint64_t stm_diff(uint64_t new, uint64_t old); + Computes the time difference between new and old. This will always + return a positive, non-zero value. + + uint64_t stm_since(uint64_t start); + Takes the current time, and returns the elapsed time since start + (this is a shortcut for "stm_diff(stm_now(), start)") + + uint64_t stm_laptime(uint64_t* last_time); + This is useful for measuring frame time and other recurring + events. It takes the current time, returns the time difference + to the value in last_time, and stores the current time in + last_time for the next call. If the value in last_time is 0, + the return value will be zero (this usually happens on the + very first call). + + uint64_t stm_round_to_common_refresh_rate(uint64_t duration) + This oddly named function takes a measured frame time and + returns the closest "nearby" common display refresh rate frame duration + in ticks. If the input duration isn't close to any common display + refresh rate, the input duration will be returned unchanged as a fallback. + The main purpose of this function is to remove jitter/inaccuracies from + measured frame times, and instead use the display refresh rate as + frame duration. + NOTE: for more robust frame timing, consider using the + sokol_app.h function sapp_frame_duration() + + Use the following functions to convert a duration in ticks into + useful time units: + + double stm_sec(uint64_t ticks); + double stm_ms(uint64_t ticks); + double stm_us(uint64_t ticks); + double stm_ns(uint64_t ticks); + Converts a tick value into seconds, milliseconds, microseconds + or nanoseconds. Note that not all platforms will have nanosecond + or even microsecond precision. + + Uses the following time measurement functions under the hood: + + Windows: QueryPerformanceFrequency() / QueryPerformanceCounter() + MacOS/iOS: mach_absolute_time() + emscripten: emscripten_get_now() + Linux+others: clock_gettime(CLOCK_MONOTONIC) + + 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_TIME_INCLUDED (1) +#include + +#if defined(SOKOL_API_DECL) && !defined(SOKOL_TIME_API_DECL) +#define SOKOL_TIME_API_DECL SOKOL_API_DECL +#endif +#ifndef SOKOL_TIME_API_DECL +#if defined(_WIN32) && defined(SOKOL_DLL) && defined(SOKOL_TIME_IMPL) +#define SOKOL_TIME_API_DECL __declspec(dllexport) +#elif defined(_WIN32) && defined(SOKOL_DLL) +#define SOKOL_TIME_API_DECL __declspec(dllimport) +#else +#define SOKOL_TIME_API_DECL extern +#endif +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +SOKOL_TIME_API_DECL void stm_setup(void); +SOKOL_TIME_API_DECL uint64_t stm_now(void); +SOKOL_TIME_API_DECL uint64_t stm_diff(uint64_t new_ticks, uint64_t old_ticks); +SOKOL_TIME_API_DECL uint64_t stm_since(uint64_t start_ticks); +SOKOL_TIME_API_DECL uint64_t stm_laptime(uint64_t* last_time); +SOKOL_TIME_API_DECL uint64_t stm_round_to_common_refresh_rate(uint64_t frame_ticks); +SOKOL_TIME_API_DECL double stm_sec(uint64_t ticks); +SOKOL_TIME_API_DECL double stm_ms(uint64_t ticks); +SOKOL_TIME_API_DECL double stm_us(uint64_t ticks); +SOKOL_TIME_API_DECL double stm_ns(uint64_t ticks); + +#ifdef __cplusplus +} /* extern "C" */ +#endif +#endif // SOKOL_TIME_INCLUDED + +/*-- IMPLEMENTATION ----------------------------------------------------------*/ +#ifdef SOKOL_TIME_IMPL +#define SOKOL_TIME_IMPL_INCLUDED (1) +#include /* memset */ + +#ifndef SOKOL_API_IMPL + #define SOKOL_API_IMPL +#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 + +#if defined(_WIN32) +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include +typedef struct { + uint32_t initialized; + LARGE_INTEGER freq; + LARGE_INTEGER start; +} _stm_state_t; +#elif defined(__APPLE__) && defined(__MACH__) +#include +typedef struct { + uint32_t initialized; + mach_timebase_info_data_t timebase; + uint64_t start; +} _stm_state_t; +#elif defined(__EMSCRIPTEN__) +#include +typedef struct { + uint32_t initialized; + double start; +} _stm_state_t; +#else /* anything else, this will need more care for non-Linux platforms */ +#ifdef ESP8266 +// On the ESP8266, clock_gettime ignores the first argument and CLOCK_MONOTONIC isn't defined +#define CLOCK_MONOTONIC 0 +#endif +#include +typedef struct { + uint32_t initialized; + uint64_t start; +} _stm_state_t; +#endif +static _stm_state_t _stm; + +/* prevent 64-bit overflow when computing relative timestamp + see https://gist.github.com/jspohr/3dc4f00033d79ec5bdaf67bc46c813e3 +*/ +#if defined(_WIN32) || (defined(__APPLE__) && defined(__MACH__)) +_SOKOL_PRIVATE int64_t _stm_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; +} +#endif + +SOKOL_API_IMPL void stm_setup(void) { + memset(&_stm, 0, sizeof(_stm)); + _stm.initialized = 0xABCDABCD; + #if defined(_WIN32) + QueryPerformanceFrequency(&_stm.freq); + QueryPerformanceCounter(&_stm.start); + #elif defined(__APPLE__) && defined(__MACH__) + mach_timebase_info(&_stm.timebase); + _stm.start = mach_absolute_time(); + #elif defined(__EMSCRIPTEN__) + _stm.start = emscripten_get_now(); + #else + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + _stm.start = (uint64_t)ts.tv_sec*1000000000 + (uint64_t)ts.tv_nsec; + #endif +} + +SOKOL_API_IMPL uint64_t stm_now(void) { + SOKOL_ASSERT(_stm.initialized == 0xABCDABCD); + uint64_t now; + #if defined(_WIN32) + LARGE_INTEGER qpc_t; + QueryPerformanceCounter(&qpc_t); + now = (uint64_t) _stm_int64_muldiv(qpc_t.QuadPart - _stm.start.QuadPart, 1000000000, _stm.freq.QuadPart); + #elif defined(__APPLE__) && defined(__MACH__) + const uint64_t mach_now = mach_absolute_time() - _stm.start; + now = (uint64_t) _stm_int64_muldiv((int64_t)mach_now, (int64_t)_stm.timebase.numer, (int64_t)_stm.timebase.denom); + #elif defined(__EMSCRIPTEN__) + double js_now = emscripten_get_now() - _stm.start; + now = (uint64_t) (js_now * 1000000.0); + #else + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + now = ((uint64_t)ts.tv_sec*1000000000 + (uint64_t)ts.tv_nsec) - _stm.start; + #endif + return now; +} + +SOKOL_API_IMPL uint64_t stm_diff(uint64_t new_ticks, uint64_t old_ticks) { + if (new_ticks > old_ticks) { + return new_ticks - old_ticks; + } + else { + return 1; + } +} + +SOKOL_API_IMPL uint64_t stm_since(uint64_t start_ticks) { + return stm_diff(stm_now(), start_ticks); +} + +SOKOL_API_IMPL uint64_t stm_laptime(uint64_t* last_time) { + SOKOL_ASSERT(last_time); + uint64_t dt = 0; + uint64_t now = stm_now(); + if (0 != *last_time) { + dt = stm_diff(now, *last_time); + } + *last_time = now; + return dt; +} + +// first number is frame duration in ns, second number is tolerance in ns, +// the resulting min/max values must not overlap! +static const uint64_t _stm_refresh_rates[][2] = { + { 16666667, 1000000 }, // 60 Hz: 16.6667 +- 1ms + { 13888889, 250000 }, // 72 Hz: 13.8889 +- 0.25ms + { 13333333, 250000 }, // 75 Hz: 13.3333 +- 0.25ms + { 11764706, 250000 }, // 85 Hz: 11.7647 +- 0.25 + { 11111111, 250000 }, // 90 Hz: 11.1111 +- 0.25ms + { 10000000, 500000 }, // 100 Hz: 10.0000 +- 0.5ms + { 8333333, 500000 }, // 120 Hz: 8.3333 +- 0.5ms + { 6944445, 500000 }, // 144 Hz: 6.9445 +- 0.5ms + { 4166667, 1000000 }, // 240 Hz: 4.1666 +- 1ms + { 0, 0 }, // keep the last element always at zero +}; + +SOKOL_API_IMPL uint64_t stm_round_to_common_refresh_rate(uint64_t ticks) { + uint64_t ns; + int i = 0; + while (0 != (ns = _stm_refresh_rates[i][0])) { + uint64_t tol = _stm_refresh_rates[i][1]; + if ((ticks > (ns - tol)) && (ticks < (ns + tol))) { + return ns; + } + i++; + } + // fallthrough: didn't fit into any buckets + return ticks; +} + +SOKOL_API_IMPL double stm_sec(uint64_t ticks) { + return (double)ticks / 1000000000.0; +} + +SOKOL_API_IMPL double stm_ms(uint64_t ticks) { + return (double)ticks / 1000000.0; +} + +SOKOL_API_IMPL double stm_us(uint64_t ticks) { + return (double)ticks / 1000.0; +} + +SOKOL_API_IMPL double stm_ns(uint64_t ticks) { + return (double)ticks; +} +#endif /* SOKOL_TIME_IMPL */ + diff --git a/inc/stb/stb_image.h b/inc/stb/stb_image.h new file mode 100644 index 0000000..3f92e3b --- /dev/null +++ b/inc/stb/stb_image.h @@ -0,0 +1,6755 @@ +/* stb_image - v2.12 - public domain image loader - http://nothings.org/stb_image.h + no warranty implied; use at your own risk + + Do this: + #define STB_IMAGE_IMPLEMENTATION + before you include this file in *one* C or C++ file to create the implementation. + + // i.e. it should look like this: + #include ... + #include ... + #include ... + #define STB_IMAGE_IMPLEMENTATION + #include "stb_image.h" + + You can #define STBI_ASSERT(x) before the #include to avoid using assert.h. + And #define STBI_MALLOC, STBI_REALLOC, and STBI_FREE to avoid using malloc,realloc,free + + + QUICK NOTES: + Primarily of interest to game developers and other people who can + avoid problematic images and only need the trivial interface + + JPEG baseline & progressive (12 bpc/arithmetic not supported, same as stock IJG lib) + PNG 1/2/4/8-bit-per-channel (16 bpc not supported) + + TGA (not sure what subset, if a subset) + BMP non-1bpp, non-RLE + PSD (composited view only, no extra channels, 8/16 bit-per-channel) + + GIF (*comp always reports as 4-channel) + HDR (radiance rgbE format) + PIC (Softimage PIC) + PNM (PPM and PGM binary only) + + Animated GIF still needs a proper API, but here's one way to do it: + http://gist.github.com/urraka/685d9a6340b26b830d49 + + - decode from memory or through FILE (define STBI_NO_STDIO to remove code) + - decode from arbitrary I/O callbacks + - SIMD acceleration on x86/x64 (SSE2) and ARM (NEON) + + Full documentation under "DOCUMENTATION" below. + + + Revision 2.00 release notes: + + - Progressive JPEG is now supported. + + - PPM and PGM binary formats are now supported, thanks to Ken Miller. + + - x86 platforms now make use of SSE2 SIMD instructions for + JPEG decoding, and ARM platforms can use NEON SIMD if requested. + This work was done by Fabian "ryg" Giesen. SSE2 is used by + default, but NEON must be enabled explicitly; see docs. + + With other JPEG optimizations included in this version, we see + 2x speedup on a JPEG on an x86 machine, and a 1.5x speedup + on a JPEG on an ARM machine, relative to previous versions of this + library. The same results will not obtain for all JPGs and for all + x86/ARM machines. (Note that progressive JPEGs are significantly + slower to decode than regular JPEGs.) This doesn't mean that this + is the fastest JPEG decoder in the land; rather, it brings it + closer to parity with standard libraries. If you want the fastest + decode, look elsewhere. (See "Philosophy" section of docs below.) + + See final bullet items below for more info on SIMD. + + - Added STBI_MALLOC, STBI_REALLOC, and STBI_FREE macros for replacing + the memory allocator. Unlike other STBI libraries, these macros don't + support a context parameter, so if you need to pass a context in to + the allocator, you'll have to store it in a global or a thread-local + variable. + + - Split existing STBI_NO_HDR flag into two flags, STBI_NO_HDR and + STBI_NO_LINEAR. + STBI_NO_HDR: suppress implementation of .hdr reader format + STBI_NO_LINEAR: suppress high-dynamic-range light-linear float API + + - You can suppress implementation of any of the decoders to reduce + your code footprint by #defining one or more of the following + symbols before creating the implementation. + + STBI_NO_JPEG + STBI_NO_PNG + STBI_NO_BMP + STBI_NO_PSD + STBI_NO_TGA + STBI_NO_GIF + STBI_NO_HDR + STBI_NO_PIC + STBI_NO_PNM (.ppm and .pgm) + + - You can request *only* certain decoders and suppress all other ones + (this will be more forward-compatible, as addition of new decoders + doesn't require you to disable them explicitly): + + STBI_ONLY_JPEG + STBI_ONLY_PNG + STBI_ONLY_BMP + STBI_ONLY_PSD + STBI_ONLY_TGA + STBI_ONLY_GIF + STBI_ONLY_HDR + STBI_ONLY_PIC + STBI_ONLY_PNM (.ppm and .pgm) + + Note that you can define multiples of these, and you will get all + of them ("only x" and "only y" is interpreted to mean "only x&y"). + + - If you use STBI_NO_PNG (or _ONLY_ without PNG), and you still + want the zlib decoder to be available, #define STBI_SUPPORT_ZLIB + + - Compilation of all SIMD code can be suppressed with + #define STBI_NO_SIMD + It should not be necessary to disable SIMD unless you have issues + compiling (e.g. using an x86 compiler which doesn't support SSE + intrinsics or that doesn't support the method used to detect + SSE2 support at run-time), and even those can be reported as + bugs so I can refine the built-in compile-time checking to be + smarter. + + - The old STBI_SIMD system which allowed installing a user-defined + IDCT etc. has been removed. If you need this, don't upgrade. My + assumption is that almost nobody was doing this, and those who + were will find the built-in SIMD more satisfactory anyway. + + - RGB values computed for JPEG images are slightly different from + previous versions of stb_image. (This is due to using less + integer precision in SIMD.) The C code has been adjusted so + that the same RGB values will be computed regardless of whether + SIMD support is available, so your app should always produce + consistent results. But these results are slightly different from + previous versions. (Specifically, about 3% of available YCbCr values + will compute different RGB results from pre-1.49 versions by +-1; + most of the deviating values are one smaller in the G channel.) + + - If you must produce consistent results with previous versions of + stb_image, #define STBI_JPEG_OLD and you will get the same results + you used to; however, you will not get the SIMD speedups for + the YCbCr-to-RGB conversion step (although you should still see + significant JPEG speedup from the other changes). + + Please note that STBI_JPEG_OLD is a temporary feature; it will be + removed in future versions of the library. It is only intended for + near-term back-compatibility use. + + + Latest revision history: + 2.12 (2016-04-02) fix typo in 2.11 PSD fix that caused crashes + 2.11 (2016-04-02) 16-bit PNGS; enable SSE2 in non-gcc x64 + RGB-format JPEG; remove white matting in PSD; + allocate large structures on the stack; + correct channel count for PNG & BMP + 2.10 (2016-01-22) avoid warning introduced in 2.09 + 2.09 (2016-01-16) 16-bit TGA; comments in PNM files; STBI_REALLOC_SIZED + 2.08 (2015-09-13) fix to 2.07 cleanup, reading RGB PSD as RGBA + 2.07 (2015-09-13) partial animated GIF support + limited 16-bit PSD support + minor bugs, code cleanup, and compiler warnings + 2.06 (2015-04-19) fix bug where PSD returns wrong '*comp' value + 2.05 (2015-04-19) fix bug in progressive JPEG handling, fix warning + 2.04 (2015-04-15) try to re-enable SIMD on MinGW 64-bit + 2.03 (2015-04-12) additional corruption checking + stbi_set_flip_vertically_on_load + fix NEON support; fix mingw support + 2.02 (2015-01-19) fix incorrect assert, fix warning + 2.01 (2015-01-17) fix various warnings + 2.00b (2014-12-25) fix STBI_MALLOC in progressive JPEG + 2.00 (2014-12-25) optimize JPEG, including x86 SSE2 & ARM NEON SIMD + progressive JPEG + PGM/PPM support + STBI_MALLOC,STBI_REALLOC,STBI_FREE + STBI_NO_*, STBI_ONLY_* + GIF bugfix + + See end of file for full revision history. + + + ============================ Contributors ========================= + + Image formats Extensions, features + Sean Barrett (jpeg, png, bmp) Jetro Lauha (stbi_info) + Nicolas Schulz (hdr, psd) Martin "SpartanJ" Golini (stbi_info) + Jonathan Dummer (tga) James "moose2000" Brown (iPhone PNG) + Jean-Marc Lienher (gif) Ben "Disch" Wenger (io callbacks) + Tom Seddon (pic) Omar Cornut (1/2/4-bit PNG) + Thatcher Ulrich (psd) Nicolas Guillemot (vertical flip) + Ken Miller (pgm, ppm) Richard Mitton (16-bit PSD) + urraka@github (animated gif) Junggon Kim (PNM comments) + Daniel Gibson (16-bit TGA) + + Optimizations & bugfixes + Fabian "ryg" Giesen + Arseny Kapoulkine + + Bug & warning fixes + Marc LeBlanc David Woo Guillaume George Martins Mozeiko + Christpher Lloyd Martin Golini Jerry Jansson Joseph Thomson + Dave Moore Roy Eltham Hayaki Saito Phil Jordan + Won Chun Luke Graham Johan Duparc Nathan Reed + the Horde3D community Thomas Ruf Ronny Chevalier Nick Verigakis + Janez Zemva John Bartholomew Michal Cichon svdijk@github + Jonathan Blow Ken Hamada Tero Hanninen Baldur Karlsson + Laurent Gomila Cort Stratton Sergio Gonzalez romigrou@github + Aruelien Pocheville Thibault Reuille Cass Everitt Matthew Gregan + Ryamond Barbiero Paul Du Bois Engin Manap snagar@github + Michaelangel007@github Oriol Ferrer Mesia socks-the-fox + Blazej Dariusz Roszkowski + + +LICENSE + +This software is dual-licensed to the public domain and under the following +license: you are granted a perpetual, irrevocable license to copy, modify, +publish, and distribute this file as you see fit. + +*/ + +#ifndef STBI_INCLUDE_STB_IMAGE_H +#define STBI_INCLUDE_STB_IMAGE_H + +// DOCUMENTATION +// +// Limitations: +// - no 16-bit-per-channel PNG +// - no 12-bit-per-channel JPEG +// - no JPEGs with arithmetic coding +// - no 1-bit BMP +// - GIF always returns *comp=4 +// +// Basic usage (see HDR discussion below for HDR usage): +// int x,y,n; +// unsigned char *data = stbi_load(filename, &x, &y, &n, 0); +// // ... process data if not NULL ... +// // ... x = width, y = height, n = # 8-bit components per pixel ... +// // ... replace '0' with '1'..'4' to force that many components per pixel +// // ... but 'n' will always be the number that it would have been if you said 0 +// stbi_image_free(data) +// +// Standard parameters: +// int *x -- outputs image width in pixels +// int *y -- outputs image height in pixels +// int *comp -- outputs # of image components in image file +// int req_comp -- if non-zero, # of image components requested in result +// +// The return value from an image loader is an 'unsigned char *' which points +// to the pixel data, or NULL on an allocation failure or if the image is +// corrupt or invalid. The pixel data consists of *y scanlines of *x pixels, +// with each pixel consisting of N interleaved 8-bit components; the first +// pixel pointed to is top-left-most in the image. There is no padding between +// image scanlines or between pixels, regardless of format. The number of +// components N is 'req_comp' if req_comp is non-zero, or *comp otherwise. +// If req_comp is non-zero, *comp has the number of components that _would_ +// have been output otherwise. E.g. if you set req_comp to 4, you will always +// get RGBA output, but you can check *comp to see if it's trivially opaque +// because e.g. there were only 3 channels in the source image. +// +// An output image with N components has the following components interleaved +// in this order in each pixel: +// +// N=#comp components +// 1 grey +// 2 grey, alpha +// 3 red, green, blue +// 4 red, green, blue, alpha +// +// If image loading fails for any reason, the return value will be NULL, +// and *x, *y, *comp will be unchanged. The function stbi_failure_reason() +// can be queried for an extremely brief, end-user unfriendly explanation +// of why the load failed. Define STBI_NO_FAILURE_STRINGS to avoid +// compiling these strings at all, and STBI_FAILURE_USERMSG to get slightly +// more user-friendly ones. +// +// Paletted PNG, BMP, GIF, and PIC images are automatically depalettized. +// +// =========================================================================== +// +// Philosophy +// +// stb libraries are designed with the following priorities: +// +// 1. easy to use +// 2. easy to maintain +// 3. good performance +// +// Sometimes I let "good performance" creep up in priority over "easy to maintain", +// and for best performance I may provide less-easy-to-use APIs that give higher +// performance, in addition to the easy to use ones. Nevertheless, it's important +// to keep in mind that from the standpoint of you, a client of this library, +// all you care about is #1 and #3, and stb libraries do not emphasize #3 above all. +// +// Some secondary priorities arise directly from the first two, some of which +// make more explicit reasons why performance can't be emphasized. +// +// - Portable ("ease of use") +// - Small footprint ("easy to maintain") +// - No dependencies ("ease of use") +// +// =========================================================================== +// +// I/O callbacks +// +// I/O callbacks allow you to read from arbitrary sources, like packaged +// files or some other source. Data read from callbacks are processed +// through a small internal buffer (currently 128 bytes) to try to reduce +// overhead. +// +// The three functions you must define are "read" (reads some bytes of data), +// "skip" (skips some bytes of data), "eof" (reports if the stream is at the end). +// +// =========================================================================== +// +// SIMD support +// +// The JPEG decoder will try to automatically use SIMD kernels on x86 when +// supported by the compiler. For ARM Neon support, you must explicitly +// request it. +// +// (The old do-it-yourself SIMD API is no longer supported in the current +// code.) +// +// On x86, SSE2 will automatically be used when available based on a run-time +// test; if not, the generic C versions are used as a fall-back. On ARM targets, +// the typical path is to have separate builds for NEON and non-NEON devices +// (at least this is true for iOS and Android). Therefore, the NEON support is +// toggled by a build flag: define STBI_NEON to get NEON loops. +// +// The output of the JPEG decoder is slightly different from versions where +// SIMD support was introduced (that is, for versions before 1.49). The +// difference is only +-1 in the 8-bit RGB channels, and only on a small +// fraction of pixels. You can force the pre-1.49 behavior by defining +// STBI_JPEG_OLD, but this will disable some of the SIMD decoding path +// and hence cost some performance. +// +// If for some reason you do not want to use any of SIMD code, or if +// you have issues compiling it, you can disable it entirely by +// defining STBI_NO_SIMD. +// +// =========================================================================== +// +// HDR image support (disable by defining STBI_NO_HDR) +// +// stb_image now supports loading HDR images in general, and currently +// the Radiance .HDR file format, although the support is provided +// generically. You can still load any file through the existing interface; +// if you attempt to load an HDR file, it will be automatically remapped to +// LDR, assuming gamma 2.2 and an arbitrary scale factor defaulting to 1; +// both of these constants can be reconfigured through this interface: +// +// stbi_hdr_to_ldr_gamma(2.2f); +// stbi_hdr_to_ldr_scale(1.0f); +// +// (note, do not use _inverse_ constants; stbi_image will invert them +// appropriately). +// +// Additionally, there is a new, parallel interface for loading files as +// (linear) floats to preserve the full dynamic range: +// +// float *data = stbi_loadf(filename, &x, &y, &n, 0); +// +// If you load LDR images through this interface, those images will +// be promoted to floating point values, run through the inverse of +// constants corresponding to the above: +// +// stbi_ldr_to_hdr_scale(1.0f); +// stbi_ldr_to_hdr_gamma(2.2f); +// +// Finally, given a filename (or an open file or memory block--see header +// file for details) containing image data, you can query for the "most +// appropriate" interface to use (that is, whether the image is HDR or +// not), using: +// +// stbi_is_hdr(char *filename); +// +// =========================================================================== +// +// iPhone PNG support: +// +// By default we convert iphone-formatted PNGs back to RGB, even though +// they are internally encoded differently. You can disable this conversion +// by by calling stbi_convert_iphone_png_to_rgb(0), in which case +// you will always just get the native iphone "format" through (which +// is BGR stored in RGB). +// +// Call stbi_set_unpremultiply_on_load(1) as well to force a divide per +// pixel to remove any premultiplied alpha *only* if the image file explicitly +// says there's premultiplied data (currently only happens in iPhone images, +// and only if iPhone convert-to-rgb processing is on). +// + + +#ifndef STBI_NO_STDIO +#include +#endif // STBI_NO_STDIO + +#define STBI_VERSION 1 + +enum +{ + STBI_default = 0, // only used for req_comp + + STBI_grey = 1, + STBI_grey_alpha = 2, + STBI_rgb = 3, + STBI_rgb_alpha = 4 +}; + +typedef unsigned char stbi_uc; + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef STB_IMAGE_STATIC +#define STBIDEF static +#else +#define STBIDEF extern +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// PRIMARY API - works on images of any type +// + +// +// load image by filename, open file, or memory buffer +// + +typedef struct +{ + int (*read) (void *user,char *data,int size); // fill 'data' with 'size' bytes. return number of bytes actually read + void (*skip) (void *user,int n); // skip the next 'n' bytes, or 'unget' the last -n bytes if negative + int (*eof) (void *user); // returns nonzero if we are at end of file/data +} stbi_io_callbacks; + +STBIDEF stbi_uc *stbi_load (char const *filename, int *x, int *y, int *comp, int req_comp); +STBIDEF stbi_uc *stbi_load_from_memory (stbi_uc const *buffer, int len , int *x, int *y, int *comp, int req_comp); +STBIDEF stbi_uc *stbi_load_from_callbacks(stbi_io_callbacks const *clbk , void *user, int *x, int *y, int *comp, int req_comp); + +#ifndef STBI_NO_STDIO +STBIDEF stbi_uc *stbi_load_from_file (FILE *f, int *x, int *y, int *comp, int req_comp); +// for stbi_load_from_file, file pointer is left pointing immediately after image +#endif + +#ifndef STBI_NO_LINEAR + STBIDEF float *stbi_loadf (char const *filename, int *x, int *y, int *comp, int req_comp); + STBIDEF float *stbi_loadf_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp); + STBIDEF float *stbi_loadf_from_callbacks (stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp); + + #ifndef STBI_NO_STDIO + STBIDEF float *stbi_loadf_from_file (FILE *f, int *x, int *y, int *comp, int req_comp); + #endif +#endif + +#ifndef STBI_NO_HDR + STBIDEF void stbi_hdr_to_ldr_gamma(float gamma); + STBIDEF void stbi_hdr_to_ldr_scale(float scale); +#endif // STBI_NO_HDR + +#ifndef STBI_NO_LINEAR + STBIDEF void stbi_ldr_to_hdr_gamma(float gamma); + STBIDEF void stbi_ldr_to_hdr_scale(float scale); +#endif // STBI_NO_LINEAR + +// stbi_is_hdr is always defined, but always returns false if STBI_NO_HDR +STBIDEF int stbi_is_hdr_from_callbacks(stbi_io_callbacks const *clbk, void *user); +STBIDEF int stbi_is_hdr_from_memory(stbi_uc const *buffer, int len); +#ifndef STBI_NO_STDIO +STBIDEF int stbi_is_hdr (char const *filename); +STBIDEF int stbi_is_hdr_from_file(FILE *f); +#endif // STBI_NO_STDIO + + +// get a VERY brief reason for failure +// NOT THREADSAFE +STBIDEF const char *stbi_failure_reason (void); + +// free the loaded image -- this is just free() +STBIDEF void stbi_image_free (void *retval_from_stbi_load); + +// get image dimensions & components without fully decoding +STBIDEF int stbi_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp); +STBIDEF int stbi_info_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp); + +#ifndef STBI_NO_STDIO +STBIDEF int stbi_info (char const *filename, int *x, int *y, int *comp); +STBIDEF int stbi_info_from_file (FILE *f, int *x, int *y, int *comp); + +#endif + + + +// for image formats that explicitly notate that they have premultiplied alpha, +// we just return the colors as stored in the file. set this flag to force +// unpremultiplication. results are undefined if the unpremultiply overflow. +STBIDEF void stbi_set_unpremultiply_on_load(int flag_true_if_should_unpremultiply); + +// indicate whether we should process iphone images back to canonical format, +// or just pass them through "as-is" +STBIDEF void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert); + +// flip the image vertically, so the first pixel in the output array is the bottom left +STBIDEF void stbi_set_flip_vertically_on_load(int flag_true_if_should_flip); + +// ZLIB client - used by PNG, available for other purposes + +STBIDEF char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int initial_size, int *outlen); +STBIDEF char *stbi_zlib_decode_malloc_guesssize_headerflag(const char *buffer, int len, int initial_size, int *outlen, int parse_header); +STBIDEF char *stbi_zlib_decode_malloc(const char *buffer, int len, int *outlen); +STBIDEF int stbi_zlib_decode_buffer(char *obuffer, int olen, const char *ibuffer, int ilen); + +STBIDEF char *stbi_zlib_decode_noheader_malloc(const char *buffer, int len, int *outlen); +STBIDEF int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const char *ibuffer, int ilen); + + +#ifdef __cplusplus +} +#endif + +// +// +//// end header file ///////////////////////////////////////////////////// +#endif // STBI_INCLUDE_STB_IMAGE_H + +#ifdef STB_IMAGE_IMPLEMENTATION + +#if defined(STBI_ONLY_JPEG) || defined(STBI_ONLY_PNG) || defined(STBI_ONLY_BMP) \ + || defined(STBI_ONLY_TGA) || defined(STBI_ONLY_GIF) || defined(STBI_ONLY_PSD) \ + || defined(STBI_ONLY_HDR) || defined(STBI_ONLY_PIC) || defined(STBI_ONLY_PNM) \ + || defined(STBI_ONLY_ZLIB) + #ifndef STBI_ONLY_JPEG + #define STBI_NO_JPEG + #endif + #ifndef STBI_ONLY_PNG + #define STBI_NO_PNG + #endif + #ifndef STBI_ONLY_BMP + #define STBI_NO_BMP + #endif + #ifndef STBI_ONLY_PSD + #define STBI_NO_PSD + #endif + #ifndef STBI_ONLY_TGA + #define STBI_NO_TGA + #endif + #ifndef STBI_ONLY_GIF + #define STBI_NO_GIF + #endif + #ifndef STBI_ONLY_HDR + #define STBI_NO_HDR + #endif + #ifndef STBI_ONLY_PIC + #define STBI_NO_PIC + #endif + #ifndef STBI_ONLY_PNM + #define STBI_NO_PNM + #endif +#endif + +#if defined(STBI_NO_PNG) && !defined(STBI_SUPPORT_ZLIB) && !defined(STBI_NO_ZLIB) +#define STBI_NO_ZLIB +#endif + + +#include +#include // ptrdiff_t on osx +#include +#include + +#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) +#include // ldexp +#endif + +#ifndef STBI_NO_STDIO +#include +#endif + +#ifndef STBI_ASSERT +#include +#define STBI_ASSERT(x) assert(x) +#endif + + +#ifndef _MSC_VER + #ifdef __cplusplus + #define stbi_inline inline + #else + #define stbi_inline + #endif +#else + #define stbi_inline __forceinline +#endif + + +#ifdef _MSC_VER +typedef unsigned short stbi__uint16; +typedef signed short stbi__int16; +typedef unsigned int stbi__uint32; +typedef signed int stbi__int32; +#else +#include +typedef uint16_t stbi__uint16; +typedef int16_t stbi__int16; +typedef uint32_t stbi__uint32; +typedef int32_t stbi__int32; +#endif + +// should produce compiler error if size is wrong +typedef unsigned char validate_uint32[sizeof(stbi__uint32)==4 ? 1 : -1]; + +#ifdef _MSC_VER +#define STBI_NOTUSED(v) (void)(v) +#else +#define STBI_NOTUSED(v) (void)sizeof(v) +#endif + +#ifdef _MSC_VER +#define STBI_HAS_LROTL +#endif + +#ifdef STBI_HAS_LROTL + #define stbi_lrot(x,y) _lrotl(x,y) +#else + #define stbi_lrot(x,y) (((x) << (y)) | ((x) >> (32 - (y)))) +#endif + +#if defined(STBI_MALLOC) && defined(STBI_FREE) && (defined(STBI_REALLOC) || defined(STBI_REALLOC_SIZED)) +// ok +#elif !defined(STBI_MALLOC) && !defined(STBI_FREE) && !defined(STBI_REALLOC) && !defined(STBI_REALLOC_SIZED) +// ok +#else +#error "Must define all or none of STBI_MALLOC, STBI_FREE, and STBI_REALLOC (or STBI_REALLOC_SIZED)." +#endif + +#ifndef STBI_MALLOC +#define STBI_MALLOC(sz) malloc(sz) +#define STBI_REALLOC(p,newsz) realloc(p,newsz) +#define STBI_FREE(p) free(p) +#endif + +#ifndef STBI_REALLOC_SIZED +#define STBI_REALLOC_SIZED(p,oldsz,newsz) STBI_REALLOC(p,newsz) +#endif + +// x86/x64 detection +#if defined(__x86_64__) || defined(_M_X64) +#define STBI__X64_TARGET +#elif defined(__i386) || defined(_M_IX86) +#define STBI__X86_TARGET +#endif + +#if defined(__GNUC__) && (defined(STBI__X86_TARGET) || defined(STBI__X64_TARGET)) && !defined(__SSE2__) && !defined(STBI_NO_SIMD) +// NOTE: not clear do we actually need this for the 64-bit path? +// gcc doesn't support sse2 intrinsics unless you compile with -msse2, +// (but compiling with -msse2 allows the compiler to use SSE2 everywhere; +// this is just broken and gcc are jerks for not fixing it properly +// http://www.virtualdub.org/blog/pivot/entry.php?id=363 ) +#define STBI_NO_SIMD +#endif + +#if defined(__MINGW32__) && defined(STBI__X86_TARGET) && !defined(STBI_MINGW_ENABLE_SSE2) && !defined(STBI_NO_SIMD) +// Note that __MINGW32__ doesn't actually mean 32-bit, so we have to avoid STBI__X64_TARGET +// +// 32-bit MinGW wants ESP to be 16-byte aligned, but this is not in the +// Windows ABI and VC++ as well as Windows DLLs don't maintain that invariant. +// As a result, enabling SSE2 on 32-bit MinGW is dangerous when not +// simultaneously enabling "-mstackrealign". +// +// See https://github.com/nothings/stb/issues/81 for more information. +// +// So default to no SSE2 on 32-bit MinGW. If you've read this far and added +// -mstackrealign to your build settings, feel free to #define STBI_MINGW_ENABLE_SSE2. +#define STBI_NO_SIMD +#endif + +#if !defined(STBI_NO_SIMD) && (defined(STBI__X86_TARGET) || defined(STBI__X64_TARGET)) +#define STBI_SSE2 +#include + +#ifdef _MSC_VER + +#if _MSC_VER >= 1400 // not VC6 +#include // __cpuid +static int stbi__cpuid3(void) +{ + int info[4]; + __cpuid(info,1); + return info[3]; +} +#else +static int stbi__cpuid3(void) +{ + int res; + __asm { + mov eax,1 + cpuid + mov res,edx + } + return res; +} +#endif + +#define STBI_SIMD_ALIGN(type, name) __declspec(align(16)) type name + +static int stbi__sse2_available() +{ + int info3 = stbi__cpuid3(); + return ((info3 >> 26) & 1) != 0; +} +#else // assume GCC-style if not VC++ +#define STBI_SIMD_ALIGN(type, name) type name __attribute__((aligned(16))) + +static int stbi__sse2_available() +{ +#if defined(__GNUC__) && (__GNUC__ * 100 + __GNUC_MINOR__) >= 408 // GCC 4.8 or later + // GCC 4.8+ has a nice way to do this + return __builtin_cpu_supports("sse2"); +#else + // portable way to do this, preferably without using GCC inline ASM? + // just bail for now. + return 0; +#endif +} +#endif +#endif + +// ARM NEON +#if defined(STBI_NO_SIMD) && defined(STBI_NEON) +#undef STBI_NEON +#endif + +#ifdef STBI_NEON +#include +// assume GCC or Clang on ARM targets +#define STBI_SIMD_ALIGN(type, name) type name __attribute__((aligned(16))) +#endif + +#ifndef STBI_SIMD_ALIGN +#define STBI_SIMD_ALIGN(type, name) type name +#endif + +/////////////////////////////////////////////// +// +// stbi__context struct and start_xxx functions + +// stbi__context structure is our basic context used by all images, so it +// contains all the IO context, plus some basic image information +typedef struct +{ + stbi__uint32 img_x, img_y; + int img_n, img_out_n; + + stbi_io_callbacks io; + void *io_user_data; + + int read_from_callbacks; + int buflen; + stbi_uc buffer_start[128]; + + stbi_uc *img_buffer, *img_buffer_end; + stbi_uc *img_buffer_original, *img_buffer_original_end; +} stbi__context; + + +static void stbi__refill_buffer(stbi__context *s); + +// initialize a memory-decode context +static void stbi__start_mem(stbi__context *s, stbi_uc const *buffer, int len) +{ + s->io.read = NULL; + s->read_from_callbacks = 0; + s->img_buffer = s->img_buffer_original = (stbi_uc *) buffer; + s->img_buffer_end = s->img_buffer_original_end = (stbi_uc *) buffer+len; +} + +// initialize a callback-based context +static void stbi__start_callbacks(stbi__context *s, stbi_io_callbacks *c, void *user) +{ + s->io = *c; + s->io_user_data = user; + s->buflen = sizeof(s->buffer_start); + s->read_from_callbacks = 1; + s->img_buffer_original = s->buffer_start; + stbi__refill_buffer(s); + s->img_buffer_original_end = s->img_buffer_end; +} + +#ifndef STBI_NO_STDIO + +static int stbi__stdio_read(void *user, char *data, int size) +{ + return (int) fread(data,1,size,(FILE*) user); +} + +static void stbi__stdio_skip(void *user, int n) +{ + fseek((FILE*) user, n, SEEK_CUR); +} + +static int stbi__stdio_eof(void *user) +{ + return feof((FILE*) user); +} + +static stbi_io_callbacks stbi__stdio_callbacks = +{ + stbi__stdio_read, + stbi__stdio_skip, + stbi__stdio_eof, +}; + +static void stbi__start_file(stbi__context *s, FILE *f) +{ + stbi__start_callbacks(s, &stbi__stdio_callbacks, (void *) f); +} + +//static void stop_file(stbi__context *s) { } + +#endif // !STBI_NO_STDIO + +static void stbi__rewind(stbi__context *s) +{ + // conceptually rewind SHOULD rewind to the beginning of the stream, + // but we just rewind to the beginning of the initial buffer, because + // we only use it after doing 'test', which only ever looks at at most 92 bytes + s->img_buffer = s->img_buffer_original; + s->img_buffer_end = s->img_buffer_original_end; +} + +#ifndef STBI_NO_JPEG +static int stbi__jpeg_test(stbi__context *s); +static stbi_uc *stbi__jpeg_load(stbi__context *s, int *x, int *y, int *comp, int req_comp); +static int stbi__jpeg_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PNG +static int stbi__png_test(stbi__context *s); +static stbi_uc *stbi__png_load(stbi__context *s, int *x, int *y, int *comp, int req_comp); +static int stbi__png_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_BMP +static int stbi__bmp_test(stbi__context *s); +static stbi_uc *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req_comp); +static int stbi__bmp_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_TGA +static int stbi__tga_test(stbi__context *s); +static stbi_uc *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req_comp); +static int stbi__tga_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PSD +static int stbi__psd_test(stbi__context *s); +static stbi_uc *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int req_comp); +static int stbi__psd_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_HDR +static int stbi__hdr_test(stbi__context *s); +static float *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int req_comp); +static int stbi__hdr_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PIC +static int stbi__pic_test(stbi__context *s); +static stbi_uc *stbi__pic_load(stbi__context *s, int *x, int *y, int *comp, int req_comp); +static int stbi__pic_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_GIF +static int stbi__gif_test(stbi__context *s); +static stbi_uc *stbi__gif_load(stbi__context *s, int *x, int *y, int *comp, int req_comp); +static int stbi__gif_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PNM +static int stbi__pnm_test(stbi__context *s); +static stbi_uc *stbi__pnm_load(stbi__context *s, int *x, int *y, int *comp, int req_comp); +static int stbi__pnm_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +// this is not threadsafe +static const char *stbi__g_failure_reason; + +STBIDEF const char *stbi_failure_reason(void) +{ + return stbi__g_failure_reason; +} + +static int stbi__err(const char *str) +{ + stbi__g_failure_reason = str; + return 0; +} + +static void *stbi__malloc(size_t size) +{ + return STBI_MALLOC(size); +} + +// stbi__err - error +// stbi__errpf - error returning pointer to float +// stbi__errpuc - error returning pointer to unsigned char + +#ifdef STBI_NO_FAILURE_STRINGS + #define stbi__err(x,y) 0 +#elif defined(STBI_FAILURE_USERMSG) + #define stbi__err(x,y) stbi__err(y) +#else + #define stbi__err(x,y) stbi__err(x) +#endif + +#define stbi__errpf(x,y) ((float *)(size_t) (stbi__err(x,y)?NULL:NULL)) +#define stbi__errpuc(x,y) ((unsigned char *)(size_t) (stbi__err(x,y)?NULL:NULL)) + +STBIDEF void stbi_image_free(void *retval_from_stbi_load) +{ + STBI_FREE(retval_from_stbi_load); +} + +#ifndef STBI_NO_LINEAR +static float *stbi__ldr_to_hdr(stbi_uc *data, int x, int y, int comp); +#endif + +#ifndef STBI_NO_HDR +static stbi_uc *stbi__hdr_to_ldr(float *data, int x, int y, int comp); +#endif + +static int stbi__vertically_flip_on_load = 0; + +STBIDEF void stbi_set_flip_vertically_on_load(int flag_true_if_should_flip) +{ + stbi__vertically_flip_on_load = flag_true_if_should_flip; +} + +static unsigned char *stbi__load_main(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + #ifndef STBI_NO_JPEG + if (stbi__jpeg_test(s)) return stbi__jpeg_load(s,x,y,comp,req_comp); + #endif + #ifndef STBI_NO_PNG + if (stbi__png_test(s)) return stbi__png_load(s,x,y,comp,req_comp); + #endif + #ifndef STBI_NO_BMP + if (stbi__bmp_test(s)) return stbi__bmp_load(s,x,y,comp,req_comp); + #endif + #ifndef STBI_NO_GIF + if (stbi__gif_test(s)) return stbi__gif_load(s,x,y,comp,req_comp); + #endif + #ifndef STBI_NO_PSD + if (stbi__psd_test(s)) return stbi__psd_load(s,x,y,comp,req_comp); + #endif + #ifndef STBI_NO_PIC + if (stbi__pic_test(s)) return stbi__pic_load(s,x,y,comp,req_comp); + #endif + #ifndef STBI_NO_PNM + if (stbi__pnm_test(s)) return stbi__pnm_load(s,x,y,comp,req_comp); + #endif + + #ifndef STBI_NO_HDR + if (stbi__hdr_test(s)) { + float *hdr = stbi__hdr_load(s, x,y,comp,req_comp); + return stbi__hdr_to_ldr(hdr, *x, *y, req_comp ? req_comp : *comp); + } + #endif + + #ifndef STBI_NO_TGA + // test tga last because it's a crappy test! + if (stbi__tga_test(s)) + return stbi__tga_load(s,x,y,comp,req_comp); + #endif + + return stbi__errpuc("unknown image type", "Image not of any known type, or corrupt"); +} + +static unsigned char *stbi__load_flip(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + unsigned char *result = stbi__load_main(s, x, y, comp, req_comp); + + if (stbi__vertically_flip_on_load && result != NULL) { + int w = *x, h = *y; + int depth = req_comp ? req_comp : *comp; + int row,col,z; + stbi_uc temp; + + // @OPTIMIZE: use a bigger temp buffer and memcpy multiple pixels at once + for (row = 0; row < (h>>1); row++) { + for (col = 0; col < w; col++) { + for (z = 0; z < depth; z++) { + temp = result[(row * w + col) * depth + z]; + result[(row * w + col) * depth + z] = result[((h - row - 1) * w + col) * depth + z]; + result[((h - row - 1) * w + col) * depth + z] = temp; + } + } + } + } + + return result; +} + +#ifndef STBI_NO_HDR +static void stbi__float_postprocess(float *result, int *x, int *y, int *comp, int req_comp) +{ + if (stbi__vertically_flip_on_load && result != NULL) { + int w = *x, h = *y; + int depth = req_comp ? req_comp : *comp; + int row,col,z; + float temp; + + // @OPTIMIZE: use a bigger temp buffer and memcpy multiple pixels at once + for (row = 0; row < (h>>1); row++) { + for (col = 0; col < w; col++) { + for (z = 0; z < depth; z++) { + temp = result[(row * w + col) * depth + z]; + result[(row * w + col) * depth + z] = result[((h - row - 1) * w + col) * depth + z]; + result[((h - row - 1) * w + col) * depth + z] = temp; + } + } + } + } +} +#endif + +#ifndef STBI_NO_STDIO + +static FILE *stbi__fopen(char const *filename, char const *mode) +{ + FILE *f; +#if defined(_MSC_VER) && _MSC_VER >= 1400 + if (0 != fopen_s(&f, filename, mode)) + f=0; +#else + f = fopen(filename, mode); +#endif + return f; +} + + +STBIDEF stbi_uc *stbi_load(char const *filename, int *x, int *y, int *comp, int req_comp) +{ + FILE *f = stbi__fopen(filename, "rb"); + unsigned char *result; + if (!f) return stbi__errpuc("can't fopen", "Unable to open file"); + result = stbi_load_from_file(f,x,y,comp,req_comp); + fclose(f); + return result; +} + +STBIDEF stbi_uc *stbi_load_from_file(FILE *f, int *x, int *y, int *comp, int req_comp) +{ + unsigned char *result; + stbi__context s; + stbi__start_file(&s,f); + result = stbi__load_flip(&s,x,y,comp,req_comp); + if (result) { + // need to 'unget' all the characters in the IO buffer + fseek(f, - (int) (s.img_buffer_end - s.img_buffer), SEEK_CUR); + } + return result; +} +#endif //!STBI_NO_STDIO + +STBIDEF stbi_uc *stbi_load_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__load_flip(&s,x,y,comp,req_comp); +} + +STBIDEF stbi_uc *stbi_load_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); + return stbi__load_flip(&s,x,y,comp,req_comp); +} + +#ifndef STBI_NO_LINEAR +static float *stbi__loadf_main(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + unsigned char *data; + #ifndef STBI_NO_HDR + if (stbi__hdr_test(s)) { + float *hdr_data = stbi__hdr_load(s,x,y,comp,req_comp); + if (hdr_data) + stbi__float_postprocess(hdr_data,x,y,comp,req_comp); + return hdr_data; + } + #endif + data = stbi__load_flip(s, x, y, comp, req_comp); + if (data) + return stbi__ldr_to_hdr(data, *x, *y, req_comp ? req_comp : *comp); + return stbi__errpf("unknown image type", "Image not of any known type, or corrupt"); +} + +STBIDEF float *stbi_loadf_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__loadf_main(&s,x,y,comp,req_comp); +} + +STBIDEF float *stbi_loadf_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); + return stbi__loadf_main(&s,x,y,comp,req_comp); +} + +#ifndef STBI_NO_STDIO +STBIDEF float *stbi_loadf(char const *filename, int *x, int *y, int *comp, int req_comp) +{ + float *result; + FILE *f = stbi__fopen(filename, "rb"); + if (!f) return stbi__errpf("can't fopen", "Unable to open file"); + result = stbi_loadf_from_file(f,x,y,comp,req_comp); + fclose(f); + return result; +} + +STBIDEF float *stbi_loadf_from_file(FILE *f, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_file(&s,f); + return stbi__loadf_main(&s,x,y,comp,req_comp); +} +#endif // !STBI_NO_STDIO + +#endif // !STBI_NO_LINEAR + +// these is-hdr-or-not is defined independent of whether STBI_NO_LINEAR is +// defined, for API simplicity; if STBI_NO_LINEAR is defined, it always +// reports false! + +STBIDEF int stbi_is_hdr_from_memory(stbi_uc const *buffer, int len) +{ + #ifndef STBI_NO_HDR + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__hdr_test(&s); + #else + STBI_NOTUSED(buffer); + STBI_NOTUSED(len); + return 0; + #endif +} + +#ifndef STBI_NO_STDIO +STBIDEF int stbi_is_hdr (char const *filename) +{ + FILE *f = stbi__fopen(filename, "rb"); + int result=0; + if (f) { + result = stbi_is_hdr_from_file(f); + fclose(f); + } + return result; +} + +STBIDEF int stbi_is_hdr_from_file(FILE *f) +{ + #ifndef STBI_NO_HDR + stbi__context s; + stbi__start_file(&s,f); + return stbi__hdr_test(&s); + #else + STBI_NOTUSED(f); + return 0; + #endif +} +#endif // !STBI_NO_STDIO + +STBIDEF int stbi_is_hdr_from_callbacks(stbi_io_callbacks const *clbk, void *user) +{ + #ifndef STBI_NO_HDR + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); + return stbi__hdr_test(&s); + #else + STBI_NOTUSED(clbk); + STBI_NOTUSED(user); + return 0; + #endif +} + +#ifndef STBI_NO_LINEAR +static float stbi__l2h_gamma=2.2f, stbi__l2h_scale=1.0f; + +STBIDEF void stbi_ldr_to_hdr_gamma(float gamma) { stbi__l2h_gamma = gamma; } +STBIDEF void stbi_ldr_to_hdr_scale(float scale) { stbi__l2h_scale = scale; } +#endif + +static float stbi__h2l_gamma_i=1.0f/2.2f, stbi__h2l_scale_i=1.0f; + +STBIDEF void stbi_hdr_to_ldr_gamma(float gamma) { stbi__h2l_gamma_i = 1/gamma; } +STBIDEF void stbi_hdr_to_ldr_scale(float scale) { stbi__h2l_scale_i = 1/scale; } + + +////////////////////////////////////////////////////////////////////////////// +// +// Common code used by all image loaders +// + +enum +{ + STBI__SCAN_load=0, + STBI__SCAN_type, + STBI__SCAN_header +}; + +static void stbi__refill_buffer(stbi__context *s) +{ + int n = (s->io.read)(s->io_user_data,(char*)s->buffer_start,s->buflen); + if (n == 0) { + // at end of file, treat same as if from memory, but need to handle case + // where s->img_buffer isn't pointing to safe memory, e.g. 0-byte file + s->read_from_callbacks = 0; + s->img_buffer = s->buffer_start; + s->img_buffer_end = s->buffer_start+1; + *s->img_buffer = 0; + } else { + s->img_buffer = s->buffer_start; + s->img_buffer_end = s->buffer_start + n; + } +} + +stbi_inline static stbi_uc stbi__get8(stbi__context *s) +{ + if (s->img_buffer < s->img_buffer_end) + return *s->img_buffer++; + if (s->read_from_callbacks) { + stbi__refill_buffer(s); + return *s->img_buffer++; + } + return 0; +} + +stbi_inline static int stbi__at_eof(stbi__context *s) +{ + if (s->io.read) { + if (!(s->io.eof)(s->io_user_data)) return 0; + // if feof() is true, check if buffer = end + // special case: we've only got the special 0 character at the end + if (s->read_from_callbacks == 0) return 1; + } + + return s->img_buffer >= s->img_buffer_end; +} + +static void stbi__skip(stbi__context *s, int n) +{ + if (n < 0) { + s->img_buffer = s->img_buffer_end; + return; + } + if (s->io.read) { + int blen = (int) (s->img_buffer_end - s->img_buffer); + if (blen < n) { + s->img_buffer = s->img_buffer_end; + (s->io.skip)(s->io_user_data, n - blen); + return; + } + } + s->img_buffer += n; +} + +static int stbi__getn(stbi__context *s, stbi_uc *buffer, int n) +{ + if (s->io.read) { + int blen = (int) (s->img_buffer_end - s->img_buffer); + if (blen < n) { + int res, count; + + memcpy(buffer, s->img_buffer, blen); + + count = (s->io.read)(s->io_user_data, (char*) buffer + blen, n - blen); + res = (count == (n-blen)); + s->img_buffer = s->img_buffer_end; + return res; + } + } + + if (s->img_buffer+n <= s->img_buffer_end) { + memcpy(buffer, s->img_buffer, n); + s->img_buffer += n; + return 1; + } else + return 0; +} + +static int stbi__get16be(stbi__context *s) +{ + int z = stbi__get8(s); + return (z << 8) + stbi__get8(s); +} + +static stbi__uint32 stbi__get32be(stbi__context *s) +{ + stbi__uint32 z = stbi__get16be(s); + return (z << 16) + stbi__get16be(s); +} + +#if defined(STBI_NO_BMP) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) +// nothing +#else +static int stbi__get16le(stbi__context *s) +{ + int z = stbi__get8(s); + return z + (stbi__get8(s) << 8); +} +#endif + +#ifndef STBI_NO_BMP +static stbi__uint32 stbi__get32le(stbi__context *s) +{ + stbi__uint32 z = stbi__get16le(s); + return z + (stbi__get16le(s) << 16); +} +#endif + +#define STBI__BYTECAST(x) ((stbi_uc) ((x) & 255)) // truncate int to byte without warnings + + +////////////////////////////////////////////////////////////////////////////// +// +// generic converter from built-in img_n to req_comp +// individual types do this automatically as much as possible (e.g. jpeg +// does all cases internally since it needs to colorspace convert anyway, +// and it never has alpha, so very few cases ). png can automatically +// interleave an alpha=255 channel, but falls back to this for other cases +// +// assume data buffer is malloced, so malloc a new one and free that one +// only failure mode is malloc failing + +static stbi_uc stbi__compute_y(int r, int g, int b) +{ + return (stbi_uc) (((r*77) + (g*150) + (29*b)) >> 8); +} + +static unsigned char *stbi__convert_format(unsigned char *data, int img_n, int req_comp, unsigned int x, unsigned int y) +{ + int i,j; + unsigned char *good; + + if (req_comp == img_n) return data; + STBI_ASSERT(req_comp >= 1 && req_comp <= 4); + + good = (unsigned char *) stbi__malloc(req_comp * x * y); + if (good == NULL) { + STBI_FREE(data); + return stbi__errpuc("outofmem", "Out of memory"); + } + + for (j=0; j < (int) y; ++j) { + unsigned char *src = data + j * x * img_n ; + unsigned char *dest = good + j * x * req_comp; + + #define COMBO(a,b) ((a)*8+(b)) + #define CASE(a,b) case COMBO(a,b): for(i=x-1; i >= 0; --i, src += a, dest += b) + // convert source image with img_n components to one with req_comp components; + // avoid switch per pixel, so use switch per scanline and massive macros + switch (COMBO(img_n, req_comp)) { + CASE(1,2) dest[0]=src[0], dest[1]=255; break; + CASE(1,3) dest[0]=dest[1]=dest[2]=src[0]; break; + CASE(1,4) dest[0]=dest[1]=dest[2]=src[0], dest[3]=255; break; + CASE(2,1) dest[0]=src[0]; break; + CASE(2,3) dest[0]=dest[1]=dest[2]=src[0]; break; + CASE(2,4) dest[0]=dest[1]=dest[2]=src[0], dest[3]=src[1]; break; + CASE(3,4) dest[0]=src[0],dest[1]=src[1],dest[2]=src[2],dest[3]=255; break; + CASE(3,1) dest[0]=stbi__compute_y(src[0],src[1],src[2]); break; + CASE(3,2) dest[0]=stbi__compute_y(src[0],src[1],src[2]), dest[1] = 255; break; + CASE(4,1) dest[0]=stbi__compute_y(src[0],src[1],src[2]); break; + CASE(4,2) dest[0]=stbi__compute_y(src[0],src[1],src[2]), dest[1] = src[3]; break; + CASE(4,3) dest[0]=src[0],dest[1]=src[1],dest[2]=src[2]; break; + default: STBI_ASSERT(0); + } + #undef CASE + } + + STBI_FREE(data); + return good; +} + +#ifndef STBI_NO_LINEAR +static float *stbi__ldr_to_hdr(stbi_uc *data, int x, int y, int comp) +{ + int i,k,n; + float *output = (float *) stbi__malloc(x * y * comp * sizeof(float)); + if (output == NULL) { STBI_FREE(data); return stbi__errpf("outofmem", "Out of memory"); } + // compute number of non-alpha components + if (comp & 1) n = comp; else n = comp-1; + for (i=0; i < x*y; ++i) { + for (k=0; k < n; ++k) { + output[i*comp + k] = (float) (pow(data[i*comp+k]/255.0f, stbi__l2h_gamma) * stbi__l2h_scale); + } + if (k < comp) output[i*comp + k] = data[i*comp+k]/255.0f; + } + STBI_FREE(data); + return output; +} +#endif + +#ifndef STBI_NO_HDR +#define stbi__float2int(x) ((int) (x)) +static stbi_uc *stbi__hdr_to_ldr(float *data, int x, int y, int comp) +{ + int i,k,n; + stbi_uc *output = (stbi_uc *) stbi__malloc(x * y * comp); + if (output == NULL) { STBI_FREE(data); return stbi__errpuc("outofmem", "Out of memory"); } + // compute number of non-alpha components + if (comp & 1) n = comp; else n = comp-1; + for (i=0; i < x*y; ++i) { + for (k=0; k < n; ++k) { + float z = (float) pow(data[i*comp+k]*stbi__h2l_scale_i, stbi__h2l_gamma_i) * 255 + 0.5f; + if (z < 0) z = 0; + if (z > 255) z = 255; + output[i*comp + k] = (stbi_uc) stbi__float2int(z); + } + if (k < comp) { + float z = data[i*comp+k] * 255 + 0.5f; + if (z < 0) z = 0; + if (z > 255) z = 255; + output[i*comp + k] = (stbi_uc) stbi__float2int(z); + } + } + STBI_FREE(data); + return output; +} +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// "baseline" JPEG/JFIF decoder +// +// simple implementation +// - doesn't support delayed output of y-dimension +// - simple interface (only one output format: 8-bit interleaved RGB) +// - doesn't try to recover corrupt jpegs +// - doesn't allow partial loading, loading multiple at once +// - still fast on x86 (copying globals into locals doesn't help x86) +// - allocates lots of intermediate memory (full size of all components) +// - non-interleaved case requires this anyway +// - allows good upsampling (see next) +// high-quality +// - upsampled channels are bilinearly interpolated, even across blocks +// - quality integer IDCT derived from IJG's 'slow' +// performance +// - fast huffman; reasonable integer IDCT +// - some SIMD kernels for common paths on targets with SSE2/NEON +// - uses a lot of intermediate memory, could cache poorly + +#ifndef STBI_NO_JPEG + +// huffman decoding acceleration +#define FAST_BITS 9 // larger handles more cases; smaller stomps less cache + +typedef struct +{ + stbi_uc fast[1 << FAST_BITS]; + // weirdly, repacking this into AoS is a 10% speed loss, instead of a win + stbi__uint16 code[256]; + stbi_uc values[256]; + stbi_uc size[257]; + unsigned int maxcode[18]; + int delta[17]; // old 'firstsymbol' - old 'firstcode' +} stbi__huffman; + +typedef struct +{ + stbi__context *s; + stbi__huffman huff_dc[4]; + stbi__huffman huff_ac[4]; + stbi_uc dequant[4][64]; + stbi__int16 fast_ac[4][1 << FAST_BITS]; + +// sizes for components, interleaved MCUs + int img_h_max, img_v_max; + int img_mcu_x, img_mcu_y; + int img_mcu_w, img_mcu_h; + +// definition of jpeg image component + struct + { + int id; + int h,v; + int tq; + int hd,ha; + int dc_pred; + + int x,y,w2,h2; + stbi_uc *data; + void *raw_data, *raw_coeff; + stbi_uc *linebuf; + short *coeff; // progressive only + int coeff_w, coeff_h; // number of 8x8 coefficient blocks + } img_comp[4]; + + stbi__uint32 code_buffer; // jpeg entropy-coded buffer + int code_bits; // number of valid bits + unsigned char marker; // marker seen while filling entropy buffer + int nomore; // flag if we saw a marker so must stop + + int progressive; + int spec_start; + int spec_end; + int succ_high; + int succ_low; + int eob_run; + int rgb; + + int scan_n, order[4]; + int restart_interval, todo; + +// kernels + void (*idct_block_kernel)(stbi_uc *out, int out_stride, short data[64]); + void (*YCbCr_to_RGB_kernel)(stbi_uc *out, const stbi_uc *y, const stbi_uc *pcb, const stbi_uc *pcr, int count, int step); + stbi_uc *(*resample_row_hv_2_kernel)(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs); +} stbi__jpeg; + +static int stbi__build_huffman(stbi__huffman *h, int *count) +{ + int i,j,k=0,code; + // build size list for each symbol (from JPEG spec) + for (i=0; i < 16; ++i) + for (j=0; j < count[i]; ++j) + h->size[k++] = (stbi_uc) (i+1); + h->size[k] = 0; + + // compute actual symbols (from jpeg spec) + code = 0; + k = 0; + for(j=1; j <= 16; ++j) { + // compute delta to add to code to compute symbol id + h->delta[j] = k - code; + if (h->size[k] == j) { + while (h->size[k] == j) + h->code[k++] = (stbi__uint16) (code++); + if (code-1 >= (1 << j)) return stbi__err("bad code lengths","Corrupt JPEG"); + } + // compute largest code + 1 for this size, preshifted as needed later + h->maxcode[j] = code << (16-j); + code <<= 1; + } + h->maxcode[j] = 0xffffffff; + + // build non-spec acceleration table; 255 is flag for not-accelerated + memset(h->fast, 255, 1 << FAST_BITS); + for (i=0; i < k; ++i) { + int s = h->size[i]; + if (s <= FAST_BITS) { + int c = h->code[i] << (FAST_BITS-s); + int m = 1 << (FAST_BITS-s); + for (j=0; j < m; ++j) { + h->fast[c+j] = (stbi_uc) i; + } + } + } + return 1; +} + +// build a table that decodes both magnitude and value of small ACs in +// one go. +static void stbi__build_fast_ac(stbi__int16 *fast_ac, stbi__huffman *h) +{ + int i; + for (i=0; i < (1 << FAST_BITS); ++i) { + stbi_uc fast = h->fast[i]; + fast_ac[i] = 0; + if (fast < 255) { + int rs = h->values[fast]; + int run = (rs >> 4) & 15; + int magbits = rs & 15; + int len = h->size[fast]; + + if (magbits && len + magbits <= FAST_BITS) { + // magnitude code followed by receive_extend code + int k = ((i << len) & ((1 << FAST_BITS) - 1)) >> (FAST_BITS - magbits); + int m = 1 << (magbits - 1); + if (k < m) k += (-1 << magbits) + 1; + // if the result is small enough, we can fit it in fast_ac table + if (k >= -128 && k <= 127) + fast_ac[i] = (stbi__int16) ((k << 8) + (run << 4) + (len + magbits)); + } + } + } +} + +static void stbi__grow_buffer_unsafe(stbi__jpeg *j) +{ + do { + int b = j->nomore ? 0 : stbi__get8(j->s); + if (b == 0xff) { + int c = stbi__get8(j->s); + if (c != 0) { + j->marker = (unsigned char) c; + j->nomore = 1; + return; + } + } + j->code_buffer |= b << (24 - j->code_bits); + j->code_bits += 8; + } while (j->code_bits <= 24); +} + +// (1 << n) - 1 +static stbi__uint32 stbi__bmask[17]={0,1,3,7,15,31,63,127,255,511,1023,2047,4095,8191,16383,32767,65535}; + +// decode a jpeg huffman value from the bitstream +stbi_inline static int stbi__jpeg_huff_decode(stbi__jpeg *j, stbi__huffman *h) +{ + unsigned int temp; + int c,k; + + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + + // look at the top FAST_BITS and determine what symbol ID it is, + // if the code is <= FAST_BITS + c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); + k = h->fast[c]; + if (k < 255) { + int s = h->size[k]; + if (s > j->code_bits) + return -1; + j->code_buffer <<= s; + j->code_bits -= s; + return h->values[k]; + } + + // naive test is to shift the code_buffer down so k bits are + // valid, then test against maxcode. To speed this up, we've + // preshifted maxcode left so that it has (16-k) 0s at the + // end; in other words, regardless of the number of bits, it + // wants to be compared against something shifted to have 16; + // that way we don't need to shift inside the loop. + temp = j->code_buffer >> 16; + for (k=FAST_BITS+1 ; ; ++k) + if (temp < h->maxcode[k]) + break; + if (k == 17) { + // error! code not found + j->code_bits -= 16; + return -1; + } + + if (k > j->code_bits) + return -1; + + // convert the huffman code to the symbol id + c = ((j->code_buffer >> (32 - k)) & stbi__bmask[k]) + h->delta[k]; + STBI_ASSERT((((j->code_buffer) >> (32 - h->size[c])) & stbi__bmask[h->size[c]]) == h->code[c]); + + // convert the id to a symbol + j->code_bits -= k; + j->code_buffer <<= k; + return h->values[c]; +} + +// bias[n] = (-1<code_bits < n) stbi__grow_buffer_unsafe(j); + + sgn = (stbi__int32)j->code_buffer >> 31; // sign bit is always in MSB + k = stbi_lrot(j->code_buffer, n); + STBI_ASSERT(n >= 0 && n < (int) (sizeof(stbi__bmask)/sizeof(*stbi__bmask))); + j->code_buffer = k & ~stbi__bmask[n]; + k &= stbi__bmask[n]; + j->code_bits -= n; + return k + (stbi__jbias[n] & ~sgn); +} + +// get some unsigned bits +stbi_inline static int stbi__jpeg_get_bits(stbi__jpeg *j, int n) +{ + unsigned int k; + if (j->code_bits < n) stbi__grow_buffer_unsafe(j); + k = stbi_lrot(j->code_buffer, n); + j->code_buffer = k & ~stbi__bmask[n]; + k &= stbi__bmask[n]; + j->code_bits -= n; + return k; +} + +stbi_inline static int stbi__jpeg_get_bit(stbi__jpeg *j) +{ + unsigned int k; + if (j->code_bits < 1) stbi__grow_buffer_unsafe(j); + k = j->code_buffer; + j->code_buffer <<= 1; + --j->code_bits; + return k & 0x80000000; +} + +// given a value that's at position X in the zigzag stream, +// where does it appear in the 8x8 matrix coded as row-major? +static stbi_uc stbi__jpeg_dezigzag[64+15] = +{ + 0, 1, 8, 16, 9, 2, 3, 10, + 17, 24, 32, 25, 18, 11, 4, 5, + 12, 19, 26, 33, 40, 48, 41, 34, + 27, 20, 13, 6, 7, 14, 21, 28, + 35, 42, 49, 56, 57, 50, 43, 36, + 29, 22, 15, 23, 30, 37, 44, 51, + 58, 59, 52, 45, 38, 31, 39, 46, + 53, 60, 61, 54, 47, 55, 62, 63, + // let corrupt input sample past end + 63, 63, 63, 63, 63, 63, 63, 63, + 63, 63, 63, 63, 63, 63, 63 +}; + +// decode one 64-entry block-- +static int stbi__jpeg_decode_block(stbi__jpeg *j, short data[64], stbi__huffman *hdc, stbi__huffman *hac, stbi__int16 *fac, int b, stbi_uc *dequant) +{ + int diff,dc,k; + int t; + + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + t = stbi__jpeg_huff_decode(j, hdc); + if (t < 0) return stbi__err("bad huffman code","Corrupt JPEG"); + + // 0 all the ac values now so we can do it 32-bits at a time + memset(data,0,64*sizeof(data[0])); + + diff = t ? stbi__extend_receive(j, t) : 0; + dc = j->img_comp[b].dc_pred + diff; + j->img_comp[b].dc_pred = dc; + data[0] = (short) (dc * dequant[0]); + + // decode AC components, see JPEG spec + k = 1; + do { + unsigned int zig; + int c,r,s; + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); + r = fac[c]; + if (r) { // fast-AC path + k += (r >> 4) & 15; // run + s = r & 15; // combined length + j->code_buffer <<= s; + j->code_bits -= s; + // decode into unzigzag'd location + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) ((r >> 8) * dequant[zig]); + } else { + int rs = stbi__jpeg_huff_decode(j, hac); + if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); + s = rs & 15; + r = rs >> 4; + if (s == 0) { + if (rs != 0xf0) break; // end block + k += 16; + } else { + k += r; + // decode into unzigzag'd location + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) (stbi__extend_receive(j,s) * dequant[zig]); + } + } + } while (k < 64); + return 1; +} + +static int stbi__jpeg_decode_block_prog_dc(stbi__jpeg *j, short data[64], stbi__huffman *hdc, int b) +{ + int diff,dc; + int t; + if (j->spec_end != 0) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); + + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + + if (j->succ_high == 0) { + // first scan for DC coefficient, must be first + memset(data,0,64*sizeof(data[0])); // 0 all the ac values now + t = stbi__jpeg_huff_decode(j, hdc); + diff = t ? stbi__extend_receive(j, t) : 0; + + dc = j->img_comp[b].dc_pred + diff; + j->img_comp[b].dc_pred = dc; + data[0] = (short) (dc << j->succ_low); + } else { + // refinement scan for DC coefficient + if (stbi__jpeg_get_bit(j)) + data[0] += (short) (1 << j->succ_low); + } + return 1; +} + +// @OPTIMIZE: store non-zigzagged during the decode passes, +// and only de-zigzag when dequantizing +static int stbi__jpeg_decode_block_prog_ac(stbi__jpeg *j, short data[64], stbi__huffman *hac, stbi__int16 *fac) +{ + int k; + if (j->spec_start == 0) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); + + if (j->succ_high == 0) { + int shift = j->succ_low; + + if (j->eob_run) { + --j->eob_run; + return 1; + } + + k = j->spec_start; + do { + unsigned int zig; + int c,r,s; + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); + r = fac[c]; + if (r) { // fast-AC path + k += (r >> 4) & 15; // run + s = r & 15; // combined length + j->code_buffer <<= s; + j->code_bits -= s; + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) ((r >> 8) << shift); + } else { + int rs = stbi__jpeg_huff_decode(j, hac); + if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); + s = rs & 15; + r = rs >> 4; + if (s == 0) { + if (r < 15) { + j->eob_run = (1 << r); + if (r) + j->eob_run += stbi__jpeg_get_bits(j, r); + --j->eob_run; + break; + } + k += 16; + } else { + k += r; + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) (stbi__extend_receive(j,s) << shift); + } + } + } while (k <= j->spec_end); + } else { + // refinement scan for these AC coefficients + + short bit = (short) (1 << j->succ_low); + + if (j->eob_run) { + --j->eob_run; + for (k = j->spec_start; k <= j->spec_end; ++k) { + short *p = &data[stbi__jpeg_dezigzag[k]]; + if (*p != 0) + if (stbi__jpeg_get_bit(j)) + if ((*p & bit)==0) { + if (*p > 0) + *p += bit; + else + *p -= bit; + } + } + } else { + k = j->spec_start; + do { + int r,s; + int rs = stbi__jpeg_huff_decode(j, hac); // @OPTIMIZE see if we can use the fast path here, advance-by-r is so slow, eh + if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); + s = rs & 15; + r = rs >> 4; + if (s == 0) { + if (r < 15) { + j->eob_run = (1 << r) - 1; + if (r) + j->eob_run += stbi__jpeg_get_bits(j, r); + r = 64; // force end of block + } else { + // r=15 s=0 should write 16 0s, so we just do + // a run of 15 0s and then write s (which is 0), + // so we don't have to do anything special here + } + } else { + if (s != 1) return stbi__err("bad huffman code", "Corrupt JPEG"); + // sign bit + if (stbi__jpeg_get_bit(j)) + s = bit; + else + s = -bit; + } + + // advance by r + while (k <= j->spec_end) { + short *p = &data[stbi__jpeg_dezigzag[k++]]; + if (*p != 0) { + if (stbi__jpeg_get_bit(j)) + if ((*p & bit)==0) { + if (*p > 0) + *p += bit; + else + *p -= bit; + } + } else { + if (r == 0) { + *p = (short) s; + break; + } + --r; + } + } + } while (k <= j->spec_end); + } + } + return 1; +} + +// take a -128..127 value and stbi__clamp it and convert to 0..255 +stbi_inline static stbi_uc stbi__clamp(int x) +{ + // trick to use a single test to catch both cases + if ((unsigned int) x > 255) { + if (x < 0) return 0; + if (x > 255) return 255; + } + return (stbi_uc) x; +} + +#define stbi__f2f(x) ((int) (((x) * 4096 + 0.5))) +#define stbi__fsh(x) ((x) << 12) + +// derived from jidctint -- DCT_ISLOW +#define STBI__IDCT_1D(s0,s1,s2,s3,s4,s5,s6,s7) \ + int t0,t1,t2,t3,p1,p2,p3,p4,p5,x0,x1,x2,x3; \ + p2 = s2; \ + p3 = s6; \ + p1 = (p2+p3) * stbi__f2f(0.5411961f); \ + t2 = p1 + p3*stbi__f2f(-1.847759065f); \ + t3 = p1 + p2*stbi__f2f( 0.765366865f); \ + p2 = s0; \ + p3 = s4; \ + t0 = stbi__fsh(p2+p3); \ + t1 = stbi__fsh(p2-p3); \ + x0 = t0+t3; \ + x3 = t0-t3; \ + x1 = t1+t2; \ + x2 = t1-t2; \ + t0 = s7; \ + t1 = s5; \ + t2 = s3; \ + t3 = s1; \ + p3 = t0+t2; \ + p4 = t1+t3; \ + p1 = t0+t3; \ + p2 = t1+t2; \ + p5 = (p3+p4)*stbi__f2f( 1.175875602f); \ + t0 = t0*stbi__f2f( 0.298631336f); \ + t1 = t1*stbi__f2f( 2.053119869f); \ + t2 = t2*stbi__f2f( 3.072711026f); \ + t3 = t3*stbi__f2f( 1.501321110f); \ + p1 = p5 + p1*stbi__f2f(-0.899976223f); \ + p2 = p5 + p2*stbi__f2f(-2.562915447f); \ + p3 = p3*stbi__f2f(-1.961570560f); \ + p4 = p4*stbi__f2f(-0.390180644f); \ + t3 += p1+p4; \ + t2 += p2+p3; \ + t1 += p2+p4; \ + t0 += p1+p3; + +static void stbi__idct_block(stbi_uc *out, int out_stride, short data[64]) +{ + int i,val[64],*v=val; + stbi_uc *o; + short *d = data; + + // columns + for (i=0; i < 8; ++i,++d, ++v) { + // if all zeroes, shortcut -- this avoids dequantizing 0s and IDCTing + if (d[ 8]==0 && d[16]==0 && d[24]==0 && d[32]==0 + && d[40]==0 && d[48]==0 && d[56]==0) { + // no shortcut 0 seconds + // (1|2|3|4|5|6|7)==0 0 seconds + // all separate -0.047 seconds + // 1 && 2|3 && 4|5 && 6|7: -0.047 seconds + int dcterm = d[0] << 2; + v[0] = v[8] = v[16] = v[24] = v[32] = v[40] = v[48] = v[56] = dcterm; + } else { + STBI__IDCT_1D(d[ 0],d[ 8],d[16],d[24],d[32],d[40],d[48],d[56]) + // constants scaled things up by 1<<12; let's bring them back + // down, but keep 2 extra bits of precision + x0 += 512; x1 += 512; x2 += 512; x3 += 512; + v[ 0] = (x0+t3) >> 10; + v[56] = (x0-t3) >> 10; + v[ 8] = (x1+t2) >> 10; + v[48] = (x1-t2) >> 10; + v[16] = (x2+t1) >> 10; + v[40] = (x2-t1) >> 10; + v[24] = (x3+t0) >> 10; + v[32] = (x3-t0) >> 10; + } + } + + for (i=0, v=val, o=out; i < 8; ++i,v+=8,o+=out_stride) { + // no fast case since the first 1D IDCT spread components out + STBI__IDCT_1D(v[0],v[1],v[2],v[3],v[4],v[5],v[6],v[7]) + // constants scaled things up by 1<<12, plus we had 1<<2 from first + // loop, plus horizontal and vertical each scale by sqrt(8) so together + // we've got an extra 1<<3, so 1<<17 total we need to remove. + // so we want to round that, which means adding 0.5 * 1<<17, + // aka 65536. Also, we'll end up with -128 to 127 that we want + // to encode as 0..255 by adding 128, so we'll add that before the shift + x0 += 65536 + (128<<17); + x1 += 65536 + (128<<17); + x2 += 65536 + (128<<17); + x3 += 65536 + (128<<17); + // tried computing the shifts into temps, or'ing the temps to see + // if any were out of range, but that was slower + o[0] = stbi__clamp((x0+t3) >> 17); + o[7] = stbi__clamp((x0-t3) >> 17); + o[1] = stbi__clamp((x1+t2) >> 17); + o[6] = stbi__clamp((x1-t2) >> 17); + o[2] = stbi__clamp((x2+t1) >> 17); + o[5] = stbi__clamp((x2-t1) >> 17); + o[3] = stbi__clamp((x3+t0) >> 17); + o[4] = stbi__clamp((x3-t0) >> 17); + } +} + +#ifdef STBI_SSE2 +// sse2 integer IDCT. not the fastest possible implementation but it +// produces bit-identical results to the generic C version so it's +// fully "transparent". +static void stbi__idct_simd(stbi_uc *out, int out_stride, short data[64]) +{ + // This is constructed to match our regular (generic) integer IDCT exactly. + __m128i row0, row1, row2, row3, row4, row5, row6, row7; + __m128i tmp; + + // dot product constant: even elems=x, odd elems=y + #define dct_const(x,y) _mm_setr_epi16((x),(y),(x),(y),(x),(y),(x),(y)) + + // out(0) = c0[even]*x + c0[odd]*y (c0, x, y 16-bit, out 32-bit) + // out(1) = c1[even]*x + c1[odd]*y + #define dct_rot(out0,out1, x,y,c0,c1) \ + __m128i c0##lo = _mm_unpacklo_epi16((x),(y)); \ + __m128i c0##hi = _mm_unpackhi_epi16((x),(y)); \ + __m128i out0##_l = _mm_madd_epi16(c0##lo, c0); \ + __m128i out0##_h = _mm_madd_epi16(c0##hi, c0); \ + __m128i out1##_l = _mm_madd_epi16(c0##lo, c1); \ + __m128i out1##_h = _mm_madd_epi16(c0##hi, c1) + + // out = in << 12 (in 16-bit, out 32-bit) + #define dct_widen(out, in) \ + __m128i out##_l = _mm_srai_epi32(_mm_unpacklo_epi16(_mm_setzero_si128(), (in)), 4); \ + __m128i out##_h = _mm_srai_epi32(_mm_unpackhi_epi16(_mm_setzero_si128(), (in)), 4) + + // wide add + #define dct_wadd(out, a, b) \ + __m128i out##_l = _mm_add_epi32(a##_l, b##_l); \ + __m128i out##_h = _mm_add_epi32(a##_h, b##_h) + + // wide sub + #define dct_wsub(out, a, b) \ + __m128i out##_l = _mm_sub_epi32(a##_l, b##_l); \ + __m128i out##_h = _mm_sub_epi32(a##_h, b##_h) + + // butterfly a/b, add bias, then shift by "s" and pack + #define dct_bfly32o(out0, out1, a,b,bias,s) \ + { \ + __m128i abiased_l = _mm_add_epi32(a##_l, bias); \ + __m128i abiased_h = _mm_add_epi32(a##_h, bias); \ + dct_wadd(sum, abiased, b); \ + dct_wsub(dif, abiased, b); \ + out0 = _mm_packs_epi32(_mm_srai_epi32(sum_l, s), _mm_srai_epi32(sum_h, s)); \ + out1 = _mm_packs_epi32(_mm_srai_epi32(dif_l, s), _mm_srai_epi32(dif_h, s)); \ + } + + // 8-bit interleave step (for transposes) + #define dct_interleave8(a, b) \ + tmp = a; \ + a = _mm_unpacklo_epi8(a, b); \ + b = _mm_unpackhi_epi8(tmp, b) + + // 16-bit interleave step (for transposes) + #define dct_interleave16(a, b) \ + tmp = a; \ + a = _mm_unpacklo_epi16(a, b); \ + b = _mm_unpackhi_epi16(tmp, b) + + #define dct_pass(bias,shift) \ + { \ + /* even part */ \ + dct_rot(t2e,t3e, row2,row6, rot0_0,rot0_1); \ + __m128i sum04 = _mm_add_epi16(row0, row4); \ + __m128i dif04 = _mm_sub_epi16(row0, row4); \ + dct_widen(t0e, sum04); \ + dct_widen(t1e, dif04); \ + dct_wadd(x0, t0e, t3e); \ + dct_wsub(x3, t0e, t3e); \ + dct_wadd(x1, t1e, t2e); \ + dct_wsub(x2, t1e, t2e); \ + /* odd part */ \ + dct_rot(y0o,y2o, row7,row3, rot2_0,rot2_1); \ + dct_rot(y1o,y3o, row5,row1, rot3_0,rot3_1); \ + __m128i sum17 = _mm_add_epi16(row1, row7); \ + __m128i sum35 = _mm_add_epi16(row3, row5); \ + dct_rot(y4o,y5o, sum17,sum35, rot1_0,rot1_1); \ + dct_wadd(x4, y0o, y4o); \ + dct_wadd(x5, y1o, y5o); \ + dct_wadd(x6, y2o, y5o); \ + dct_wadd(x7, y3o, y4o); \ + dct_bfly32o(row0,row7, x0,x7,bias,shift); \ + dct_bfly32o(row1,row6, x1,x6,bias,shift); \ + dct_bfly32o(row2,row5, x2,x5,bias,shift); \ + dct_bfly32o(row3,row4, x3,x4,bias,shift); \ + } + + __m128i rot0_0 = dct_const(stbi__f2f(0.5411961f), stbi__f2f(0.5411961f) + stbi__f2f(-1.847759065f)); + __m128i rot0_1 = dct_const(stbi__f2f(0.5411961f) + stbi__f2f( 0.765366865f), stbi__f2f(0.5411961f)); + __m128i rot1_0 = dct_const(stbi__f2f(1.175875602f) + stbi__f2f(-0.899976223f), stbi__f2f(1.175875602f)); + __m128i rot1_1 = dct_const(stbi__f2f(1.175875602f), stbi__f2f(1.175875602f) + stbi__f2f(-2.562915447f)); + __m128i rot2_0 = dct_const(stbi__f2f(-1.961570560f) + stbi__f2f( 0.298631336f), stbi__f2f(-1.961570560f)); + __m128i rot2_1 = dct_const(stbi__f2f(-1.961570560f), stbi__f2f(-1.961570560f) + stbi__f2f( 3.072711026f)); + __m128i rot3_0 = dct_const(stbi__f2f(-0.390180644f) + stbi__f2f( 2.053119869f), stbi__f2f(-0.390180644f)); + __m128i rot3_1 = dct_const(stbi__f2f(-0.390180644f), stbi__f2f(-0.390180644f) + stbi__f2f( 1.501321110f)); + + // rounding biases in column/row passes, see stbi__idct_block for explanation. + __m128i bias_0 = _mm_set1_epi32(512); + __m128i bias_1 = _mm_set1_epi32(65536 + (128<<17)); + + // load + row0 = _mm_load_si128((const __m128i *) (data + 0*8)); + row1 = _mm_load_si128((const __m128i *) (data + 1*8)); + row2 = _mm_load_si128((const __m128i *) (data + 2*8)); + row3 = _mm_load_si128((const __m128i *) (data + 3*8)); + row4 = _mm_load_si128((const __m128i *) (data + 4*8)); + row5 = _mm_load_si128((const __m128i *) (data + 5*8)); + row6 = _mm_load_si128((const __m128i *) (data + 6*8)); + row7 = _mm_load_si128((const __m128i *) (data + 7*8)); + + // column pass + dct_pass(bias_0, 10); + + { + // 16bit 8x8 transpose pass 1 + dct_interleave16(row0, row4); + dct_interleave16(row1, row5); + dct_interleave16(row2, row6); + dct_interleave16(row3, row7); + + // transpose pass 2 + dct_interleave16(row0, row2); + dct_interleave16(row1, row3); + dct_interleave16(row4, row6); + dct_interleave16(row5, row7); + + // transpose pass 3 + dct_interleave16(row0, row1); + dct_interleave16(row2, row3); + dct_interleave16(row4, row5); + dct_interleave16(row6, row7); + } + + // row pass + dct_pass(bias_1, 17); + + { + // pack + __m128i p0 = _mm_packus_epi16(row0, row1); // a0a1a2a3...a7b0b1b2b3...b7 + __m128i p1 = _mm_packus_epi16(row2, row3); + __m128i p2 = _mm_packus_epi16(row4, row5); + __m128i p3 = _mm_packus_epi16(row6, row7); + + // 8bit 8x8 transpose pass 1 + dct_interleave8(p0, p2); // a0e0a1e1... + dct_interleave8(p1, p3); // c0g0c1g1... + + // transpose pass 2 + dct_interleave8(p0, p1); // a0c0e0g0... + dct_interleave8(p2, p3); // b0d0f0h0... + + // transpose pass 3 + dct_interleave8(p0, p2); // a0b0c0d0... + dct_interleave8(p1, p3); // a4b4c4d4... + + // store + _mm_storel_epi64((__m128i *) out, p0); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p0, 0x4e)); out += out_stride; + _mm_storel_epi64((__m128i *) out, p2); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p2, 0x4e)); out += out_stride; + _mm_storel_epi64((__m128i *) out, p1); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p1, 0x4e)); out += out_stride; + _mm_storel_epi64((__m128i *) out, p3); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p3, 0x4e)); + } + +#undef dct_const +#undef dct_rot +#undef dct_widen +#undef dct_wadd +#undef dct_wsub +#undef dct_bfly32o +#undef dct_interleave8 +#undef dct_interleave16 +#undef dct_pass +} + +#endif // STBI_SSE2 + +#ifdef STBI_NEON + +// NEON integer IDCT. should produce bit-identical +// results to the generic C version. +static void stbi__idct_simd(stbi_uc *out, int out_stride, short data[64]) +{ + int16x8_t row0, row1, row2, row3, row4, row5, row6, row7; + + int16x4_t rot0_0 = vdup_n_s16(stbi__f2f(0.5411961f)); + int16x4_t rot0_1 = vdup_n_s16(stbi__f2f(-1.847759065f)); + int16x4_t rot0_2 = vdup_n_s16(stbi__f2f( 0.765366865f)); + int16x4_t rot1_0 = vdup_n_s16(stbi__f2f( 1.175875602f)); + int16x4_t rot1_1 = vdup_n_s16(stbi__f2f(-0.899976223f)); + int16x4_t rot1_2 = vdup_n_s16(stbi__f2f(-2.562915447f)); + int16x4_t rot2_0 = vdup_n_s16(stbi__f2f(-1.961570560f)); + int16x4_t rot2_1 = vdup_n_s16(stbi__f2f(-0.390180644f)); + int16x4_t rot3_0 = vdup_n_s16(stbi__f2f( 0.298631336f)); + int16x4_t rot3_1 = vdup_n_s16(stbi__f2f( 2.053119869f)); + int16x4_t rot3_2 = vdup_n_s16(stbi__f2f( 3.072711026f)); + int16x4_t rot3_3 = vdup_n_s16(stbi__f2f( 1.501321110f)); + +#define dct_long_mul(out, inq, coeff) \ + int32x4_t out##_l = vmull_s16(vget_low_s16(inq), coeff); \ + int32x4_t out##_h = vmull_s16(vget_high_s16(inq), coeff) + +#define dct_long_mac(out, acc, inq, coeff) \ + int32x4_t out##_l = vmlal_s16(acc##_l, vget_low_s16(inq), coeff); \ + int32x4_t out##_h = vmlal_s16(acc##_h, vget_high_s16(inq), coeff) + +#define dct_widen(out, inq) \ + int32x4_t out##_l = vshll_n_s16(vget_low_s16(inq), 12); \ + int32x4_t out##_h = vshll_n_s16(vget_high_s16(inq), 12) + +// wide add +#define dct_wadd(out, a, b) \ + int32x4_t out##_l = vaddq_s32(a##_l, b##_l); \ + int32x4_t out##_h = vaddq_s32(a##_h, b##_h) + +// wide sub +#define dct_wsub(out, a, b) \ + int32x4_t out##_l = vsubq_s32(a##_l, b##_l); \ + int32x4_t out##_h = vsubq_s32(a##_h, b##_h) + +// butterfly a/b, then shift using "shiftop" by "s" and pack +#define dct_bfly32o(out0,out1, a,b,shiftop,s) \ + { \ + dct_wadd(sum, a, b); \ + dct_wsub(dif, a, b); \ + out0 = vcombine_s16(shiftop(sum_l, s), shiftop(sum_h, s)); \ + out1 = vcombine_s16(shiftop(dif_l, s), shiftop(dif_h, s)); \ + } + +#define dct_pass(shiftop, shift) \ + { \ + /* even part */ \ + int16x8_t sum26 = vaddq_s16(row2, row6); \ + dct_long_mul(p1e, sum26, rot0_0); \ + dct_long_mac(t2e, p1e, row6, rot0_1); \ + dct_long_mac(t3e, p1e, row2, rot0_2); \ + int16x8_t sum04 = vaddq_s16(row0, row4); \ + int16x8_t dif04 = vsubq_s16(row0, row4); \ + dct_widen(t0e, sum04); \ + dct_widen(t1e, dif04); \ + dct_wadd(x0, t0e, t3e); \ + dct_wsub(x3, t0e, t3e); \ + dct_wadd(x1, t1e, t2e); \ + dct_wsub(x2, t1e, t2e); \ + /* odd part */ \ + int16x8_t sum15 = vaddq_s16(row1, row5); \ + int16x8_t sum17 = vaddq_s16(row1, row7); \ + int16x8_t sum35 = vaddq_s16(row3, row5); \ + int16x8_t sum37 = vaddq_s16(row3, row7); \ + int16x8_t sumodd = vaddq_s16(sum17, sum35); \ + dct_long_mul(p5o, sumodd, rot1_0); \ + dct_long_mac(p1o, p5o, sum17, rot1_1); \ + dct_long_mac(p2o, p5o, sum35, rot1_2); \ + dct_long_mul(p3o, sum37, rot2_0); \ + dct_long_mul(p4o, sum15, rot2_1); \ + dct_wadd(sump13o, p1o, p3o); \ + dct_wadd(sump24o, p2o, p4o); \ + dct_wadd(sump23o, p2o, p3o); \ + dct_wadd(sump14o, p1o, p4o); \ + dct_long_mac(x4, sump13o, row7, rot3_0); \ + dct_long_mac(x5, sump24o, row5, rot3_1); \ + dct_long_mac(x6, sump23o, row3, rot3_2); \ + dct_long_mac(x7, sump14o, row1, rot3_3); \ + dct_bfly32o(row0,row7, x0,x7,shiftop,shift); \ + dct_bfly32o(row1,row6, x1,x6,shiftop,shift); \ + dct_bfly32o(row2,row5, x2,x5,shiftop,shift); \ + dct_bfly32o(row3,row4, x3,x4,shiftop,shift); \ + } + + // load + row0 = vld1q_s16(data + 0*8); + row1 = vld1q_s16(data + 1*8); + row2 = vld1q_s16(data + 2*8); + row3 = vld1q_s16(data + 3*8); + row4 = vld1q_s16(data + 4*8); + row5 = vld1q_s16(data + 5*8); + row6 = vld1q_s16(data + 6*8); + row7 = vld1q_s16(data + 7*8); + + // add DC bias + row0 = vaddq_s16(row0, vsetq_lane_s16(1024, vdupq_n_s16(0), 0)); + + // column pass + dct_pass(vrshrn_n_s32, 10); + + // 16bit 8x8 transpose + { +// these three map to a single VTRN.16, VTRN.32, and VSWP, respectively. +// whether compilers actually get this is another story, sadly. +#define dct_trn16(x, y) { int16x8x2_t t = vtrnq_s16(x, y); x = t.val[0]; y = t.val[1]; } +#define dct_trn32(x, y) { int32x4x2_t t = vtrnq_s32(vreinterpretq_s32_s16(x), vreinterpretq_s32_s16(y)); x = vreinterpretq_s16_s32(t.val[0]); y = vreinterpretq_s16_s32(t.val[1]); } +#define dct_trn64(x, y) { int16x8_t x0 = x; int16x8_t y0 = y; x = vcombine_s16(vget_low_s16(x0), vget_low_s16(y0)); y = vcombine_s16(vget_high_s16(x0), vget_high_s16(y0)); } + + // pass 1 + dct_trn16(row0, row1); // a0b0a2b2a4b4a6b6 + dct_trn16(row2, row3); + dct_trn16(row4, row5); + dct_trn16(row6, row7); + + // pass 2 + dct_trn32(row0, row2); // a0b0c0d0a4b4c4d4 + dct_trn32(row1, row3); + dct_trn32(row4, row6); + dct_trn32(row5, row7); + + // pass 3 + dct_trn64(row0, row4); // a0b0c0d0e0f0g0h0 + dct_trn64(row1, row5); + dct_trn64(row2, row6); + dct_trn64(row3, row7); + +#undef dct_trn16 +#undef dct_trn32 +#undef dct_trn64 + } + + // row pass + // vrshrn_n_s32 only supports shifts up to 16, we need + // 17. so do a non-rounding shift of 16 first then follow + // up with a rounding shift by 1. + dct_pass(vshrn_n_s32, 16); + + { + // pack and round + uint8x8_t p0 = vqrshrun_n_s16(row0, 1); + uint8x8_t p1 = vqrshrun_n_s16(row1, 1); + uint8x8_t p2 = vqrshrun_n_s16(row2, 1); + uint8x8_t p3 = vqrshrun_n_s16(row3, 1); + uint8x8_t p4 = vqrshrun_n_s16(row4, 1); + uint8x8_t p5 = vqrshrun_n_s16(row5, 1); + uint8x8_t p6 = vqrshrun_n_s16(row6, 1); + uint8x8_t p7 = vqrshrun_n_s16(row7, 1); + + // again, these can translate into one instruction, but often don't. +#define dct_trn8_8(x, y) { uint8x8x2_t t = vtrn_u8(x, y); x = t.val[0]; y = t.val[1]; } +#define dct_trn8_16(x, y) { uint16x4x2_t t = vtrn_u16(vreinterpret_u16_u8(x), vreinterpret_u16_u8(y)); x = vreinterpret_u8_u16(t.val[0]); y = vreinterpret_u8_u16(t.val[1]); } +#define dct_trn8_32(x, y) { uint32x2x2_t t = vtrn_u32(vreinterpret_u32_u8(x), vreinterpret_u32_u8(y)); x = vreinterpret_u8_u32(t.val[0]); y = vreinterpret_u8_u32(t.val[1]); } + + // sadly can't use interleaved stores here since we only write + // 8 bytes to each scan line! + + // 8x8 8-bit transpose pass 1 + dct_trn8_8(p0, p1); + dct_trn8_8(p2, p3); + dct_trn8_8(p4, p5); + dct_trn8_8(p6, p7); + + // pass 2 + dct_trn8_16(p0, p2); + dct_trn8_16(p1, p3); + dct_trn8_16(p4, p6); + dct_trn8_16(p5, p7); + + // pass 3 + dct_trn8_32(p0, p4); + dct_trn8_32(p1, p5); + dct_trn8_32(p2, p6); + dct_trn8_32(p3, p7); + + // store + vst1_u8(out, p0); out += out_stride; + vst1_u8(out, p1); out += out_stride; + vst1_u8(out, p2); out += out_stride; + vst1_u8(out, p3); out += out_stride; + vst1_u8(out, p4); out += out_stride; + vst1_u8(out, p5); out += out_stride; + vst1_u8(out, p6); out += out_stride; + vst1_u8(out, p7); + +#undef dct_trn8_8 +#undef dct_trn8_16 +#undef dct_trn8_32 + } + +#undef dct_long_mul +#undef dct_long_mac +#undef dct_widen +#undef dct_wadd +#undef dct_wsub +#undef dct_bfly32o +#undef dct_pass +} + +#endif // STBI_NEON + +#define STBI__MARKER_none 0xff +// if there's a pending marker from the entropy stream, return that +// otherwise, fetch from the stream and get a marker. if there's no +// marker, return 0xff, which is never a valid marker value +static stbi_uc stbi__get_marker(stbi__jpeg *j) +{ + stbi_uc x; + if (j->marker != STBI__MARKER_none) { x = j->marker; j->marker = STBI__MARKER_none; return x; } + x = stbi__get8(j->s); + if (x != 0xff) return STBI__MARKER_none; + while (x == 0xff) + x = stbi__get8(j->s); + return x; +} + +// in each scan, we'll have scan_n components, and the order +// of the components is specified by order[] +#define STBI__RESTART(x) ((x) >= 0xd0 && (x) <= 0xd7) + +// after a restart interval, stbi__jpeg_reset the entropy decoder and +// the dc prediction +static void stbi__jpeg_reset(stbi__jpeg *j) +{ + j->code_bits = 0; + j->code_buffer = 0; + j->nomore = 0; + j->img_comp[0].dc_pred = j->img_comp[1].dc_pred = j->img_comp[2].dc_pred = 0; + j->marker = STBI__MARKER_none; + j->todo = j->restart_interval ? j->restart_interval : 0x7fffffff; + j->eob_run = 0; + // no more than 1<<31 MCUs if no restart_interal? that's plenty safe, + // since we don't even allow 1<<30 pixels +} + +static int stbi__parse_entropy_coded_data(stbi__jpeg *z) +{ + stbi__jpeg_reset(z); + if (!z->progressive) { + if (z->scan_n == 1) { + int i,j; + STBI_SIMD_ALIGN(short, data[64]); + int n = z->order[0]; + // non-interleaved data, we just need to process one block at a time, + // in trivial scanline order + // number of blocks to do just depends on how many actual "pixels" this + // component has, independent of interleaved MCU blocking and such + int w = (z->img_comp[n].x+7) >> 3; + int h = (z->img_comp[n].y+7) >> 3; + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) { + int ha = z->img_comp[n].ha; + if (!stbi__jpeg_decode_block(z, data, z->huff_dc+z->img_comp[n].hd, z->huff_ac+ha, z->fast_ac[ha], n, z->dequant[z->img_comp[n].tq])) return 0; + z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*j*8+i*8, z->img_comp[n].w2, data); + // every data block is an MCU, so countdown the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + // if it's NOT a restart, then just bail, so we get corrupt data + // rather than no data + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + return 1; + } else { // interleaved + int i,j,k,x,y; + STBI_SIMD_ALIGN(short, data[64]); + for (j=0; j < z->img_mcu_y; ++j) { + for (i=0; i < z->img_mcu_x; ++i) { + // scan an interleaved mcu... process scan_n components in order + for (k=0; k < z->scan_n; ++k) { + int n = z->order[k]; + // scan out an mcu's worth of this component; that's just determined + // by the basic H and V specified for the component + for (y=0; y < z->img_comp[n].v; ++y) { + for (x=0; x < z->img_comp[n].h; ++x) { + int x2 = (i*z->img_comp[n].h + x)*8; + int y2 = (j*z->img_comp[n].v + y)*8; + int ha = z->img_comp[n].ha; + if (!stbi__jpeg_decode_block(z, data, z->huff_dc+z->img_comp[n].hd, z->huff_ac+ha, z->fast_ac[ha], n, z->dequant[z->img_comp[n].tq])) return 0; + z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*y2+x2, z->img_comp[n].w2, data); + } + } + } + // after all interleaved components, that's an interleaved MCU, + // so now count down the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + return 1; + } + } else { + if (z->scan_n == 1) { + int i,j; + int n = z->order[0]; + // non-interleaved data, we just need to process one block at a time, + // in trivial scanline order + // number of blocks to do just depends on how many actual "pixels" this + // component has, independent of interleaved MCU blocking and such + int w = (z->img_comp[n].x+7) >> 3; + int h = (z->img_comp[n].y+7) >> 3; + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) { + short *data = z->img_comp[n].coeff + 64 * (i + j * z->img_comp[n].coeff_w); + if (z->spec_start == 0) { + if (!stbi__jpeg_decode_block_prog_dc(z, data, &z->huff_dc[z->img_comp[n].hd], n)) + return 0; + } else { + int ha = z->img_comp[n].ha; + if (!stbi__jpeg_decode_block_prog_ac(z, data, &z->huff_ac[ha], z->fast_ac[ha])) + return 0; + } + // every data block is an MCU, so countdown the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + return 1; + } else { // interleaved + int i,j,k,x,y; + for (j=0; j < z->img_mcu_y; ++j) { + for (i=0; i < z->img_mcu_x; ++i) { + // scan an interleaved mcu... process scan_n components in order + for (k=0; k < z->scan_n; ++k) { + int n = z->order[k]; + // scan out an mcu's worth of this component; that's just determined + // by the basic H and V specified for the component + for (y=0; y < z->img_comp[n].v; ++y) { + for (x=0; x < z->img_comp[n].h; ++x) { + int x2 = (i*z->img_comp[n].h + x); + int y2 = (j*z->img_comp[n].v + y); + short *data = z->img_comp[n].coeff + 64 * (x2 + y2 * z->img_comp[n].coeff_w); + if (!stbi__jpeg_decode_block_prog_dc(z, data, &z->huff_dc[z->img_comp[n].hd], n)) + return 0; + } + } + } + // after all interleaved components, that's an interleaved MCU, + // so now count down the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + return 1; + } + } +} + +static void stbi__jpeg_dequantize(short *data, stbi_uc *dequant) +{ + int i; + for (i=0; i < 64; ++i) + data[i] *= dequant[i]; +} + +static void stbi__jpeg_finish(stbi__jpeg *z) +{ + if (z->progressive) { + // dequantize and idct the data + int i,j,n; + for (n=0; n < z->s->img_n; ++n) { + int w = (z->img_comp[n].x+7) >> 3; + int h = (z->img_comp[n].y+7) >> 3; + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) { + short *data = z->img_comp[n].coeff + 64 * (i + j * z->img_comp[n].coeff_w); + stbi__jpeg_dequantize(data, z->dequant[z->img_comp[n].tq]); + z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*j*8+i*8, z->img_comp[n].w2, data); + } + } + } + } +} + +static int stbi__process_marker(stbi__jpeg *z, int m) +{ + int L; + switch (m) { + case STBI__MARKER_none: // no marker found + return stbi__err("expected marker","Corrupt JPEG"); + + case 0xDD: // DRI - specify restart interval + if (stbi__get16be(z->s) != 4) return stbi__err("bad DRI len","Corrupt JPEG"); + z->restart_interval = stbi__get16be(z->s); + return 1; + + case 0xDB: // DQT - define quantization table + L = stbi__get16be(z->s)-2; + while (L > 0) { + int q = stbi__get8(z->s); + int p = q >> 4; + int t = q & 15,i; + if (p != 0) return stbi__err("bad DQT type","Corrupt JPEG"); + if (t > 3) return stbi__err("bad DQT table","Corrupt JPEG"); + for (i=0; i < 64; ++i) + z->dequant[t][stbi__jpeg_dezigzag[i]] = stbi__get8(z->s); + L -= 65; + } + return L==0; + + case 0xC4: // DHT - define huffman table + L = stbi__get16be(z->s)-2; + while (L > 0) { + stbi_uc *v; + int sizes[16],i,n=0; + int q = stbi__get8(z->s); + int tc = q >> 4; + int th = q & 15; + if (tc > 1 || th > 3) return stbi__err("bad DHT header","Corrupt JPEG"); + for (i=0; i < 16; ++i) { + sizes[i] = stbi__get8(z->s); + n += sizes[i]; + } + L -= 17; + if (tc == 0) { + if (!stbi__build_huffman(z->huff_dc+th, sizes)) return 0; + v = z->huff_dc[th].values; + } else { + if (!stbi__build_huffman(z->huff_ac+th, sizes)) return 0; + v = z->huff_ac[th].values; + } + for (i=0; i < n; ++i) + v[i] = stbi__get8(z->s); + if (tc != 0) + stbi__build_fast_ac(z->fast_ac[th], z->huff_ac + th); + L -= n; + } + return L==0; + } + // check for comment block or APP blocks + if ((m >= 0xE0 && m <= 0xEF) || m == 0xFE) { + stbi__skip(z->s, stbi__get16be(z->s)-2); + return 1; + } + return 0; +} + +// after we see SOS +static int stbi__process_scan_header(stbi__jpeg *z) +{ + int i; + int Ls = stbi__get16be(z->s); + z->scan_n = stbi__get8(z->s); + if (z->scan_n < 1 || z->scan_n > 4 || z->scan_n > (int) z->s->img_n) return stbi__err("bad SOS component count","Corrupt JPEG"); + if (Ls != 6+2*z->scan_n) return stbi__err("bad SOS len","Corrupt JPEG"); + for (i=0; i < z->scan_n; ++i) { + int id = stbi__get8(z->s), which; + int q = stbi__get8(z->s); + for (which = 0; which < z->s->img_n; ++which) + if (z->img_comp[which].id == id) + break; + if (which == z->s->img_n) return 0; // no match + z->img_comp[which].hd = q >> 4; if (z->img_comp[which].hd > 3) return stbi__err("bad DC huff","Corrupt JPEG"); + z->img_comp[which].ha = q & 15; if (z->img_comp[which].ha > 3) return stbi__err("bad AC huff","Corrupt JPEG"); + z->order[i] = which; + } + + { + int aa; + z->spec_start = stbi__get8(z->s); + z->spec_end = stbi__get8(z->s); // should be 63, but might be 0 + aa = stbi__get8(z->s); + z->succ_high = (aa >> 4); + z->succ_low = (aa & 15); + if (z->progressive) { + if (z->spec_start > 63 || z->spec_end > 63 || z->spec_start > z->spec_end || z->succ_high > 13 || z->succ_low > 13) + return stbi__err("bad SOS", "Corrupt JPEG"); + } else { + if (z->spec_start != 0) return stbi__err("bad SOS","Corrupt JPEG"); + if (z->succ_high != 0 || z->succ_low != 0) return stbi__err("bad SOS","Corrupt JPEG"); + z->spec_end = 63; + } + } + + return 1; +} + +static int stbi__process_frame_header(stbi__jpeg *z, int scan) +{ + stbi__context *s = z->s; + int Lf,p,i,q, h_max=1,v_max=1,c; + Lf = stbi__get16be(s); if (Lf < 11) return stbi__err("bad SOF len","Corrupt JPEG"); // JPEG + p = stbi__get8(s); if (p != 8) return stbi__err("only 8-bit","JPEG format not supported: 8-bit only"); // JPEG baseline + s->img_y = stbi__get16be(s); if (s->img_y == 0) return stbi__err("no header height", "JPEG format not supported: delayed height"); // Legal, but we don't handle it--but neither does IJG + s->img_x = stbi__get16be(s); if (s->img_x == 0) return stbi__err("0 width","Corrupt JPEG"); // JPEG requires + c = stbi__get8(s); + if (c != 3 && c != 1) return stbi__err("bad component count","Corrupt JPEG"); // JFIF requires + s->img_n = c; + for (i=0; i < c; ++i) { + z->img_comp[i].data = NULL; + z->img_comp[i].linebuf = NULL; + } + + if (Lf != 8+3*s->img_n) return stbi__err("bad SOF len","Corrupt JPEG"); + + z->rgb = 0; + for (i=0; i < s->img_n; ++i) { + static unsigned char rgb[3] = { 'R', 'G', 'B' }; + z->img_comp[i].id = stbi__get8(s); + if (z->img_comp[i].id != i+1) // JFIF requires + if (z->img_comp[i].id != i) { // some version of jpegtran outputs non-JFIF-compliant files! + // somethings output this (see http://fileformats.archiveteam.org/wiki/JPEG#Color_format) + if (z->img_comp[i].id != rgb[i]) + return stbi__err("bad component ID","Corrupt JPEG"); + ++z->rgb; + } + q = stbi__get8(s); + z->img_comp[i].h = (q >> 4); if (!z->img_comp[i].h || z->img_comp[i].h > 4) return stbi__err("bad H","Corrupt JPEG"); + z->img_comp[i].v = q & 15; if (!z->img_comp[i].v || z->img_comp[i].v > 4) return stbi__err("bad V","Corrupt JPEG"); + z->img_comp[i].tq = stbi__get8(s); if (z->img_comp[i].tq > 3) return stbi__err("bad TQ","Corrupt JPEG"); + } + + if (scan != STBI__SCAN_load) return 1; + + if ((1 << 30) / s->img_x / s->img_n < s->img_y) return stbi__err("too large", "Image too large to decode"); + + for (i=0; i < s->img_n; ++i) { + if (z->img_comp[i].h > h_max) h_max = z->img_comp[i].h; + if (z->img_comp[i].v > v_max) v_max = z->img_comp[i].v; + } + + // compute interleaved mcu info + z->img_h_max = h_max; + z->img_v_max = v_max; + z->img_mcu_w = h_max * 8; + z->img_mcu_h = v_max * 8; + z->img_mcu_x = (s->img_x + z->img_mcu_w-1) / z->img_mcu_w; + z->img_mcu_y = (s->img_y + z->img_mcu_h-1) / z->img_mcu_h; + + for (i=0; i < s->img_n; ++i) { + // number of effective pixels (e.g. for non-interleaved MCU) + z->img_comp[i].x = (s->img_x * z->img_comp[i].h + h_max-1) / h_max; + z->img_comp[i].y = (s->img_y * z->img_comp[i].v + v_max-1) / v_max; + // to simplify generation, we'll allocate enough memory to decode + // the bogus oversized data from using interleaved MCUs and their + // big blocks (e.g. a 16x16 iMCU on an image of width 33); we won't + // discard the extra data until colorspace conversion + z->img_comp[i].w2 = z->img_mcu_x * z->img_comp[i].h * 8; + z->img_comp[i].h2 = z->img_mcu_y * z->img_comp[i].v * 8; + z->img_comp[i].raw_data = stbi__malloc(z->img_comp[i].w2 * z->img_comp[i].h2+15); + + if (z->img_comp[i].raw_data == NULL) { + for(--i; i >= 0; --i) { + STBI_FREE(z->img_comp[i].raw_data); + z->img_comp[i].raw_data = NULL; + } + return stbi__err("outofmem", "Out of memory"); + } + // align blocks for idct using mmx/sse + z->img_comp[i].data = (stbi_uc*) (((size_t) z->img_comp[i].raw_data + 15) & ~15); + z->img_comp[i].linebuf = NULL; + if (z->progressive) { + z->img_comp[i].coeff_w = (z->img_comp[i].w2 + 7) >> 3; + z->img_comp[i].coeff_h = (z->img_comp[i].h2 + 7) >> 3; + z->img_comp[i].raw_coeff = STBI_MALLOC(z->img_comp[i].coeff_w * z->img_comp[i].coeff_h * 64 * sizeof(short) + 15); + z->img_comp[i].coeff = (short*) (((size_t) z->img_comp[i].raw_coeff + 15) & ~15); + } else { + z->img_comp[i].coeff = 0; + z->img_comp[i].raw_coeff = 0; + } + } + + return 1; +} + +// use comparisons since in some cases we handle more than one case (e.g. SOF) +#define stbi__DNL(x) ((x) == 0xdc) +#define stbi__SOI(x) ((x) == 0xd8) +#define stbi__EOI(x) ((x) == 0xd9) +#define stbi__SOF(x) ((x) == 0xc0 || (x) == 0xc1 || (x) == 0xc2) +#define stbi__SOS(x) ((x) == 0xda) + +#define stbi__SOF_progressive(x) ((x) == 0xc2) + +static int stbi__decode_jpeg_header(stbi__jpeg *z, int scan) +{ + int m; + z->marker = STBI__MARKER_none; // initialize cached marker to empty + m = stbi__get_marker(z); + if (!stbi__SOI(m)) return stbi__err("no SOI","Corrupt JPEG"); + if (scan == STBI__SCAN_type) return 1; + m = stbi__get_marker(z); + while (!stbi__SOF(m)) { + if (!stbi__process_marker(z,m)) return 0; + m = stbi__get_marker(z); + while (m == STBI__MARKER_none) { + // some files have extra padding after their blocks, so ok, we'll scan + if (stbi__at_eof(z->s)) return stbi__err("no SOF", "Corrupt JPEG"); + m = stbi__get_marker(z); + } + } + z->progressive = stbi__SOF_progressive(m); + if (!stbi__process_frame_header(z, scan)) return 0; + return 1; +} + +// decode image to YCbCr format +static int stbi__decode_jpeg_image(stbi__jpeg *j) +{ + int m; + for (m = 0; m < 4; m++) { + j->img_comp[m].raw_data = NULL; + j->img_comp[m].raw_coeff = NULL; + } + j->restart_interval = 0; + if (!stbi__decode_jpeg_header(j, STBI__SCAN_load)) return 0; + m = stbi__get_marker(j); + while (!stbi__EOI(m)) { + if (stbi__SOS(m)) { + if (!stbi__process_scan_header(j)) return 0; + if (!stbi__parse_entropy_coded_data(j)) return 0; + if (j->marker == STBI__MARKER_none ) { + // handle 0s at the end of image data from IP Kamera 9060 + while (!stbi__at_eof(j->s)) { + int x = stbi__get8(j->s); + if (x == 255) { + j->marker = stbi__get8(j->s); + break; + } else if (x != 0) { + return stbi__err("junk before marker", "Corrupt JPEG"); + } + } + // if we reach eof without hitting a marker, stbi__get_marker() below will fail and we'll eventually return 0 + } + } else { + if (!stbi__process_marker(j, m)) return 0; + } + m = stbi__get_marker(j); + } + if (j->progressive) + stbi__jpeg_finish(j); + return 1; +} + +// static jfif-centered resampling (across block boundaries) + +typedef stbi_uc *(*resample_row_func)(stbi_uc *out, stbi_uc *in0, stbi_uc *in1, + int w, int hs); + +#define stbi__div4(x) ((stbi_uc) ((x) >> 2)) + +static stbi_uc *resample_row_1(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + STBI_NOTUSED(out); + STBI_NOTUSED(in_far); + STBI_NOTUSED(w); + STBI_NOTUSED(hs); + return in_near; +} + +static stbi_uc* stbi__resample_row_v_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate two samples vertically for every one in input + int i; + STBI_NOTUSED(hs); + for (i=0; i < w; ++i) + out[i] = stbi__div4(3*in_near[i] + in_far[i] + 2); + return out; +} + +static stbi_uc* stbi__resample_row_h_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate two samples horizontally for every one in input + int i; + stbi_uc *input = in_near; + + if (w == 1) { + // if only one sample, can't do any interpolation + out[0] = out[1] = input[0]; + return out; + } + + out[0] = input[0]; + out[1] = stbi__div4(input[0]*3 + input[1] + 2); + for (i=1; i < w-1; ++i) { + int n = 3*input[i]+2; + out[i*2+0] = stbi__div4(n+input[i-1]); + out[i*2+1] = stbi__div4(n+input[i+1]); + } + out[i*2+0] = stbi__div4(input[w-2]*3 + input[w-1] + 2); + out[i*2+1] = input[w-1]; + + STBI_NOTUSED(in_far); + STBI_NOTUSED(hs); + + return out; +} + +#define stbi__div16(x) ((stbi_uc) ((x) >> 4)) + +static stbi_uc *stbi__resample_row_hv_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate 2x2 samples for every one in input + int i,t0,t1; + if (w == 1) { + out[0] = out[1] = stbi__div4(3*in_near[0] + in_far[0] + 2); + return out; + } + + t1 = 3*in_near[0] + in_far[0]; + out[0] = stbi__div4(t1+2); + for (i=1; i < w; ++i) { + t0 = t1; + t1 = 3*in_near[i]+in_far[i]; + out[i*2-1] = stbi__div16(3*t0 + t1 + 8); + out[i*2 ] = stbi__div16(3*t1 + t0 + 8); + } + out[w*2-1] = stbi__div4(t1+2); + + STBI_NOTUSED(hs); + + return out; +} + +#if defined(STBI_SSE2) || defined(STBI_NEON) +static stbi_uc *stbi__resample_row_hv_2_simd(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate 2x2 samples for every one in input + int i=0,t0,t1; + + if (w == 1) { + out[0] = out[1] = stbi__div4(3*in_near[0] + in_far[0] + 2); + return out; + } + + t1 = 3*in_near[0] + in_far[0]; + // process groups of 8 pixels for as long as we can. + // note we can't handle the last pixel in a row in this loop + // because we need to handle the filter boundary conditions. + for (; i < ((w-1) & ~7); i += 8) { +#if defined(STBI_SSE2) + // load and perform the vertical filtering pass + // this uses 3*x + y = 4*x + (y - x) + __m128i zero = _mm_setzero_si128(); + __m128i farb = _mm_loadl_epi64((__m128i *) (in_far + i)); + __m128i nearb = _mm_loadl_epi64((__m128i *) (in_near + i)); + __m128i farw = _mm_unpacklo_epi8(farb, zero); + __m128i nearw = _mm_unpacklo_epi8(nearb, zero); + __m128i diff = _mm_sub_epi16(farw, nearw); + __m128i nears = _mm_slli_epi16(nearw, 2); + __m128i curr = _mm_add_epi16(nears, diff); // current row + + // horizontal filter works the same based on shifted vers of current + // row. "prev" is current row shifted right by 1 pixel; we need to + // insert the previous pixel value (from t1). + // "next" is current row shifted left by 1 pixel, with first pixel + // of next block of 8 pixels added in. + __m128i prv0 = _mm_slli_si128(curr, 2); + __m128i nxt0 = _mm_srli_si128(curr, 2); + __m128i prev = _mm_insert_epi16(prv0, t1, 0); + __m128i next = _mm_insert_epi16(nxt0, 3*in_near[i+8] + in_far[i+8], 7); + + // horizontal filter, polyphase implementation since it's convenient: + // even pixels = 3*cur + prev = cur*4 + (prev - cur) + // odd pixels = 3*cur + next = cur*4 + (next - cur) + // note the shared term. + __m128i bias = _mm_set1_epi16(8); + __m128i curs = _mm_slli_epi16(curr, 2); + __m128i prvd = _mm_sub_epi16(prev, curr); + __m128i nxtd = _mm_sub_epi16(next, curr); + __m128i curb = _mm_add_epi16(curs, bias); + __m128i even = _mm_add_epi16(prvd, curb); + __m128i odd = _mm_add_epi16(nxtd, curb); + + // interleave even and odd pixels, then undo scaling. + __m128i int0 = _mm_unpacklo_epi16(even, odd); + __m128i int1 = _mm_unpackhi_epi16(even, odd); + __m128i de0 = _mm_srli_epi16(int0, 4); + __m128i de1 = _mm_srli_epi16(int1, 4); + + // pack and write output + __m128i outv = _mm_packus_epi16(de0, de1); + _mm_storeu_si128((__m128i *) (out + i*2), outv); +#elif defined(STBI_NEON) + // load and perform the vertical filtering pass + // this uses 3*x + y = 4*x + (y - x) + uint8x8_t farb = vld1_u8(in_far + i); + uint8x8_t nearb = vld1_u8(in_near + i); + int16x8_t diff = vreinterpretq_s16_u16(vsubl_u8(farb, nearb)); + int16x8_t nears = vreinterpretq_s16_u16(vshll_n_u8(nearb, 2)); + int16x8_t curr = vaddq_s16(nears, diff); // current row + + // horizontal filter works the same based on shifted vers of current + // row. "prev" is current row shifted right by 1 pixel; we need to + // insert the previous pixel value (from t1). + // "next" is current row shifted left by 1 pixel, with first pixel + // of next block of 8 pixels added in. + int16x8_t prv0 = vextq_s16(curr, curr, 7); + int16x8_t nxt0 = vextq_s16(curr, curr, 1); + int16x8_t prev = vsetq_lane_s16(t1, prv0, 0); + int16x8_t next = vsetq_lane_s16(3*in_near[i+8] + in_far[i+8], nxt0, 7); + + // horizontal filter, polyphase implementation since it's convenient: + // even pixels = 3*cur + prev = cur*4 + (prev - cur) + // odd pixels = 3*cur + next = cur*4 + (next - cur) + // note the shared term. + int16x8_t curs = vshlq_n_s16(curr, 2); + int16x8_t prvd = vsubq_s16(prev, curr); + int16x8_t nxtd = vsubq_s16(next, curr); + int16x8_t even = vaddq_s16(curs, prvd); + int16x8_t odd = vaddq_s16(curs, nxtd); + + // undo scaling and round, then store with even/odd phases interleaved + uint8x8x2_t o; + o.val[0] = vqrshrun_n_s16(even, 4); + o.val[1] = vqrshrun_n_s16(odd, 4); + vst2_u8(out + i*2, o); +#endif + + // "previous" value for next iter + t1 = 3*in_near[i+7] + in_far[i+7]; + } + + t0 = t1; + t1 = 3*in_near[i] + in_far[i]; + out[i*2] = stbi__div16(3*t1 + t0 + 8); + + for (++i; i < w; ++i) { + t0 = t1; + t1 = 3*in_near[i]+in_far[i]; + out[i*2-1] = stbi__div16(3*t0 + t1 + 8); + out[i*2 ] = stbi__div16(3*t1 + t0 + 8); + } + out[w*2-1] = stbi__div4(t1+2); + + STBI_NOTUSED(hs); + + return out; +} +#endif + +static stbi_uc *stbi__resample_row_generic(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // resample with nearest-neighbor + int i,j; + STBI_NOTUSED(in_far); + for (i=0; i < w; ++i) + for (j=0; j < hs; ++j) + out[i*hs+j] = in_near[i]; + return out; +} + +#ifdef STBI_JPEG_OLD +// this is the same YCbCr-to-RGB calculation that stb_image has used +// historically before the algorithm changes in 1.49 +#define float2fixed(x) ((int) ((x) * 65536 + 0.5)) +static void stbi__YCbCr_to_RGB_row(stbi_uc *out, const stbi_uc *y, const stbi_uc *pcb, const stbi_uc *pcr, int count, int step) +{ + int i; + for (i=0; i < count; ++i) { + int y_fixed = (y[i] << 16) + 32768; // rounding + int r,g,b; + int cr = pcr[i] - 128; + int cb = pcb[i] - 128; + r = y_fixed + cr*float2fixed(1.40200f); + g = y_fixed - cr*float2fixed(0.71414f) - cb*float2fixed(0.34414f); + b = y_fixed + cb*float2fixed(1.77200f); + r >>= 16; + g >>= 16; + b >>= 16; + if ((unsigned) r > 255) { if (r < 0) r = 0; else r = 255; } + if ((unsigned) g > 255) { if (g < 0) g = 0; else g = 255; } + if ((unsigned) b > 255) { if (b < 0) b = 0; else b = 255; } + out[0] = (stbi_uc)r; + out[1] = (stbi_uc)g; + out[2] = (stbi_uc)b; + out[3] = 255; + out += step; + } +} +#else +// this is a reduced-precision calculation of YCbCr-to-RGB introduced +// to make sure the code produces the same results in both SIMD and scalar +#define float2fixed(x) (((int) ((x) * 4096.0f + 0.5f)) << 8) +static void stbi__YCbCr_to_RGB_row(stbi_uc *out, const stbi_uc *y, const stbi_uc *pcb, const stbi_uc *pcr, int count, int step) +{ + int i; + for (i=0; i < count; ++i) { + int y_fixed = (y[i] << 20) + (1<<19); // rounding + int r,g,b; + int cr = pcr[i] - 128; + int cb = pcb[i] - 128; + r = y_fixed + cr* float2fixed(1.40200f); + g = y_fixed + (cr*-float2fixed(0.71414f)) + ((cb*-float2fixed(0.34414f)) & 0xffff0000); + b = y_fixed + cb* float2fixed(1.77200f); + r >>= 20; + g >>= 20; + b >>= 20; + if ((unsigned) r > 255) { if (r < 0) r = 0; else r = 255; } + if ((unsigned) g > 255) { if (g < 0) g = 0; else g = 255; } + if ((unsigned) b > 255) { if (b < 0) b = 0; else b = 255; } + out[0] = (stbi_uc)r; + out[1] = (stbi_uc)g; + out[2] = (stbi_uc)b; + out[3] = 255; + out += step; + } +} +#endif + +#if defined(STBI_SSE2) || defined(STBI_NEON) +static void stbi__YCbCr_to_RGB_simd(stbi_uc *out, stbi_uc const *y, stbi_uc const *pcb, stbi_uc const *pcr, int count, int step) +{ + int i = 0; + +#ifdef STBI_SSE2 + // step == 3 is pretty ugly on the final interleave, and i'm not convinced + // it's useful in practice (you wouldn't use it for textures, for example). + // so just accelerate step == 4 case. + if (step == 4) { + // this is a fairly straightforward implementation and not super-optimized. + __m128i signflip = _mm_set1_epi8(-0x80); + __m128i cr_const0 = _mm_set1_epi16( (short) ( 1.40200f*4096.0f+0.5f)); + __m128i cr_const1 = _mm_set1_epi16( - (short) ( 0.71414f*4096.0f+0.5f)); + __m128i cb_const0 = _mm_set1_epi16( - (short) ( 0.34414f*4096.0f+0.5f)); + __m128i cb_const1 = _mm_set1_epi16( (short) ( 1.77200f*4096.0f+0.5f)); + __m128i y_bias = _mm_set1_epi8((char) (unsigned char) 128); + __m128i xw = _mm_set1_epi16(255); // alpha channel + + for (; i+7 < count; i += 8) { + // load + __m128i y_bytes = _mm_loadl_epi64((__m128i *) (y+i)); + __m128i cr_bytes = _mm_loadl_epi64((__m128i *) (pcr+i)); + __m128i cb_bytes = _mm_loadl_epi64((__m128i *) (pcb+i)); + __m128i cr_biased = _mm_xor_si128(cr_bytes, signflip); // -128 + __m128i cb_biased = _mm_xor_si128(cb_bytes, signflip); // -128 + + // unpack to short (and left-shift cr, cb by 8) + __m128i yw = _mm_unpacklo_epi8(y_bias, y_bytes); + __m128i crw = _mm_unpacklo_epi8(_mm_setzero_si128(), cr_biased); + __m128i cbw = _mm_unpacklo_epi8(_mm_setzero_si128(), cb_biased); + + // color transform + __m128i yws = _mm_srli_epi16(yw, 4); + __m128i cr0 = _mm_mulhi_epi16(cr_const0, crw); + __m128i cb0 = _mm_mulhi_epi16(cb_const0, cbw); + __m128i cb1 = _mm_mulhi_epi16(cbw, cb_const1); + __m128i cr1 = _mm_mulhi_epi16(crw, cr_const1); + __m128i rws = _mm_add_epi16(cr0, yws); + __m128i gwt = _mm_add_epi16(cb0, yws); + __m128i bws = _mm_add_epi16(yws, cb1); + __m128i gws = _mm_add_epi16(gwt, cr1); + + // descale + __m128i rw = _mm_srai_epi16(rws, 4); + __m128i bw = _mm_srai_epi16(bws, 4); + __m128i gw = _mm_srai_epi16(gws, 4); + + // back to byte, set up for transpose + __m128i brb = _mm_packus_epi16(rw, bw); + __m128i gxb = _mm_packus_epi16(gw, xw); + + // transpose to interleave channels + __m128i t0 = _mm_unpacklo_epi8(brb, gxb); + __m128i t1 = _mm_unpackhi_epi8(brb, gxb); + __m128i o0 = _mm_unpacklo_epi16(t0, t1); + __m128i o1 = _mm_unpackhi_epi16(t0, t1); + + // store + _mm_storeu_si128((__m128i *) (out + 0), o0); + _mm_storeu_si128((__m128i *) (out + 16), o1); + out += 32; + } + } +#endif + +#ifdef STBI_NEON + // in this version, step=3 support would be easy to add. but is there demand? + if (step == 4) { + // this is a fairly straightforward implementation and not super-optimized. + uint8x8_t signflip = vdup_n_u8(0x80); + int16x8_t cr_const0 = vdupq_n_s16( (short) ( 1.40200f*4096.0f+0.5f)); + int16x8_t cr_const1 = vdupq_n_s16( - (short) ( 0.71414f*4096.0f+0.5f)); + int16x8_t cb_const0 = vdupq_n_s16( - (short) ( 0.34414f*4096.0f+0.5f)); + int16x8_t cb_const1 = vdupq_n_s16( (short) ( 1.77200f*4096.0f+0.5f)); + + for (; i+7 < count; i += 8) { + // load + uint8x8_t y_bytes = vld1_u8(y + i); + uint8x8_t cr_bytes = vld1_u8(pcr + i); + uint8x8_t cb_bytes = vld1_u8(pcb + i); + int8x8_t cr_biased = vreinterpret_s8_u8(vsub_u8(cr_bytes, signflip)); + int8x8_t cb_biased = vreinterpret_s8_u8(vsub_u8(cb_bytes, signflip)); + + // expand to s16 + int16x8_t yws = vreinterpretq_s16_u16(vshll_n_u8(y_bytes, 4)); + int16x8_t crw = vshll_n_s8(cr_biased, 7); + int16x8_t cbw = vshll_n_s8(cb_biased, 7); + + // color transform + int16x8_t cr0 = vqdmulhq_s16(crw, cr_const0); + int16x8_t cb0 = vqdmulhq_s16(cbw, cb_const0); + int16x8_t cr1 = vqdmulhq_s16(crw, cr_const1); + int16x8_t cb1 = vqdmulhq_s16(cbw, cb_const1); + int16x8_t rws = vaddq_s16(yws, cr0); + int16x8_t gws = vaddq_s16(vaddq_s16(yws, cb0), cr1); + int16x8_t bws = vaddq_s16(yws, cb1); + + // undo scaling, round, convert to byte + uint8x8x4_t o; + o.val[0] = vqrshrun_n_s16(rws, 4); + o.val[1] = vqrshrun_n_s16(gws, 4); + o.val[2] = vqrshrun_n_s16(bws, 4); + o.val[3] = vdup_n_u8(255); + + // store, interleaving r/g/b/a + vst4_u8(out, o); + out += 8*4; + } + } +#endif + + for (; i < count; ++i) { + int y_fixed = (y[i] << 20) + (1<<19); // rounding + int r,g,b; + int cr = pcr[i] - 128; + int cb = pcb[i] - 128; + r = y_fixed + cr* float2fixed(1.40200f); + g = y_fixed + cr*-float2fixed(0.71414f) + ((cb*-float2fixed(0.34414f)) & 0xffff0000); + b = y_fixed + cb* float2fixed(1.77200f); + r >>= 20; + g >>= 20; + b >>= 20; + if ((unsigned) r > 255) { if (r < 0) r = 0; else r = 255; } + if ((unsigned) g > 255) { if (g < 0) g = 0; else g = 255; } + if ((unsigned) b > 255) { if (b < 0) b = 0; else b = 255; } + out[0] = (stbi_uc)r; + out[1] = (stbi_uc)g; + out[2] = (stbi_uc)b; + out[3] = 255; + out += step; + } +} +#endif + +// set up the kernels +static void stbi__setup_jpeg(stbi__jpeg *j) +{ + j->idct_block_kernel = stbi__idct_block; + j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_row; + j->resample_row_hv_2_kernel = stbi__resample_row_hv_2; + +#ifdef STBI_SSE2 + if (stbi__sse2_available()) { + j->idct_block_kernel = stbi__idct_simd; + #ifndef STBI_JPEG_OLD + j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_simd; + #endif + j->resample_row_hv_2_kernel = stbi__resample_row_hv_2_simd; + } +#endif + +#ifdef STBI_NEON + j->idct_block_kernel = stbi__idct_simd; + #ifndef STBI_JPEG_OLD + j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_simd; + #endif + j->resample_row_hv_2_kernel = stbi__resample_row_hv_2_simd; +#endif +} + +// clean up the temporary component buffers +static void stbi__cleanup_jpeg(stbi__jpeg *j) +{ + int i; + for (i=0; i < j->s->img_n; ++i) { + if (j->img_comp[i].raw_data) { + STBI_FREE(j->img_comp[i].raw_data); + j->img_comp[i].raw_data = NULL; + j->img_comp[i].data = NULL; + } + if (j->img_comp[i].raw_coeff) { + STBI_FREE(j->img_comp[i].raw_coeff); + j->img_comp[i].raw_coeff = 0; + j->img_comp[i].coeff = 0; + } + if (j->img_comp[i].linebuf) { + STBI_FREE(j->img_comp[i].linebuf); + j->img_comp[i].linebuf = NULL; + } + } +} + +typedef struct +{ + resample_row_func resample; + stbi_uc *line0,*line1; + int hs,vs; // expansion factor in each axis + int w_lores; // horizontal pixels pre-expansion + int ystep; // how far through vertical expansion we are + int ypos; // which pre-expansion row we're on +} stbi__resample; + +static stbi_uc *load_jpeg_image(stbi__jpeg *z, int *out_x, int *out_y, int *comp, int req_comp) +{ + int n, decode_n; + z->s->img_n = 0; // make stbi__cleanup_jpeg safe + + // validate req_comp + if (req_comp < 0 || req_comp > 4) return stbi__errpuc("bad req_comp", "Internal error"); + + // load a jpeg image from whichever source, but leave in YCbCr format + if (!stbi__decode_jpeg_image(z)) { stbi__cleanup_jpeg(z); return NULL; } + + // determine actual number of components to generate + n = req_comp ? req_comp : z->s->img_n; + + if (z->s->img_n == 3 && n < 3) + decode_n = 1; + else + decode_n = z->s->img_n; + + // resample and color-convert + { + int k; + unsigned int i,j; + stbi_uc *output; + stbi_uc *coutput[4]; + + stbi__resample res_comp[4]; + + for (k=0; k < decode_n; ++k) { + stbi__resample *r = &res_comp[k]; + + // allocate line buffer big enough for upsampling off the edges + // with upsample factor of 4 + z->img_comp[k].linebuf = (stbi_uc *) stbi__malloc(z->s->img_x + 3); + if (!z->img_comp[k].linebuf) { stbi__cleanup_jpeg(z); return stbi__errpuc("outofmem", "Out of memory"); } + + r->hs = z->img_h_max / z->img_comp[k].h; + r->vs = z->img_v_max / z->img_comp[k].v; + r->ystep = r->vs >> 1; + r->w_lores = (z->s->img_x + r->hs-1) / r->hs; + r->ypos = 0; + r->line0 = r->line1 = z->img_comp[k].data; + + if (r->hs == 1 && r->vs == 1) r->resample = resample_row_1; + else if (r->hs == 1 && r->vs == 2) r->resample = stbi__resample_row_v_2; + else if (r->hs == 2 && r->vs == 1) r->resample = stbi__resample_row_h_2; + else if (r->hs == 2 && r->vs == 2) r->resample = z->resample_row_hv_2_kernel; + else r->resample = stbi__resample_row_generic; + } + + // can't error after this so, this is safe + output = (stbi_uc *) stbi__malloc(n * z->s->img_x * z->s->img_y + 1); + if (!output) { stbi__cleanup_jpeg(z); return stbi__errpuc("outofmem", "Out of memory"); } + + // now go ahead and resample + for (j=0; j < z->s->img_y; ++j) { + stbi_uc *out = output + n * z->s->img_x * j; + for (k=0; k < decode_n; ++k) { + stbi__resample *r = &res_comp[k]; + int y_bot = r->ystep >= (r->vs >> 1); + coutput[k] = r->resample(z->img_comp[k].linebuf, + y_bot ? r->line1 : r->line0, + y_bot ? r->line0 : r->line1, + r->w_lores, r->hs); + if (++r->ystep >= r->vs) { + r->ystep = 0; + r->line0 = r->line1; + if (++r->ypos < z->img_comp[k].y) + r->line1 += z->img_comp[k].w2; + } + } + if (n >= 3) { + stbi_uc *y = coutput[0]; + if (z->s->img_n == 3) { + if (z->rgb == 3) { + for (i=0; i < z->s->img_x; ++i) { + out[0] = y[i]; + out[1] = coutput[1][i]; + out[2] = coutput[2][i]; + out[3] = 255; + out += n; + } + } else { + z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n); + } + } else + for (i=0; i < z->s->img_x; ++i) { + out[0] = out[1] = out[2] = y[i]; + out[3] = 255; // not used if n==3 + out += n; + } + } else { + stbi_uc *y = coutput[0]; + if (n == 1) + for (i=0; i < z->s->img_x; ++i) out[i] = y[i]; + else + for (i=0; i < z->s->img_x; ++i) *out++ = y[i], *out++ = 255; + } + } + stbi__cleanup_jpeg(z); + *out_x = z->s->img_x; + *out_y = z->s->img_y; + if (comp) *comp = z->s->img_n; // report original components, not output + return output; + } +} + +static unsigned char *stbi__jpeg_load(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + unsigned char* result; + stbi__jpeg* j = (stbi__jpeg*) stbi__malloc(sizeof(stbi__jpeg)); + j->s = s; + stbi__setup_jpeg(j); + result = load_jpeg_image(j, x,y,comp,req_comp); + STBI_FREE(j); + return result; +} + +static int stbi__jpeg_test(stbi__context *s) +{ + int r; + stbi__jpeg j; + j.s = s; + stbi__setup_jpeg(&j); + r = stbi__decode_jpeg_header(&j, STBI__SCAN_type); + stbi__rewind(s); + return r; +} + +static int stbi__jpeg_info_raw(stbi__jpeg *j, int *x, int *y, int *comp) +{ + if (!stbi__decode_jpeg_header(j, STBI__SCAN_header)) { + stbi__rewind( j->s ); + return 0; + } + if (x) *x = j->s->img_x; + if (y) *y = j->s->img_y; + if (comp) *comp = j->s->img_n; + return 1; +} + +static int stbi__jpeg_info(stbi__context *s, int *x, int *y, int *comp) +{ + int result; + stbi__jpeg* j = (stbi__jpeg*) (stbi__malloc(sizeof(stbi__jpeg))); + j->s = s; + result = stbi__jpeg_info_raw(j, x, y, comp); + STBI_FREE(j); + return result; +} +#endif + +// public domain zlib decode v0.2 Sean Barrett 2006-11-18 +// simple implementation +// - all input must be provided in an upfront buffer +// - all output is written to a single output buffer (can malloc/realloc) +// performance +// - fast huffman + +#ifndef STBI_NO_ZLIB + +// fast-way is faster to check than jpeg huffman, but slow way is slower +#define STBI__ZFAST_BITS 9 // accelerate all cases in default tables +#define STBI__ZFAST_MASK ((1 << STBI__ZFAST_BITS) - 1) + +// zlib-style huffman encoding +// (jpegs packs from left, zlib from right, so can't share code) +typedef struct +{ + stbi__uint16 fast[1 << STBI__ZFAST_BITS]; + stbi__uint16 firstcode[16]; + int maxcode[17]; + stbi__uint16 firstsymbol[16]; + stbi_uc size[288]; + stbi__uint16 value[288]; +} stbi__zhuffman; + +stbi_inline static int stbi__bitreverse16(int n) +{ + n = ((n & 0xAAAA) >> 1) | ((n & 0x5555) << 1); + n = ((n & 0xCCCC) >> 2) | ((n & 0x3333) << 2); + n = ((n & 0xF0F0) >> 4) | ((n & 0x0F0F) << 4); + n = ((n & 0xFF00) >> 8) | ((n & 0x00FF) << 8); + return n; +} + +stbi_inline static int stbi__bit_reverse(int v, int bits) +{ + STBI_ASSERT(bits <= 16); + // to bit reverse n bits, reverse 16 and shift + // e.g. 11 bits, bit reverse and shift away 5 + return stbi__bitreverse16(v) >> (16-bits); +} + +static int stbi__zbuild_huffman(stbi__zhuffman *z, stbi_uc *sizelist, int num) +{ + int i,k=0; + int code, next_code[16], sizes[17]; + + // DEFLATE spec for generating codes + memset(sizes, 0, sizeof(sizes)); + memset(z->fast, 0, sizeof(z->fast)); + for (i=0; i < num; ++i) + ++sizes[sizelist[i]]; + sizes[0] = 0; + for (i=1; i < 16; ++i) + if (sizes[i] > (1 << i)) + return stbi__err("bad sizes", "Corrupt PNG"); + code = 0; + for (i=1; i < 16; ++i) { + next_code[i] = code; + z->firstcode[i] = (stbi__uint16) code; + z->firstsymbol[i] = (stbi__uint16) k; + code = (code + sizes[i]); + if (sizes[i]) + if (code-1 >= (1 << i)) return stbi__err("bad codelengths","Corrupt PNG"); + z->maxcode[i] = code << (16-i); // preshift for inner loop + code <<= 1; + k += sizes[i]; + } + z->maxcode[16] = 0x10000; // sentinel + for (i=0; i < num; ++i) { + int s = sizelist[i]; + if (s) { + int c = next_code[s] - z->firstcode[s] + z->firstsymbol[s]; + stbi__uint16 fastv = (stbi__uint16) ((s << 9) | i); + z->size [c] = (stbi_uc ) s; + z->value[c] = (stbi__uint16) i; + if (s <= STBI__ZFAST_BITS) { + int j = stbi__bit_reverse(next_code[s],s); + while (j < (1 << STBI__ZFAST_BITS)) { + z->fast[j] = fastv; + j += (1 << s); + } + } + ++next_code[s]; + } + } + return 1; +} + +// zlib-from-memory implementation for PNG reading +// because PNG allows splitting the zlib stream arbitrarily, +// and it's annoying structurally to have PNG call ZLIB call PNG, +// we require PNG read all the IDATs and combine them into a single +// memory buffer + +typedef struct +{ + stbi_uc *zbuffer, *zbuffer_end; + int num_bits; + stbi__uint32 code_buffer; + + char *zout; + char *zout_start; + char *zout_end; + int z_expandable; + + stbi__zhuffman z_length, z_distance; +} stbi__zbuf; + +stbi_inline static stbi_uc stbi__zget8(stbi__zbuf *z) +{ + if (z->zbuffer >= z->zbuffer_end) return 0; + return *z->zbuffer++; +} + +static void stbi__fill_bits(stbi__zbuf *z) +{ + do { + STBI_ASSERT(z->code_buffer < (1U << z->num_bits)); + z->code_buffer |= (unsigned int) stbi__zget8(z) << z->num_bits; + z->num_bits += 8; + } while (z->num_bits <= 24); +} + +stbi_inline static unsigned int stbi__zreceive(stbi__zbuf *z, int n) +{ + unsigned int k; + if (z->num_bits < n) stbi__fill_bits(z); + k = z->code_buffer & ((1 << n) - 1); + z->code_buffer >>= n; + z->num_bits -= n; + return k; +} + +static int stbi__zhuffman_decode_slowpath(stbi__zbuf *a, stbi__zhuffman *z) +{ + int b,s,k; + // not resolved by fast table, so compute it the slow way + // use jpeg approach, which requires MSbits at top + k = stbi__bit_reverse(a->code_buffer, 16); + for (s=STBI__ZFAST_BITS+1; ; ++s) + if (k < z->maxcode[s]) + break; + if (s == 16) return -1; // invalid code! + // code size is s, so: + b = (k >> (16-s)) - z->firstcode[s] + z->firstsymbol[s]; + STBI_ASSERT(z->size[b] == s); + a->code_buffer >>= s; + a->num_bits -= s; + return z->value[b]; +} + +stbi_inline static int stbi__zhuffman_decode(stbi__zbuf *a, stbi__zhuffman *z) +{ + int b,s; + if (a->num_bits < 16) stbi__fill_bits(a); + b = z->fast[a->code_buffer & STBI__ZFAST_MASK]; + if (b) { + s = b >> 9; + a->code_buffer >>= s; + a->num_bits -= s; + return b & 511; + } + return stbi__zhuffman_decode_slowpath(a, z); +} + +static int stbi__zexpand(stbi__zbuf *z, char *zout, int n) // need to make room for n bytes +{ + char *q; + int cur, limit, old_limit; + z->zout = zout; + if (!z->z_expandable) return stbi__err("output buffer limit","Corrupt PNG"); + cur = (int) (z->zout - z->zout_start); + limit = old_limit = (int) (z->zout_end - z->zout_start); + while (cur + n > limit) + limit *= 2; + q = (char *) STBI_REALLOC_SIZED(z->zout_start, old_limit, limit); + STBI_NOTUSED(old_limit); + if (q == NULL) return stbi__err("outofmem", "Out of memory"); + z->zout_start = q; + z->zout = q + cur; + z->zout_end = q + limit; + return 1; +} + +static int stbi__zlength_base[31] = { + 3,4,5,6,7,8,9,10,11,13, + 15,17,19,23,27,31,35,43,51,59, + 67,83,99,115,131,163,195,227,258,0,0 }; + +static int stbi__zlength_extra[31]= +{ 0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0 }; + +static int stbi__zdist_base[32] = { 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193, +257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,0,0}; + +static int stbi__zdist_extra[32] = +{ 0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13}; + +static int stbi__parse_huffman_block(stbi__zbuf *a) +{ + char *zout = a->zout; + for(;;) { + int z = stbi__zhuffman_decode(a, &a->z_length); + if (z < 256) { + if (z < 0) return stbi__err("bad huffman code","Corrupt PNG"); // error in huffman codes + if (zout >= a->zout_end) { + if (!stbi__zexpand(a, zout, 1)) return 0; + zout = a->zout; + } + *zout++ = (char) z; + } else { + stbi_uc *p; + int len,dist; + if (z == 256) { + a->zout = zout; + return 1; + } + z -= 257; + len = stbi__zlength_base[z]; + if (stbi__zlength_extra[z]) len += stbi__zreceive(a, stbi__zlength_extra[z]); + z = stbi__zhuffman_decode(a, &a->z_distance); + if (z < 0) return stbi__err("bad huffman code","Corrupt PNG"); + dist = stbi__zdist_base[z]; + if (stbi__zdist_extra[z]) dist += stbi__zreceive(a, stbi__zdist_extra[z]); + if (zout - a->zout_start < dist) return stbi__err("bad dist","Corrupt PNG"); + if (zout + len > a->zout_end) { + if (!stbi__zexpand(a, zout, len)) return 0; + zout = a->zout; + } + p = (stbi_uc *) (zout - dist); + if (dist == 1) { // run of one byte; common in images. + stbi_uc v = *p; + if (len) { do *zout++ = v; while (--len); } + } else { + if (len) { do *zout++ = *p++; while (--len); } + } + } + } +} + +static int stbi__compute_huffman_codes(stbi__zbuf *a) +{ + static stbi_uc length_dezigzag[19] = { 16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15 }; + stbi__zhuffman z_codelength; + stbi_uc lencodes[286+32+137];//padding for maximum single op + stbi_uc codelength_sizes[19]; + int i,n; + + int hlit = stbi__zreceive(a,5) + 257; + int hdist = stbi__zreceive(a,5) + 1; + int hclen = stbi__zreceive(a,4) + 4; + + memset(codelength_sizes, 0, sizeof(codelength_sizes)); + for (i=0; i < hclen; ++i) { + int s = stbi__zreceive(a,3); + codelength_sizes[length_dezigzag[i]] = (stbi_uc) s; + } + if (!stbi__zbuild_huffman(&z_codelength, codelength_sizes, 19)) return 0; + + n = 0; + while (n < hlit + hdist) { + int c = stbi__zhuffman_decode(a, &z_codelength); + if (c < 0 || c >= 19) return stbi__err("bad codelengths", "Corrupt PNG"); + if (c < 16) + lencodes[n++] = (stbi_uc) c; + else if (c == 16) { + c = stbi__zreceive(a,2)+3; + memset(lencodes+n, lencodes[n-1], c); + n += c; + } else if (c == 17) { + c = stbi__zreceive(a,3)+3; + memset(lencodes+n, 0, c); + n += c; + } else { + STBI_ASSERT(c == 18); + c = stbi__zreceive(a,7)+11; + memset(lencodes+n, 0, c); + n += c; + } + } + if (n != hlit+hdist) return stbi__err("bad codelengths","Corrupt PNG"); + if (!stbi__zbuild_huffman(&a->z_length, lencodes, hlit)) return 0; + if (!stbi__zbuild_huffman(&a->z_distance, lencodes+hlit, hdist)) return 0; + return 1; +} + +static int stbi__parse_uncompressed_block(stbi__zbuf *a) +{ + stbi_uc header[4]; + int len,nlen,k; + if (a->num_bits & 7) + stbi__zreceive(a, a->num_bits & 7); // discard + // drain the bit-packed data into header + k = 0; + while (a->num_bits > 0) { + header[k++] = (stbi_uc) (a->code_buffer & 255); // suppress MSVC run-time check + a->code_buffer >>= 8; + a->num_bits -= 8; + } + STBI_ASSERT(a->num_bits == 0); + // now fill header the normal way + while (k < 4) + header[k++] = stbi__zget8(a); + len = header[1] * 256 + header[0]; + nlen = header[3] * 256 + header[2]; + if (nlen != (len ^ 0xffff)) return stbi__err("zlib corrupt","Corrupt PNG"); + if (a->zbuffer + len > a->zbuffer_end) return stbi__err("read past buffer","Corrupt PNG"); + if (a->zout + len > a->zout_end) + if (!stbi__zexpand(a, a->zout, len)) return 0; + memcpy(a->zout, a->zbuffer, len); + a->zbuffer += len; + a->zout += len; + return 1; +} + +static int stbi__parse_zlib_header(stbi__zbuf *a) +{ + int cmf = stbi__zget8(a); + int cm = cmf & 15; + /* int cinfo = cmf >> 4; */ + int flg = stbi__zget8(a); + if ((cmf*256+flg) % 31 != 0) return stbi__err("bad zlib header","Corrupt PNG"); // zlib spec + if (flg & 32) return stbi__err("no preset dict","Corrupt PNG"); // preset dictionary not allowed in png + if (cm != 8) return stbi__err("bad compression","Corrupt PNG"); // DEFLATE required for png + // window = 1 << (8 + cinfo)... but who cares, we fully buffer output + return 1; +} + +// @TODO: should statically initialize these for optimal thread safety +static stbi_uc stbi__zdefault_length[288], stbi__zdefault_distance[32]; +static void stbi__init_zdefaults(void) +{ + int i; // use <= to match clearly with spec + for (i=0; i <= 143; ++i) stbi__zdefault_length[i] = 8; + for ( ; i <= 255; ++i) stbi__zdefault_length[i] = 9; + for ( ; i <= 279; ++i) stbi__zdefault_length[i] = 7; + for ( ; i <= 287; ++i) stbi__zdefault_length[i] = 8; + + for (i=0; i <= 31; ++i) stbi__zdefault_distance[i] = 5; +} + +static int stbi__parse_zlib(stbi__zbuf *a, int parse_header) +{ + int final, type; + if (parse_header) + if (!stbi__parse_zlib_header(a)) return 0; + a->num_bits = 0; + a->code_buffer = 0; + do { + final = stbi__zreceive(a,1); + type = stbi__zreceive(a,2); + if (type == 0) { + if (!stbi__parse_uncompressed_block(a)) return 0; + } else if (type == 3) { + return 0; + } else { + if (type == 1) { + // use fixed code lengths + if (!stbi__zdefault_distance[31]) stbi__init_zdefaults(); + if (!stbi__zbuild_huffman(&a->z_length , stbi__zdefault_length , 288)) return 0; + if (!stbi__zbuild_huffman(&a->z_distance, stbi__zdefault_distance, 32)) return 0; + } else { + if (!stbi__compute_huffman_codes(a)) return 0; + } + if (!stbi__parse_huffman_block(a)) return 0; + } + } while (!final); + return 1; +} + +static int stbi__do_zlib(stbi__zbuf *a, char *obuf, int olen, int exp, int parse_header) +{ + a->zout_start = obuf; + a->zout = obuf; + a->zout_end = obuf + olen; + a->z_expandable = exp; + + return stbi__parse_zlib(a, parse_header); +} + +STBIDEF char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int initial_size, int *outlen) +{ + stbi__zbuf a; + char *p = (char *) stbi__malloc(initial_size); + if (p == NULL) return NULL; + a.zbuffer = (stbi_uc *) buffer; + a.zbuffer_end = (stbi_uc *) buffer + len; + if (stbi__do_zlib(&a, p, initial_size, 1, 1)) { + if (outlen) *outlen = (int) (a.zout - a.zout_start); + return a.zout_start; + } else { + STBI_FREE(a.zout_start); + return NULL; + } +} + +STBIDEF char *stbi_zlib_decode_malloc(char const *buffer, int len, int *outlen) +{ + return stbi_zlib_decode_malloc_guesssize(buffer, len, 16384, outlen); +} + +STBIDEF char *stbi_zlib_decode_malloc_guesssize_headerflag(const char *buffer, int len, int initial_size, int *outlen, int parse_header) +{ + stbi__zbuf a; + char *p = (char *) stbi__malloc(initial_size); + if (p == NULL) return NULL; + a.zbuffer = (stbi_uc *) buffer; + a.zbuffer_end = (stbi_uc *) buffer + len; + if (stbi__do_zlib(&a, p, initial_size, 1, parse_header)) { + if (outlen) *outlen = (int) (a.zout - a.zout_start); + return a.zout_start; + } else { + STBI_FREE(a.zout_start); + return NULL; + } +} + +STBIDEF int stbi_zlib_decode_buffer(char *obuffer, int olen, char const *ibuffer, int ilen) +{ + stbi__zbuf a; + a.zbuffer = (stbi_uc *) ibuffer; + a.zbuffer_end = (stbi_uc *) ibuffer + ilen; + if (stbi__do_zlib(&a, obuffer, olen, 0, 1)) + return (int) (a.zout - a.zout_start); + else + return -1; +} + +STBIDEF char *stbi_zlib_decode_noheader_malloc(char const *buffer, int len, int *outlen) +{ + stbi__zbuf a; + char *p = (char *) stbi__malloc(16384); + if (p == NULL) return NULL; + a.zbuffer = (stbi_uc *) buffer; + a.zbuffer_end = (stbi_uc *) buffer+len; + if (stbi__do_zlib(&a, p, 16384, 1, 0)) { + if (outlen) *outlen = (int) (a.zout - a.zout_start); + return a.zout_start; + } else { + STBI_FREE(a.zout_start); + return NULL; + } +} + +STBIDEF int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const char *ibuffer, int ilen) +{ + stbi__zbuf a; + a.zbuffer = (stbi_uc *) ibuffer; + a.zbuffer_end = (stbi_uc *) ibuffer + ilen; + if (stbi__do_zlib(&a, obuffer, olen, 0, 0)) + return (int) (a.zout - a.zout_start); + else + return -1; +} +#endif + +// public domain "baseline" PNG decoder v0.10 Sean Barrett 2006-11-18 +// simple implementation +// - only 8-bit samples +// - no CRC checking +// - allocates lots of intermediate memory +// - avoids problem of streaming data between subsystems +// - avoids explicit window management +// performance +// - uses stb_zlib, a PD zlib implementation with fast huffman decoding + +#ifndef STBI_NO_PNG +typedef struct +{ + stbi__uint32 length; + stbi__uint32 type; +} stbi__pngchunk; + +static stbi__pngchunk stbi__get_chunk_header(stbi__context *s) +{ + stbi__pngchunk c; + c.length = stbi__get32be(s); + c.type = stbi__get32be(s); + return c; +} + +static int stbi__check_png_header(stbi__context *s) +{ + static stbi_uc png_sig[8] = { 137,80,78,71,13,10,26,10 }; + int i; + for (i=0; i < 8; ++i) + if (stbi__get8(s) != png_sig[i]) return stbi__err("bad png sig","Not a PNG"); + return 1; +} + +typedef struct +{ + stbi__context *s; + stbi_uc *idata, *expanded, *out; + int depth; +} stbi__png; + + +enum { + STBI__F_none=0, + STBI__F_sub=1, + STBI__F_up=2, + STBI__F_avg=3, + STBI__F_paeth=4, + // synthetic filters used for first scanline to avoid needing a dummy row of 0s + STBI__F_avg_first, + STBI__F_paeth_first +}; + +static stbi_uc first_row_filter[5] = +{ + STBI__F_none, + STBI__F_sub, + STBI__F_none, + STBI__F_avg_first, + STBI__F_paeth_first +}; + +static int stbi__paeth(int a, int b, int c) +{ + int p = a + b - c; + int pa = abs(p-a); + int pb = abs(p-b); + int pc = abs(p-c); + if (pa <= pb && pa <= pc) return a; + if (pb <= pc) return b; + return c; +} + +static stbi_uc stbi__depth_scale_table[9] = { 0, 0xff, 0x55, 0, 0x11, 0,0,0, 0x01 }; + +// create the png data from post-deflated data +static int stbi__create_png_image_raw(stbi__png *a, stbi_uc *raw, stbi__uint32 raw_len, int out_n, stbi__uint32 x, stbi__uint32 y, int depth, int color) +{ + int bytes = (depth == 16? 2 : 1); + stbi__context *s = a->s; + stbi__uint32 i,j,stride = x*out_n*bytes; + stbi__uint32 img_len, img_width_bytes; + int k; + int img_n = s->img_n; // copy it into a local for later + + int output_bytes = out_n*bytes; + int filter_bytes = img_n*bytes; + int width = x; + + STBI_ASSERT(out_n == s->img_n || out_n == s->img_n+1); + a->out = (stbi_uc *) stbi__malloc(x * y * output_bytes); // extra bytes to write off the end into + if (!a->out) return stbi__err("outofmem", "Out of memory"); + + img_width_bytes = (((img_n * x * depth) + 7) >> 3); + img_len = (img_width_bytes + 1) * y; + if (s->img_x == x && s->img_y == y) { + if (raw_len != img_len) return stbi__err("not enough pixels","Corrupt PNG"); + } else { // interlaced: + if (raw_len < img_len) return stbi__err("not enough pixels","Corrupt PNG"); + } + + for (j=0; j < y; ++j) { + stbi_uc *cur = a->out + stride*j; + stbi_uc *prior = cur - stride; + int filter = *raw++; + + if (filter > 4) + return stbi__err("invalid filter","Corrupt PNG"); + + if (depth < 8) { + STBI_ASSERT(img_width_bytes <= x); + cur += x*out_n - img_width_bytes; // store output to the rightmost img_len bytes, so we can decode in place + filter_bytes = 1; + width = img_width_bytes; + } + + // if first row, use special filter that doesn't sample previous row + if (j == 0) filter = first_row_filter[filter]; + + // handle first byte explicitly + for (k=0; k < filter_bytes; ++k) { + switch (filter) { + case STBI__F_none : cur[k] = raw[k]; break; + case STBI__F_sub : cur[k] = raw[k]; break; + case STBI__F_up : cur[k] = STBI__BYTECAST(raw[k] + prior[k]); break; + case STBI__F_avg : cur[k] = STBI__BYTECAST(raw[k] + (prior[k]>>1)); break; + case STBI__F_paeth : cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(0,prior[k],0)); break; + case STBI__F_avg_first : cur[k] = raw[k]; break; + case STBI__F_paeth_first: cur[k] = raw[k]; break; + } + } + + if (depth == 8) { + if (img_n != out_n) + cur[img_n] = 255; // first pixel + raw += img_n; + cur += out_n; + prior += out_n; + } else if (depth == 16) { + if (img_n != out_n) { + cur[filter_bytes] = 255; // first pixel top byte + cur[filter_bytes+1] = 255; // first pixel bottom byte + } + raw += filter_bytes; + cur += output_bytes; + prior += output_bytes; + } else { + raw += 1; + cur += 1; + prior += 1; + } + + // this is a little gross, so that we don't switch per-pixel or per-component + if (depth < 8 || img_n == out_n) { + int nk = (width - 1)*filter_bytes; + #define CASE(f) \ + case f: \ + for (k=0; k < nk; ++k) + switch (filter) { + // "none" filter turns into a memcpy here; make that explicit. + case STBI__F_none: memcpy(cur, raw, nk); break; + CASE(STBI__F_sub) cur[k] = STBI__BYTECAST(raw[k] + cur[k-filter_bytes]); break; + CASE(STBI__F_up) cur[k] = STBI__BYTECAST(raw[k] + prior[k]); break; + CASE(STBI__F_avg) cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k-filter_bytes])>>1)); break; + CASE(STBI__F_paeth) cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes],prior[k],prior[k-filter_bytes])); break; + CASE(STBI__F_avg_first) cur[k] = STBI__BYTECAST(raw[k] + (cur[k-filter_bytes] >> 1)); break; + CASE(STBI__F_paeth_first) cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes],0,0)); break; + } + #undef CASE + raw += nk; + } else { + STBI_ASSERT(img_n+1 == out_n); + #define CASE(f) \ + case f: \ + for (i=x-1; i >= 1; --i, cur[filter_bytes]=255,raw+=filter_bytes,cur+=output_bytes,prior+=output_bytes) \ + for (k=0; k < filter_bytes; ++k) + switch (filter) { + CASE(STBI__F_none) cur[k] = raw[k]; break; + CASE(STBI__F_sub) cur[k] = STBI__BYTECAST(raw[k] + cur[k- output_bytes]); break; + CASE(STBI__F_up) cur[k] = STBI__BYTECAST(raw[k] + prior[k]); break; + CASE(STBI__F_avg) cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k- output_bytes])>>1)); break; + CASE(STBI__F_paeth) cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k- output_bytes],prior[k],prior[k- output_bytes])); break; + CASE(STBI__F_avg_first) cur[k] = STBI__BYTECAST(raw[k] + (cur[k- output_bytes] >> 1)); break; + CASE(STBI__F_paeth_first) cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k- output_bytes],0,0)); break; + } + #undef CASE + + // the loop above sets the high byte of the pixels' alpha, but for + // 16 bit png files we also need the low byte set. we'll do that here. + if (depth == 16) { + cur = a->out + stride*j; // start at the beginning of the row again + for (i=0; i < x; ++i,cur+=output_bytes) { + cur[filter_bytes+1] = 255; + } + } + } + } + + // we make a separate pass to expand bits to pixels; for performance, + // this could run two scanlines behind the above code, so it won't + // intefere with filtering but will still be in the cache. + if (depth < 8) { + for (j=0; j < y; ++j) { + stbi_uc *cur = a->out + stride*j; + stbi_uc *in = a->out + stride*j + x*out_n - img_width_bytes; + // unpack 1/2/4-bit into a 8-bit buffer. allows us to keep the common 8-bit path optimal at minimal cost for 1/2/4-bit + // png guarante byte alignment, if width is not multiple of 8/4/2 we'll decode dummy trailing data that will be skipped in the later loop + stbi_uc scale = (color == 0) ? stbi__depth_scale_table[depth] : 1; // scale grayscale values to 0..255 range + + // note that the final byte might overshoot and write more data than desired. + // we can allocate enough data that this never writes out of memory, but it + // could also overwrite the next scanline. can it overwrite non-empty data + // on the next scanline? yes, consider 1-pixel-wide scanlines with 1-bit-per-pixel. + // so we need to explicitly clamp the final ones + + if (depth == 4) { + for (k=x*img_n; k >= 2; k-=2, ++in) { + *cur++ = scale * ((*in >> 4) ); + *cur++ = scale * ((*in ) & 0x0f); + } + if (k > 0) *cur++ = scale * ((*in >> 4) ); + } else if (depth == 2) { + for (k=x*img_n; k >= 4; k-=4, ++in) { + *cur++ = scale * ((*in >> 6) ); + *cur++ = scale * ((*in >> 4) & 0x03); + *cur++ = scale * ((*in >> 2) & 0x03); + *cur++ = scale * ((*in ) & 0x03); + } + if (k > 0) *cur++ = scale * ((*in >> 6) ); + if (k > 1) *cur++ = scale * ((*in >> 4) & 0x03); + if (k > 2) *cur++ = scale * ((*in >> 2) & 0x03); + } else if (depth == 1) { + for (k=x*img_n; k >= 8; k-=8, ++in) { + *cur++ = scale * ((*in >> 7) ); + *cur++ = scale * ((*in >> 6) & 0x01); + *cur++ = scale * ((*in >> 5) & 0x01); + *cur++ = scale * ((*in >> 4) & 0x01); + *cur++ = scale * ((*in >> 3) & 0x01); + *cur++ = scale * ((*in >> 2) & 0x01); + *cur++ = scale * ((*in >> 1) & 0x01); + *cur++ = scale * ((*in ) & 0x01); + } + if (k > 0) *cur++ = scale * ((*in >> 7) ); + if (k > 1) *cur++ = scale * ((*in >> 6) & 0x01); + if (k > 2) *cur++ = scale * ((*in >> 5) & 0x01); + if (k > 3) *cur++ = scale * ((*in >> 4) & 0x01); + if (k > 4) *cur++ = scale * ((*in >> 3) & 0x01); + if (k > 5) *cur++ = scale * ((*in >> 2) & 0x01); + if (k > 6) *cur++ = scale * ((*in >> 1) & 0x01); + } + if (img_n != out_n) { + int q; + // insert alpha = 255 + cur = a->out + stride*j; + if (img_n == 1) { + for (q=x-1; q >= 0; --q) { + cur[q*2+1] = 255; + cur[q*2+0] = cur[q]; + } + } else { + STBI_ASSERT(img_n == 3); + for (q=x-1; q >= 0; --q) { + cur[q*4+3] = 255; + cur[q*4+2] = cur[q*3+2]; + cur[q*4+1] = cur[q*3+1]; + cur[q*4+0] = cur[q*3+0]; + } + } + } + } + } else if (depth == 16) { + // force the image data from big-endian to platform-native. + // this is done in a separate pass due to the decoding relying + // on the data being untouched, but could probably be done + // per-line during decode if care is taken. + stbi_uc *cur = a->out; + stbi__uint16 *cur16 = (stbi__uint16*)cur; + + for(i=0; i < x*y*out_n; ++i,cur16++,cur+=2) { + *cur16 = (cur[0] << 8) | cur[1]; + } + } + + return 1; +} + +static int stbi__create_png_image(stbi__png *a, stbi_uc *image_data, stbi__uint32 image_data_len, int out_n, int depth, int color, int interlaced) +{ + stbi_uc *final; + int p; + if (!interlaced) + return stbi__create_png_image_raw(a, image_data, image_data_len, out_n, a->s->img_x, a->s->img_y, depth, color); + + // de-interlacing + final = (stbi_uc *) stbi__malloc(a->s->img_x * a->s->img_y * out_n); + for (p=0; p < 7; ++p) { + int xorig[] = { 0,4,0,2,0,1,0 }; + int yorig[] = { 0,0,4,0,2,0,1 }; + int xspc[] = { 8,8,4,4,2,2,1 }; + int yspc[] = { 8,8,8,4,4,2,2 }; + int i,j,x,y; + // pass1_x[4] = 0, pass1_x[5] = 1, pass1_x[12] = 1 + x = (a->s->img_x - xorig[p] + xspc[p]-1) / xspc[p]; + y = (a->s->img_y - yorig[p] + yspc[p]-1) / yspc[p]; + if (x && y) { + stbi__uint32 img_len = ((((a->s->img_n * x * depth) + 7) >> 3) + 1) * y; + if (!stbi__create_png_image_raw(a, image_data, image_data_len, out_n, x, y, depth, color)) { + STBI_FREE(final); + return 0; + } + for (j=0; j < y; ++j) { + for (i=0; i < x; ++i) { + int out_y = j*yspc[p]+yorig[p]; + int out_x = i*xspc[p]+xorig[p]; + memcpy(final + out_y*a->s->img_x*out_n + out_x*out_n, + a->out + (j*x+i)*out_n, out_n); + } + } + STBI_FREE(a->out); + image_data += img_len; + image_data_len -= img_len; + } + } + a->out = final; + + return 1; +} + +static int stbi__compute_transparency(stbi__png *z, stbi_uc tc[3], int out_n) +{ + stbi__context *s = z->s; + stbi__uint32 i, pixel_count = s->img_x * s->img_y; + stbi_uc *p = z->out; + + // compute color-based transparency, assuming we've + // already got 255 as the alpha value in the output + STBI_ASSERT(out_n == 2 || out_n == 4); + + if (out_n == 2) { + for (i=0; i < pixel_count; ++i) { + p[1] = (p[0] == tc[0] ? 0 : 255); + p += 2; + } + } else { + for (i=0; i < pixel_count; ++i) { + if (p[0] == tc[0] && p[1] == tc[1] && p[2] == tc[2]) + p[3] = 0; + p += 4; + } + } + return 1; +} + +static int stbi__compute_transparency16(stbi__png *z, stbi__uint16 tc[3], int out_n) +{ + stbi__context *s = z->s; + stbi__uint32 i, pixel_count = s->img_x * s->img_y; + stbi__uint16 *p = (stbi__uint16*) z->out; + + // compute color-based transparency, assuming we've + // already got 65535 as the alpha value in the output + STBI_ASSERT(out_n == 2 || out_n == 4); + + if (out_n == 2) { + for (i = 0; i < pixel_count; ++i) { + p[1] = (p[0] == tc[0] ? 0 : 65535); + p += 2; + } + } else { + for (i = 0; i < pixel_count; ++i) { + if (p[0] == tc[0] && p[1] == tc[1] && p[2] == tc[2]) + p[3] = 0; + p += 4; + } + } + return 1; +} + +static int stbi__expand_png_palette(stbi__png *a, stbi_uc *palette, int len, int pal_img_n) +{ + stbi__uint32 i, pixel_count = a->s->img_x * a->s->img_y; + stbi_uc *p, *temp_out, *orig = a->out; + + p = (stbi_uc *) stbi__malloc(pixel_count * pal_img_n); + if (p == NULL) return stbi__err("outofmem", "Out of memory"); + + // between here and free(out) below, exitting would leak + temp_out = p; + + if (pal_img_n == 3) { + for (i=0; i < pixel_count; ++i) { + int n = orig[i]*4; + p[0] = palette[n ]; + p[1] = palette[n+1]; + p[2] = palette[n+2]; + p += 3; + } + } else { + for (i=0; i < pixel_count; ++i) { + int n = orig[i]*4; + p[0] = palette[n ]; + p[1] = palette[n+1]; + p[2] = palette[n+2]; + p[3] = palette[n+3]; + p += 4; + } + } + STBI_FREE(a->out); + a->out = temp_out; + + STBI_NOTUSED(len); + + return 1; +} + +static int stbi__reduce_png(stbi__png *p) +{ + int i; + int img_len = p->s->img_x * p->s->img_y * p->s->img_out_n; + stbi_uc *reduced; + stbi__uint16 *orig = (stbi__uint16*)p->out; + + if (p->depth != 16) return 1; // don't need to do anything if not 16-bit data + + reduced = (stbi_uc *)stbi__malloc(img_len); + if (p == NULL) return stbi__err("outofmem", "Out of memory"); + + for (i = 0; i < img_len; ++i) reduced[i] = (stbi_uc)((orig[i] >> 8) & 0xFF); // top half of each byte is a decent approx of 16->8 bit scaling + + p->out = reduced; + STBI_FREE(orig); + + return 1; +} + +static int stbi__unpremultiply_on_load = 0; +static int stbi__de_iphone_flag = 0; + +STBIDEF void stbi_set_unpremultiply_on_load(int flag_true_if_should_unpremultiply) +{ + stbi__unpremultiply_on_load = flag_true_if_should_unpremultiply; +} + +STBIDEF void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert) +{ + stbi__de_iphone_flag = flag_true_if_should_convert; +} + +static void stbi__de_iphone(stbi__png *z) +{ + stbi__context *s = z->s; + stbi__uint32 i, pixel_count = s->img_x * s->img_y; + stbi_uc *p = z->out; + + if (s->img_out_n == 3) { // convert bgr to rgb + for (i=0; i < pixel_count; ++i) { + stbi_uc t = p[0]; + p[0] = p[2]; + p[2] = t; + p += 3; + } + } else { + STBI_ASSERT(s->img_out_n == 4); + if (stbi__unpremultiply_on_load) { + // convert bgr to rgb and unpremultiply + for (i=0; i < pixel_count; ++i) { + stbi_uc a = p[3]; + stbi_uc t = p[0]; + if (a) { + p[0] = p[2] * 255 / a; + p[1] = p[1] * 255 / a; + p[2] = t * 255 / a; + } else { + p[0] = p[2]; + p[2] = t; + } + p += 4; + } + } else { + // convert bgr to rgb + for (i=0; i < pixel_count; ++i) { + stbi_uc t = p[0]; + p[0] = p[2]; + p[2] = t; + p += 4; + } + } + } +} + +#define STBI__PNG_TYPE(a,b,c,d) (((a) << 24) + ((b) << 16) + ((c) << 8) + (d)) + +static int stbi__parse_png_file(stbi__png *z, int scan, int req_comp) +{ + stbi_uc palette[1024], pal_img_n=0; + stbi_uc has_trans=0, tc[3]; + stbi__uint16 tc16[3]; + stbi__uint32 ioff=0, idata_limit=0, i, pal_len=0; + int first=1,k,interlace=0, color=0, is_iphone=0; + stbi__context *s = z->s; + + z->expanded = NULL; + z->idata = NULL; + z->out = NULL; + + if (!stbi__check_png_header(s)) return 0; + + if (scan == STBI__SCAN_type) return 1; + + for (;;) { + stbi__pngchunk c = stbi__get_chunk_header(s); + switch (c.type) { + case STBI__PNG_TYPE('C','g','B','I'): + is_iphone = 1; + stbi__skip(s, c.length); + break; + case STBI__PNG_TYPE('I','H','D','R'): { + int comp,filter; + if (!first) return stbi__err("multiple IHDR","Corrupt PNG"); + first = 0; + if (c.length != 13) return stbi__err("bad IHDR len","Corrupt PNG"); + s->img_x = stbi__get32be(s); if (s->img_x > (1 << 24)) return stbi__err("too large","Very large image (corrupt?)"); + s->img_y = stbi__get32be(s); if (s->img_y > (1 << 24)) return stbi__err("too large","Very large image (corrupt?)"); + z->depth = stbi__get8(s); if (z->depth != 1 && z->depth != 2 && z->depth != 4 && z->depth != 8 && z->depth != 16) return stbi__err("1/2/4/8/16-bit only","PNG not supported: 1/2/4/8/16-bit only"); + color = stbi__get8(s); if (color > 6) return stbi__err("bad ctype","Corrupt PNG"); + if (color == 3 && z->depth == 16) return stbi__err("bad ctype","Corrupt PNG"); + if (color == 3) pal_img_n = 3; else if (color & 1) return stbi__err("bad ctype","Corrupt PNG"); + comp = stbi__get8(s); if (comp) return stbi__err("bad comp method","Corrupt PNG"); + filter= stbi__get8(s); if (filter) return stbi__err("bad filter method","Corrupt PNG"); + interlace = stbi__get8(s); if (interlace>1) return stbi__err("bad interlace method","Corrupt PNG"); + if (!s->img_x || !s->img_y) return stbi__err("0-pixel image","Corrupt PNG"); + if (!pal_img_n) { + s->img_n = (color & 2 ? 3 : 1) + (color & 4 ? 1 : 0); + if ((1 << 30) / s->img_x / s->img_n < s->img_y) return stbi__err("too large", "Image too large to decode"); + if (scan == STBI__SCAN_header) return 1; + } else { + // if paletted, then pal_n is our final components, and + // img_n is # components to decompress/filter. + s->img_n = 1; + if ((1 << 30) / s->img_x / 4 < s->img_y) return stbi__err("too large","Corrupt PNG"); + // if SCAN_header, have to scan to see if we have a tRNS + } + break; + } + + case STBI__PNG_TYPE('P','L','T','E'): { + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (c.length > 256*3) return stbi__err("invalid PLTE","Corrupt PNG"); + pal_len = c.length / 3; + if (pal_len * 3 != c.length) return stbi__err("invalid PLTE","Corrupt PNG"); + for (i=0; i < pal_len; ++i) { + palette[i*4+0] = stbi__get8(s); + palette[i*4+1] = stbi__get8(s); + palette[i*4+2] = stbi__get8(s); + palette[i*4+3] = 255; + } + break; + } + + case STBI__PNG_TYPE('t','R','N','S'): { + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (z->idata) return stbi__err("tRNS after IDAT","Corrupt PNG"); + if (pal_img_n) { + if (scan == STBI__SCAN_header) { s->img_n = 4; return 1; } + if (pal_len == 0) return stbi__err("tRNS before PLTE","Corrupt PNG"); + if (c.length > pal_len) return stbi__err("bad tRNS len","Corrupt PNG"); + pal_img_n = 4; + for (i=0; i < c.length; ++i) + palette[i*4+3] = stbi__get8(s); + } else { + if (!(s->img_n & 1)) return stbi__err("tRNS with alpha","Corrupt PNG"); + if (c.length != (stbi__uint32) s->img_n*2) return stbi__err("bad tRNS len","Corrupt PNG"); + has_trans = 1; + if (z->depth == 16) { + for (k = 0; k < s->img_n; ++k) tc16[k] = stbi__get16be(s); // copy the values as-is + } else { + for (k = 0; k < s->img_n; ++k) tc[k] = (stbi_uc)(stbi__get16be(s) & 255) * stbi__depth_scale_table[z->depth]; // non 8-bit images will be larger + } + } + break; + } + + case STBI__PNG_TYPE('I','D','A','T'): { + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (pal_img_n && !pal_len) return stbi__err("no PLTE","Corrupt PNG"); + if (scan == STBI__SCAN_header) { s->img_n = pal_img_n; return 1; } + if ((int)(ioff + c.length) < (int)ioff) return 0; + if (ioff + c.length > idata_limit) { + stbi__uint32 idata_limit_old = idata_limit; + stbi_uc *p; + if (idata_limit == 0) idata_limit = c.length > 4096 ? c.length : 4096; + while (ioff + c.length > idata_limit) + idata_limit *= 2; + STBI_NOTUSED(idata_limit_old); + p = (stbi_uc *) STBI_REALLOC_SIZED(z->idata, idata_limit_old, idata_limit); if (p == NULL) return stbi__err("outofmem", "Out of memory"); + z->idata = p; + } + if (!stbi__getn(s, z->idata+ioff,c.length)) return stbi__err("outofdata","Corrupt PNG"); + ioff += c.length; + break; + } + + case STBI__PNG_TYPE('I','E','N','D'): { + stbi__uint32 raw_len, bpl; + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (scan != STBI__SCAN_load) return 1; + if (z->idata == NULL) return stbi__err("no IDAT","Corrupt PNG"); + // initial guess for decoded data size to avoid unnecessary reallocs + bpl = (s->img_x * z->depth + 7) / 8; // bytes per line, per component + raw_len = bpl * s->img_y * s->img_n /* pixels */ + s->img_y /* filter mode per row */; + z->expanded = (stbi_uc *) stbi_zlib_decode_malloc_guesssize_headerflag((char *) z->idata, ioff, raw_len, (int *) &raw_len, !is_iphone); + if (z->expanded == NULL) return 0; // zlib should set error + STBI_FREE(z->idata); z->idata = NULL; + if ((req_comp == s->img_n+1 && req_comp != 3 && !pal_img_n) || has_trans) + s->img_out_n = s->img_n+1; + else + s->img_out_n = s->img_n; + if (!stbi__create_png_image(z, z->expanded, raw_len, s->img_out_n, z->depth, color, interlace)) return 0; + if (has_trans) { + if (z->depth == 16) { + if (!stbi__compute_transparency16(z, tc16, s->img_out_n)) return 0; + } else { + if (!stbi__compute_transparency(z, tc, s->img_out_n)) return 0; + } + } + if (is_iphone && stbi__de_iphone_flag && s->img_out_n > 2) + stbi__de_iphone(z); + if (pal_img_n) { + // pal_img_n == 3 or 4 + s->img_n = pal_img_n; // record the actual colors we had + s->img_out_n = pal_img_n; + if (req_comp >= 3) s->img_out_n = req_comp; + if (!stbi__expand_png_palette(z, palette, pal_len, s->img_out_n)) + return 0; + } + STBI_FREE(z->expanded); z->expanded = NULL; + return 1; + } + + default: + // if critical, fail + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if ((c.type & (1 << 29)) == 0) { + #ifndef STBI_NO_FAILURE_STRINGS + // not threadsafe + static char invalid_chunk[] = "XXXX PNG chunk not known"; + invalid_chunk[0] = STBI__BYTECAST(c.type >> 24); + invalid_chunk[1] = STBI__BYTECAST(c.type >> 16); + invalid_chunk[2] = STBI__BYTECAST(c.type >> 8); + invalid_chunk[3] = STBI__BYTECAST(c.type >> 0); + #endif + return stbi__err(invalid_chunk, "PNG not supported: unknown PNG chunk type"); + } + stbi__skip(s, c.length); + break; + } + // end of PNG chunk, read and skip CRC + stbi__get32be(s); + } +} + +static unsigned char *stbi__do_png(stbi__png *p, int *x, int *y, int *n, int req_comp) +{ + unsigned char *result=NULL; + if (req_comp < 0 || req_comp > 4) return stbi__errpuc("bad req_comp", "Internal error"); + if (stbi__parse_png_file(p, STBI__SCAN_load, req_comp)) { + if (p->depth == 16) { + if (!stbi__reduce_png(p)) { + return result; + } + } + result = p->out; + p->out = NULL; + if (req_comp && req_comp != p->s->img_out_n) { + result = stbi__convert_format(result, p->s->img_out_n, req_comp, p->s->img_x, p->s->img_y); + p->s->img_out_n = req_comp; + if (result == NULL) return result; + } + *x = p->s->img_x; + *y = p->s->img_y; + if (n) *n = p->s->img_n; + } + STBI_FREE(p->out); p->out = NULL; + STBI_FREE(p->expanded); p->expanded = NULL; + STBI_FREE(p->idata); p->idata = NULL; + + return result; +} + +static unsigned char *stbi__png_load(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + stbi__png p; + p.s = s; + return stbi__do_png(&p, x,y,comp,req_comp); +} + +static int stbi__png_test(stbi__context *s) +{ + int r; + r = stbi__check_png_header(s); + stbi__rewind(s); + return r; +} + +static int stbi__png_info_raw(stbi__png *p, int *x, int *y, int *comp) +{ + if (!stbi__parse_png_file(p, STBI__SCAN_header, 0)) { + stbi__rewind( p->s ); + return 0; + } + if (x) *x = p->s->img_x; + if (y) *y = p->s->img_y; + if (comp) *comp = p->s->img_n; + return 1; +} + +static int stbi__png_info(stbi__context *s, int *x, int *y, int *comp) +{ + stbi__png p; + p.s = s; + return stbi__png_info_raw(&p, x, y, comp); +} +#endif + +// Microsoft/Windows BMP image + +#ifndef STBI_NO_BMP +static int stbi__bmp_test_raw(stbi__context *s) +{ + int r; + int sz; + if (stbi__get8(s) != 'B') return 0; + if (stbi__get8(s) != 'M') return 0; + stbi__get32le(s); // discard filesize + stbi__get16le(s); // discard reserved + stbi__get16le(s); // discard reserved + stbi__get32le(s); // discard data offset + sz = stbi__get32le(s); + r = (sz == 12 || sz == 40 || sz == 56 || sz == 108 || sz == 124); + return r; +} + +static int stbi__bmp_test(stbi__context *s) +{ + int r = stbi__bmp_test_raw(s); + stbi__rewind(s); + return r; +} + + +// returns 0..31 for the highest set bit +static int stbi__high_bit(unsigned int z) +{ + int n=0; + if (z == 0) return -1; + if (z >= 0x10000) n += 16, z >>= 16; + if (z >= 0x00100) n += 8, z >>= 8; + if (z >= 0x00010) n += 4, z >>= 4; + if (z >= 0x00004) n += 2, z >>= 2; + if (z >= 0x00002) n += 1, z >>= 1; + return n; +} + +static int stbi__bitcount(unsigned int a) +{ + a = (a & 0x55555555) + ((a >> 1) & 0x55555555); // max 2 + a = (a & 0x33333333) + ((a >> 2) & 0x33333333); // max 4 + a = (a + (a >> 4)) & 0x0f0f0f0f; // max 8 per 4, now 8 bits + a = (a + (a >> 8)); // max 16 per 8 bits + a = (a + (a >> 16)); // max 32 per 8 bits + return a & 0xff; +} + +static int stbi__shiftsigned(int v, int shift, int bits) +{ + int result; + int z=0; + + if (shift < 0) v <<= -shift; + else v >>= shift; + result = v; + + z = bits; + while (z < 8) { + result += v >> z; + z += bits; + } + return result; +} + +typedef struct +{ + int bpp, offset, hsz; + unsigned int mr,mg,mb,ma, all_a; +} stbi__bmp_data; + +static void *stbi__bmp_parse_header(stbi__context *s, stbi__bmp_data *info) +{ + int hsz; + if (stbi__get8(s) != 'B' || stbi__get8(s) != 'M') return stbi__errpuc("not BMP", "Corrupt BMP"); + stbi__get32le(s); // discard filesize + stbi__get16le(s); // discard reserved + stbi__get16le(s); // discard reserved + info->offset = stbi__get32le(s); + info->hsz = hsz = stbi__get32le(s); + info->mr = info->mg = info->mb = info->ma = 0; + + if (hsz != 12 && hsz != 40 && hsz != 56 && hsz != 108 && hsz != 124) return stbi__errpuc("unknown BMP", "BMP type not supported: unknown"); + if (hsz == 12) { + s->img_x = stbi__get16le(s); + s->img_y = stbi__get16le(s); + } else { + s->img_x = stbi__get32le(s); + s->img_y = stbi__get32le(s); + } + if (stbi__get16le(s) != 1) return stbi__errpuc("bad BMP", "bad BMP"); + info->bpp = stbi__get16le(s); + if (info->bpp == 1) return stbi__errpuc("monochrome", "BMP type not supported: 1-bit"); + if (hsz != 12) { + int compress = stbi__get32le(s); + if (compress == 1 || compress == 2) return stbi__errpuc("BMP RLE", "BMP type not supported: RLE"); + stbi__get32le(s); // discard sizeof + stbi__get32le(s); // discard hres + stbi__get32le(s); // discard vres + stbi__get32le(s); // discard colorsused + stbi__get32le(s); // discard max important + if (hsz == 40 || hsz == 56) { + if (hsz == 56) { + stbi__get32le(s); + stbi__get32le(s); + stbi__get32le(s); + stbi__get32le(s); + } + if (info->bpp == 16 || info->bpp == 32) { + if (compress == 0) { + if (info->bpp == 32) { + info->mr = 0xffu << 16; + info->mg = 0xffu << 8; + info->mb = 0xffu << 0; + info->ma = 0xffu << 24; + info->all_a = 0; // if all_a is 0 at end, then we loaded alpha channel but it was all 0 + } else { + info->mr = 31u << 10; + info->mg = 31u << 5; + info->mb = 31u << 0; + } + } else if (compress == 3) { + info->mr = stbi__get32le(s); + info->mg = stbi__get32le(s); + info->mb = stbi__get32le(s); + // not documented, but generated by photoshop and handled by mspaint + if (info->mr == info->mg && info->mg == info->mb) { + // ?!?!? + return stbi__errpuc("bad BMP", "bad BMP"); + } + } else + return stbi__errpuc("bad BMP", "bad BMP"); + } + } else { + int i; + if (hsz != 108 && hsz != 124) + return stbi__errpuc("bad BMP", "bad BMP"); + info->mr = stbi__get32le(s); + info->mg = stbi__get32le(s); + info->mb = stbi__get32le(s); + info->ma = stbi__get32le(s); + stbi__get32le(s); // discard color space + for (i=0; i < 12; ++i) + stbi__get32le(s); // discard color space parameters + if (hsz == 124) { + stbi__get32le(s); // discard rendering intent + stbi__get32le(s); // discard offset of profile data + stbi__get32le(s); // discard size of profile data + stbi__get32le(s); // discard reserved + } + } + } + return (void *) 1; +} + + +static stbi_uc *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + stbi_uc *out; + unsigned int mr=0,mg=0,mb=0,ma=0, all_a; + stbi_uc pal[256][4]; + int psize=0,i,j,width; + int flip_vertically, pad, target; + stbi__bmp_data info; + + info.all_a = 255; + if (stbi__bmp_parse_header(s, &info) == NULL) + return NULL; // error code already set + + flip_vertically = ((int) s->img_y) > 0; + s->img_y = abs((int) s->img_y); + + mr = info.mr; + mg = info.mg; + mb = info.mb; + ma = info.ma; + all_a = info.all_a; + + if (info.hsz == 12) { + if (info.bpp < 24) + psize = (info.offset - 14 - 24) / 3; + } else { + if (info.bpp < 16) + psize = (info.offset - 14 - info.hsz) >> 2; + } + + s->img_n = ma ? 4 : 3; + if (req_comp && req_comp >= 3) // we can directly decode 3 or 4 + target = req_comp; + else + target = s->img_n; // if they want monochrome, we'll post-convert + + out = (stbi_uc *) stbi__malloc(target * s->img_x * s->img_y); + if (!out) return stbi__errpuc("outofmem", "Out of memory"); + if (info.bpp < 16) { + int z=0; + if (psize == 0 || psize > 256) { STBI_FREE(out); return stbi__errpuc("invalid", "Corrupt BMP"); } + for (i=0; i < psize; ++i) { + pal[i][2] = stbi__get8(s); + pal[i][1] = stbi__get8(s); + pal[i][0] = stbi__get8(s); + if (info.hsz != 12) stbi__get8(s); + pal[i][3] = 255; + } + stbi__skip(s, info.offset - 14 - info.hsz - psize * (info.hsz == 12 ? 3 : 4)); + if (info.bpp == 4) width = (s->img_x + 1) >> 1; + else if (info.bpp == 8) width = s->img_x; + else { STBI_FREE(out); return stbi__errpuc("bad bpp", "Corrupt BMP"); } + pad = (-width)&3; + for (j=0; j < (int) s->img_y; ++j) { + for (i=0; i < (int) s->img_x; i += 2) { + int v=stbi__get8(s),v2=0; + if (info.bpp == 4) { + v2 = v & 15; + v >>= 4; + } + out[z++] = pal[v][0]; + out[z++] = pal[v][1]; + out[z++] = pal[v][2]; + if (target == 4) out[z++] = 255; + if (i+1 == (int) s->img_x) break; + v = (info.bpp == 8) ? stbi__get8(s) : v2; + out[z++] = pal[v][0]; + out[z++] = pal[v][1]; + out[z++] = pal[v][2]; + if (target == 4) out[z++] = 255; + } + stbi__skip(s, pad); + } + } else { + int rshift=0,gshift=0,bshift=0,ashift=0,rcount=0,gcount=0,bcount=0,acount=0; + int z = 0; + int easy=0; + stbi__skip(s, info.offset - 14 - info.hsz); + if (info.bpp == 24) width = 3 * s->img_x; + else if (info.bpp == 16) width = 2*s->img_x; + else /* bpp = 32 and pad = 0 */ width=0; + pad = (-width) & 3; + if (info.bpp == 24) { + easy = 1; + } else if (info.bpp == 32) { + if (mb == 0xff && mg == 0xff00 && mr == 0x00ff0000 && ma == 0xff000000) + easy = 2; + } + if (!easy) { + if (!mr || !mg || !mb) { STBI_FREE(out); return stbi__errpuc("bad masks", "Corrupt BMP"); } + // right shift amt to put high bit in position #7 + rshift = stbi__high_bit(mr)-7; rcount = stbi__bitcount(mr); + gshift = stbi__high_bit(mg)-7; gcount = stbi__bitcount(mg); + bshift = stbi__high_bit(mb)-7; bcount = stbi__bitcount(mb); + ashift = stbi__high_bit(ma)-7; acount = stbi__bitcount(ma); + } + for (j=0; j < (int) s->img_y; ++j) { + if (easy) { + for (i=0; i < (int) s->img_x; ++i) { + unsigned char a; + out[z+2] = stbi__get8(s); + out[z+1] = stbi__get8(s); + out[z+0] = stbi__get8(s); + z += 3; + a = (easy == 2 ? stbi__get8(s) : 255); + all_a |= a; + if (target == 4) out[z++] = a; + } + } else { + int bpp = info.bpp; + for (i=0; i < (int) s->img_x; ++i) { + stbi__uint32 v = (bpp == 16 ? (stbi__uint32) stbi__get16le(s) : stbi__get32le(s)); + int a; + out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mr, rshift, rcount)); + out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mg, gshift, gcount)); + out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mb, bshift, bcount)); + a = (ma ? stbi__shiftsigned(v & ma, ashift, acount) : 255); + all_a |= a; + if (target == 4) out[z++] = STBI__BYTECAST(a); + } + } + stbi__skip(s, pad); + } + } + + // if alpha channel is all 0s, replace with all 255s + if (target == 4 && all_a == 0) + for (i=4*s->img_x*s->img_y-1; i >= 0; i -= 4) + out[i] = 255; + + if (flip_vertically) { + stbi_uc t; + for (j=0; j < (int) s->img_y>>1; ++j) { + stbi_uc *p1 = out + j *s->img_x*target; + stbi_uc *p2 = out + (s->img_y-1-j)*s->img_x*target; + for (i=0; i < (int) s->img_x*target; ++i) { + t = p1[i], p1[i] = p2[i], p2[i] = t; + } + } + } + + if (req_comp && req_comp != target) { + out = stbi__convert_format(out, target, req_comp, s->img_x, s->img_y); + if (out == NULL) return out; // stbi__convert_format frees input on failure + } + + *x = s->img_x; + *y = s->img_y; + if (comp) *comp = s->img_n; + return out; +} +#endif + +// Targa Truevision - TGA +// by Jonathan Dummer +#ifndef STBI_NO_TGA +// returns STBI_rgb or whatever, 0 on error +static int stbi__tga_get_comp(int bits_per_pixel, int is_grey, int* is_rgb16) +{ + // only RGB or RGBA (incl. 16bit) or grey allowed + if(is_rgb16) *is_rgb16 = 0; + switch(bits_per_pixel) { + case 8: return STBI_grey; + case 16: if(is_grey) return STBI_grey_alpha; + // else: fall-through + case 15: if(is_rgb16) *is_rgb16 = 1; + return STBI_rgb; + case 24: // fall-through + case 32: return bits_per_pixel/8; + default: return 0; + } +} + +static int stbi__tga_info(stbi__context *s, int *x, int *y, int *comp) +{ + int tga_w, tga_h, tga_comp, tga_image_type, tga_bits_per_pixel, tga_colormap_bpp; + int sz, tga_colormap_type; + stbi__get8(s); // discard Offset + tga_colormap_type = stbi__get8(s); // colormap type + if( tga_colormap_type > 1 ) { + stbi__rewind(s); + return 0; // only RGB or indexed allowed + } + tga_image_type = stbi__get8(s); // image type + if ( tga_colormap_type == 1 ) { // colormapped (paletted) image + if (tga_image_type != 1 && tga_image_type != 9) { + stbi__rewind(s); + return 0; + } + stbi__skip(s,4); // skip index of first colormap entry and number of entries + sz = stbi__get8(s); // check bits per palette color entry + if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) { + stbi__rewind(s); + return 0; + } + stbi__skip(s,4); // skip image x and y origin + tga_colormap_bpp = sz; + } else { // "normal" image w/o colormap - only RGB or grey allowed, +/- RLE + if ( (tga_image_type != 2) && (tga_image_type != 3) && (tga_image_type != 10) && (tga_image_type != 11) ) { + stbi__rewind(s); + return 0; // only RGB or grey allowed, +/- RLE + } + stbi__skip(s,9); // skip colormap specification and image x/y origin + tga_colormap_bpp = 0; + } + tga_w = stbi__get16le(s); + if( tga_w < 1 ) { + stbi__rewind(s); + return 0; // test width + } + tga_h = stbi__get16le(s); + if( tga_h < 1 ) { + stbi__rewind(s); + return 0; // test height + } + tga_bits_per_pixel = stbi__get8(s); // bits per pixel + stbi__get8(s); // ignore alpha bits + if (tga_colormap_bpp != 0) { + if((tga_bits_per_pixel != 8) && (tga_bits_per_pixel != 16)) { + // when using a colormap, tga_bits_per_pixel is the size of the indexes + // I don't think anything but 8 or 16bit indexes makes sense + stbi__rewind(s); + return 0; + } + tga_comp = stbi__tga_get_comp(tga_colormap_bpp, 0, NULL); + } else { + tga_comp = stbi__tga_get_comp(tga_bits_per_pixel, (tga_image_type == 3) || (tga_image_type == 11), NULL); + } + if(!tga_comp) { + stbi__rewind(s); + return 0; + } + if (x) *x = tga_w; + if (y) *y = tga_h; + if (comp) *comp = tga_comp; + return 1; // seems to have passed everything +} + +static int stbi__tga_test(stbi__context *s) +{ + int res = 0; + int sz, tga_color_type; + stbi__get8(s); // discard Offset + tga_color_type = stbi__get8(s); // color type + if ( tga_color_type > 1 ) goto errorEnd; // only RGB or indexed allowed + sz = stbi__get8(s); // image type + if ( tga_color_type == 1 ) { // colormapped (paletted) image + if (sz != 1 && sz != 9) goto errorEnd; // colortype 1 demands image type 1 or 9 + stbi__skip(s,4); // skip index of first colormap entry and number of entries + sz = stbi__get8(s); // check bits per palette color entry + if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) goto errorEnd; + stbi__skip(s,4); // skip image x and y origin + } else { // "normal" image w/o colormap + if ( (sz != 2) && (sz != 3) && (sz != 10) && (sz != 11) ) goto errorEnd; // only RGB or grey allowed, +/- RLE + stbi__skip(s,9); // skip colormap specification and image x/y origin + } + if ( stbi__get16le(s) < 1 ) goto errorEnd; // test width + if ( stbi__get16le(s) < 1 ) goto errorEnd; // test height + sz = stbi__get8(s); // bits per pixel + if ( (tga_color_type == 1) && (sz != 8) && (sz != 16) ) goto errorEnd; // for colormapped images, bpp is size of an index + if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) goto errorEnd; + + res = 1; // if we got this far, everything's good and we can return 1 instead of 0 + +errorEnd: + stbi__rewind(s); + return res; +} + +// read 16bit value and convert to 24bit RGB +void stbi__tga_read_rgb16(stbi__context *s, stbi_uc* out) +{ + stbi__uint16 px = stbi__get16le(s); + stbi__uint16 fiveBitMask = 31; + // we have 3 channels with 5bits each + int r = (px >> 10) & fiveBitMask; + int g = (px >> 5) & fiveBitMask; + int b = px & fiveBitMask; + // Note that this saves the data in RGB(A) order, so it doesn't need to be swapped later + out[0] = (r * 255)/31; + out[1] = (g * 255)/31; + out[2] = (b * 255)/31; + + // some people claim that the most significant bit might be used for alpha + // (possibly if an alpha-bit is set in the "image descriptor byte") + // but that only made 16bit test images completely translucent.. + // so let's treat all 15 and 16bit TGAs as RGB with no alpha. +} + +static stbi_uc *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + // read in the TGA header stuff + int tga_offset = stbi__get8(s); + int tga_indexed = stbi__get8(s); + int tga_image_type = stbi__get8(s); + int tga_is_RLE = 0; + int tga_palette_start = stbi__get16le(s); + int tga_palette_len = stbi__get16le(s); + int tga_palette_bits = stbi__get8(s); + int tga_x_origin = stbi__get16le(s); + int tga_y_origin = stbi__get16le(s); + int tga_width = stbi__get16le(s); + int tga_height = stbi__get16le(s); + int tga_bits_per_pixel = stbi__get8(s); + int tga_comp, tga_rgb16=0; + int tga_inverted = stbi__get8(s); + // int tga_alpha_bits = tga_inverted & 15; // the 4 lowest bits - unused (useless?) + // image data + unsigned char *tga_data; + unsigned char *tga_palette = NULL; + int i, j; + unsigned char raw_data[4]; + int RLE_count = 0; + int RLE_repeating = 0; + int read_next_pixel = 1; + + // do a tiny bit of precessing + if ( tga_image_type >= 8 ) + { + tga_image_type -= 8; + tga_is_RLE = 1; + } + tga_inverted = 1 - ((tga_inverted >> 5) & 1); + + // If I'm paletted, then I'll use the number of bits from the palette + if ( tga_indexed ) tga_comp = stbi__tga_get_comp(tga_palette_bits, 0, &tga_rgb16); + else tga_comp = stbi__tga_get_comp(tga_bits_per_pixel, (tga_image_type == 3), &tga_rgb16); + + if(!tga_comp) // shouldn't really happen, stbi__tga_test() should have ensured basic consistency + return stbi__errpuc("bad format", "Can't find out TGA pixelformat"); + + // tga info + *x = tga_width; + *y = tga_height; + if (comp) *comp = tga_comp; + + tga_data = (unsigned char*)stbi__malloc( (size_t)tga_width * tga_height * tga_comp ); + if (!tga_data) return stbi__errpuc("outofmem", "Out of memory"); + + // skip to the data's starting position (offset usually = 0) + stbi__skip(s, tga_offset ); + + if ( !tga_indexed && !tga_is_RLE && !tga_rgb16 ) { + for (i=0; i < tga_height; ++i) { + int row = tga_inverted ? tga_height -i - 1 : i; + stbi_uc *tga_row = tga_data + row*tga_width*tga_comp; + stbi__getn(s, tga_row, tga_width * tga_comp); + } + } else { + // do I need to load a palette? + if ( tga_indexed) + { + // any data to skip? (offset usually = 0) + stbi__skip(s, tga_palette_start ); + // load the palette + tga_palette = (unsigned char*)stbi__malloc( tga_palette_len * tga_comp ); + if (!tga_palette) { + STBI_FREE(tga_data); + return stbi__errpuc("outofmem", "Out of memory"); + } + if (tga_rgb16) { + stbi_uc *pal_entry = tga_palette; + STBI_ASSERT(tga_comp == STBI_rgb); + for (i=0; i < tga_palette_len; ++i) { + stbi__tga_read_rgb16(s, pal_entry); + pal_entry += tga_comp; + } + } else if (!stbi__getn(s, tga_palette, tga_palette_len * tga_comp)) { + STBI_FREE(tga_data); + STBI_FREE(tga_palette); + return stbi__errpuc("bad palette", "Corrupt TGA"); + } + } + // load the data + for (i=0; i < tga_width * tga_height; ++i) + { + // if I'm in RLE mode, do I need to get a RLE stbi__pngchunk? + if ( tga_is_RLE ) + { + if ( RLE_count == 0 ) + { + // yep, get the next byte as a RLE command + int RLE_cmd = stbi__get8(s); + RLE_count = 1 + (RLE_cmd & 127); + RLE_repeating = RLE_cmd >> 7; + read_next_pixel = 1; + } else if ( !RLE_repeating ) + { + read_next_pixel = 1; + } + } else + { + read_next_pixel = 1; + } + // OK, if I need to read a pixel, do it now + if ( read_next_pixel ) + { + // load however much data we did have + if ( tga_indexed ) + { + // read in index, then perform the lookup + int pal_idx = (tga_bits_per_pixel == 8) ? stbi__get8(s) : stbi__get16le(s); + if ( pal_idx >= tga_palette_len ) { + // invalid index + pal_idx = 0; + } + pal_idx *= tga_comp; + for (j = 0; j < tga_comp; ++j) { + raw_data[j] = tga_palette[pal_idx+j]; + } + } else if(tga_rgb16) { + STBI_ASSERT(tga_comp == STBI_rgb); + stbi__tga_read_rgb16(s, raw_data); + } else { + // read in the data raw + for (j = 0; j < tga_comp; ++j) { + raw_data[j] = stbi__get8(s); + } + } + // clear the reading flag for the next pixel + read_next_pixel = 0; + } // end of reading a pixel + + // copy data + for (j = 0; j < tga_comp; ++j) + tga_data[i*tga_comp+j] = raw_data[j]; + + // in case we're in RLE mode, keep counting down + --RLE_count; + } + // do I need to invert the image? + if ( tga_inverted ) + { + for (j = 0; j*2 < tga_height; ++j) + { + int index1 = j * tga_width * tga_comp; + int index2 = (tga_height - 1 - j) * tga_width * tga_comp; + for (i = tga_width * tga_comp; i > 0; --i) + { + unsigned char temp = tga_data[index1]; + tga_data[index1] = tga_data[index2]; + tga_data[index2] = temp; + ++index1; + ++index2; + } + } + } + // clear my palette, if I had one + if ( tga_palette != NULL ) + { + STBI_FREE( tga_palette ); + } + } + + // swap RGB - if the source data was RGB16, it already is in the right order + if (tga_comp >= 3 && !tga_rgb16) + { + unsigned char* tga_pixel = tga_data; + for (i=0; i < tga_width * tga_height; ++i) + { + unsigned char temp = tga_pixel[0]; + tga_pixel[0] = tga_pixel[2]; + tga_pixel[2] = temp; + tga_pixel += tga_comp; + } + } + + // convert to target component count + if (req_comp && req_comp != tga_comp) + tga_data = stbi__convert_format(tga_data, tga_comp, req_comp, tga_width, tga_height); + + // the things I do to get rid of an error message, and yet keep + // Microsoft's C compilers happy... [8^( + tga_palette_start = tga_palette_len = tga_palette_bits = + tga_x_origin = tga_y_origin = 0; + // OK, done + return tga_data; +} +#endif + +// ************************************************************************************************* +// Photoshop PSD loader -- PD by Thatcher Ulrich, integration by Nicolas Schulz, tweaked by STB + +#ifndef STBI_NO_PSD +static int stbi__psd_test(stbi__context *s) +{ + int r = (stbi__get32be(s) == 0x38425053); + stbi__rewind(s); + return r; +} + +static stbi_uc *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + int pixelCount; + int channelCount, compression; + int channel, i, count, len; + int bitdepth; + int w,h; + stbi_uc *out; + + // Check identifier + if (stbi__get32be(s) != 0x38425053) // "8BPS" + return stbi__errpuc("not PSD", "Corrupt PSD image"); + + // Check file type version. + if (stbi__get16be(s) != 1) + return stbi__errpuc("wrong version", "Unsupported version of PSD image"); + + // Skip 6 reserved bytes. + stbi__skip(s, 6 ); + + // Read the number of channels (R, G, B, A, etc). + channelCount = stbi__get16be(s); + if (channelCount < 0 || channelCount > 16) + return stbi__errpuc("wrong channel count", "Unsupported number of channels in PSD image"); + + // Read the rows and columns of the image. + h = stbi__get32be(s); + w = stbi__get32be(s); + + // Make sure the depth is 8 bits. + bitdepth = stbi__get16be(s); + if (bitdepth != 8 && bitdepth != 16) + return stbi__errpuc("unsupported bit depth", "PSD bit depth is not 8 or 16 bit"); + + // Make sure the color mode is RGB. + // Valid options are: + // 0: Bitmap + // 1: Grayscale + // 2: Indexed color + // 3: RGB color + // 4: CMYK color + // 7: Multichannel + // 8: Duotone + // 9: Lab color + if (stbi__get16be(s) != 3) + return stbi__errpuc("wrong color format", "PSD is not in RGB color format"); + + // Skip the Mode Data. (It's the palette for indexed color; other info for other modes.) + stbi__skip(s,stbi__get32be(s) ); + + // Skip the image resources. (resolution, pen tool paths, etc) + stbi__skip(s, stbi__get32be(s) ); + + // Skip the reserved data. + stbi__skip(s, stbi__get32be(s) ); + + // Find out if the data is compressed. + // Known values: + // 0: no compression + // 1: RLE compressed + compression = stbi__get16be(s); + if (compression > 1) + return stbi__errpuc("bad compression", "PSD has an unknown compression format"); + + // Create the destination image. + out = (stbi_uc *) stbi__malloc(4 * w*h); + if (!out) return stbi__errpuc("outofmem", "Out of memory"); + pixelCount = w*h; + + // Initialize the data to zero. + //memset( out, 0, pixelCount * 4 ); + + // Finally, the image data. + if (compression) { + // RLE as used by .PSD and .TIFF + // Loop until you get the number of unpacked bytes you are expecting: + // Read the next source byte into n. + // If n is between 0 and 127 inclusive, copy the next n+1 bytes literally. + // Else if n is between -127 and -1 inclusive, copy the next byte -n+1 times. + // Else if n is 128, noop. + // Endloop + + // The RLE-compressed data is preceeded by a 2-byte data count for each row in the data, + // which we're going to just skip. + stbi__skip(s, h * channelCount * 2 ); + + // Read the RLE data by channel. + for (channel = 0; channel < 4; channel++) { + stbi_uc *p; + + p = out+channel; + if (channel >= channelCount) { + // Fill this channel with default data. + for (i = 0; i < pixelCount; i++, p += 4) + *p = (channel == 3 ? 255 : 0); + } else { + // Read the RLE data. + count = 0; + while (count < pixelCount) { + len = stbi__get8(s); + if (len == 128) { + // No-op. + } else if (len < 128) { + // Copy next len+1 bytes literally. + len++; + count += len; + while (len) { + *p = stbi__get8(s); + p += 4; + len--; + } + } else if (len > 128) { + stbi_uc val; + // Next -len+1 bytes in the dest are replicated from next source byte. + // (Interpret len as a negative 8-bit int.) + len ^= 0x0FF; + len += 2; + val = stbi__get8(s); + count += len; + while (len) { + *p = val; + p += 4; + len--; + } + } + } + } + } + + } else { + // We're at the raw image data. It's each channel in order (Red, Green, Blue, Alpha, ...) + // where each channel consists of an 8-bit value for each pixel in the image. + + // Read the data by channel. + for (channel = 0; channel < 4; channel++) { + stbi_uc *p; + + p = out + channel; + if (channel >= channelCount) { + // Fill this channel with default data. + stbi_uc val = channel == 3 ? 255 : 0; + for (i = 0; i < pixelCount; i++, p += 4) + *p = val; + } else { + // Read the data. + if (bitdepth == 16) { + for (i = 0; i < pixelCount; i++, p += 4) + *p = (stbi_uc) (stbi__get16be(s) >> 8); + } else { + for (i = 0; i < pixelCount; i++, p += 4) + *p = stbi__get8(s); + } + } + } + } + + if (channelCount >= 4) { + for (i=0; i < w*h; ++i) { + unsigned char *pixel = out + 4*i; + if (pixel[3] != 0 && pixel[3] != 255) { + // remove weird white matte from PSD + float a = pixel[3] / 255.0f; + float ra = 1.0f / a; + float inv_a = 255.0f * (1 - ra); + pixel[0] = (unsigned char) (pixel[0]*ra + inv_a); + pixel[1] = (unsigned char) (pixel[1]*ra + inv_a); + pixel[2] = (unsigned char) (pixel[2]*ra + inv_a); + } + } + } + + if (req_comp && req_comp != 4) { + out = stbi__convert_format(out, 4, req_comp, w, h); + if (out == NULL) return out; // stbi__convert_format frees input on failure + } + + if (comp) *comp = 4; + *y = h; + *x = w; + + return out; +} +#endif + +// ************************************************************************************************* +// Softimage PIC loader +// by Tom Seddon +// +// See http://softimage.wiki.softimage.com/index.php/INFO:_PIC_file_format +// See http://ozviz.wasp.uwa.edu.au/~pbourke/dataformats/softimagepic/ + +#ifndef STBI_NO_PIC +static int stbi__pic_is4(stbi__context *s,const char *str) +{ + int i; + for (i=0; i<4; ++i) + if (stbi__get8(s) != (stbi_uc)str[i]) + return 0; + + return 1; +} + +static int stbi__pic_test_core(stbi__context *s) +{ + int i; + + if (!stbi__pic_is4(s,"\x53\x80\xF6\x34")) + return 0; + + for(i=0;i<84;++i) + stbi__get8(s); + + if (!stbi__pic_is4(s,"PICT")) + return 0; + + return 1; +} + +typedef struct +{ + stbi_uc size,type,channel; +} stbi__pic_packet; + +static stbi_uc *stbi__readval(stbi__context *s, int channel, stbi_uc *dest) +{ + int mask=0x80, i; + + for (i=0; i<4; ++i, mask>>=1) { + if (channel & mask) { + if (stbi__at_eof(s)) return stbi__errpuc("bad file","PIC file too short"); + dest[i]=stbi__get8(s); + } + } + + return dest; +} + +static void stbi__copyval(int channel,stbi_uc *dest,const stbi_uc *src) +{ + int mask=0x80,i; + + for (i=0;i<4; ++i, mask>>=1) + if (channel&mask) + dest[i]=src[i]; +} + +static stbi_uc *stbi__pic_load_core(stbi__context *s,int width,int height,int *comp, stbi_uc *result) +{ + int act_comp=0,num_packets=0,y,chained; + stbi__pic_packet packets[10]; + + // this will (should...) cater for even some bizarre stuff like having data + // for the same channel in multiple packets. + do { + stbi__pic_packet *packet; + + if (num_packets==sizeof(packets)/sizeof(packets[0])) + return stbi__errpuc("bad format","too many packets"); + + packet = &packets[num_packets++]; + + chained = stbi__get8(s); + packet->size = stbi__get8(s); + packet->type = stbi__get8(s); + packet->channel = stbi__get8(s); + + act_comp |= packet->channel; + + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (reading packets)"); + if (packet->size != 8) return stbi__errpuc("bad format","packet isn't 8bpp"); + } while (chained); + + *comp = (act_comp & 0x10 ? 4 : 3); // has alpha channel? + + for(y=0; ytype) { + default: + return stbi__errpuc("bad format","packet has bad compression type"); + + case 0: {//uncompressed + int x; + + for(x=0;xchannel,dest)) + return 0; + break; + } + + case 1://Pure RLE + { + int left=width, i; + + while (left>0) { + stbi_uc count,value[4]; + + count=stbi__get8(s); + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (pure read count)"); + + if (count > left) + count = (stbi_uc) left; + + if (!stbi__readval(s,packet->channel,value)) return 0; + + for(i=0; ichannel,dest,value); + left -= count; + } + } + break; + + case 2: {//Mixed RLE + int left=width; + while (left>0) { + int count = stbi__get8(s), i; + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (mixed read count)"); + + if (count >= 128) { // Repeated + stbi_uc value[4]; + + if (count==128) + count = stbi__get16be(s); + else + count -= 127; + if (count > left) + return stbi__errpuc("bad file","scanline overrun"); + + if (!stbi__readval(s,packet->channel,value)) + return 0; + + for(i=0;ichannel,dest,value); + } else { // Raw + ++count; + if (count>left) return stbi__errpuc("bad file","scanline overrun"); + + for(i=0;ichannel,dest)) + return 0; + } + left-=count; + } + break; + } + } + } + } + + return result; +} + +static stbi_uc *stbi__pic_load(stbi__context *s,int *px,int *py,int *comp,int req_comp) +{ + stbi_uc *result; + int i, x,y; + + for (i=0; i<92; ++i) + stbi__get8(s); + + x = stbi__get16be(s); + y = stbi__get16be(s); + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (pic header)"); + if ((1 << 28) / x < y) return stbi__errpuc("too large", "Image too large to decode"); + + stbi__get32be(s); //skip `ratio' + stbi__get16be(s); //skip `fields' + stbi__get16be(s); //skip `pad' + + // intermediate buffer is RGBA + result = (stbi_uc *) stbi__malloc(x*y*4); + memset(result, 0xff, x*y*4); + + if (!stbi__pic_load_core(s,x,y,comp, result)) { + STBI_FREE(result); + result=0; + } + *px = x; + *py = y; + if (req_comp == 0) req_comp = *comp; + result=stbi__convert_format(result,4,req_comp,x,y); + + return result; +} + +static int stbi__pic_test(stbi__context *s) +{ + int r = stbi__pic_test_core(s); + stbi__rewind(s); + return r; +} +#endif + +// ************************************************************************************************* +// GIF loader -- public domain by Jean-Marc Lienher -- simplified/shrunk by stb + +#ifndef STBI_NO_GIF +typedef struct +{ + stbi__int16 prefix; + stbi_uc first; + stbi_uc suffix; +} stbi__gif_lzw; + +typedef struct +{ + int w,h; + stbi_uc *out, *old_out; // output buffer (always 4 components) + int flags, bgindex, ratio, transparent, eflags, delay; + stbi_uc pal[256][4]; + stbi_uc lpal[256][4]; + stbi__gif_lzw codes[4096]; + stbi_uc *color_table; + int parse, step; + int lflags; + int start_x, start_y; + int max_x, max_y; + int cur_x, cur_y; + int line_size; +} stbi__gif; + +static int stbi__gif_test_raw(stbi__context *s) +{ + int sz; + if (stbi__get8(s) != 'G' || stbi__get8(s) != 'I' || stbi__get8(s) != 'F' || stbi__get8(s) != '8') return 0; + sz = stbi__get8(s); + if (sz != '9' && sz != '7') return 0; + if (stbi__get8(s) != 'a') return 0; + return 1; +} + +static int stbi__gif_test(stbi__context *s) +{ + int r = stbi__gif_test_raw(s); + stbi__rewind(s); + return r; +} + +static void stbi__gif_parse_colortable(stbi__context *s, stbi_uc pal[256][4], int num_entries, int transp) +{ + int i; + for (i=0; i < num_entries; ++i) { + pal[i][2] = stbi__get8(s); + pal[i][1] = stbi__get8(s); + pal[i][0] = stbi__get8(s); + pal[i][3] = transp == i ? 0 : 255; + } +} + +static int stbi__gif_header(stbi__context *s, stbi__gif *g, int *comp, int is_info) +{ + stbi_uc version; + if (stbi__get8(s) != 'G' || stbi__get8(s) != 'I' || stbi__get8(s) != 'F' || stbi__get8(s) != '8') + return stbi__err("not GIF", "Corrupt GIF"); + + version = stbi__get8(s); + if (version != '7' && version != '9') return stbi__err("not GIF", "Corrupt GIF"); + if (stbi__get8(s) != 'a') return stbi__err("not GIF", "Corrupt GIF"); + + stbi__g_failure_reason = ""; + g->w = stbi__get16le(s); + g->h = stbi__get16le(s); + g->flags = stbi__get8(s); + g->bgindex = stbi__get8(s); + g->ratio = stbi__get8(s); + g->transparent = -1; + + if (comp != 0) *comp = 4; // can't actually tell whether it's 3 or 4 until we parse the comments + + if (is_info) return 1; + + if (g->flags & 0x80) + stbi__gif_parse_colortable(s,g->pal, 2 << (g->flags & 7), -1); + + return 1; +} + +static int stbi__gif_info_raw(stbi__context *s, int *x, int *y, int *comp) +{ + stbi__gif* g = (stbi__gif*) stbi__malloc(sizeof(stbi__gif)); + if (!stbi__gif_header(s, g, comp, 1)) { + STBI_FREE(g); + stbi__rewind( s ); + return 0; + } + if (x) *x = g->w; + if (y) *y = g->h; + STBI_FREE(g); + return 1; +} + +static void stbi__out_gif_code(stbi__gif *g, stbi__uint16 code) +{ + stbi_uc *p, *c; + + // recurse to decode the prefixes, since the linked-list is backwards, + // and working backwards through an interleaved image would be nasty + if (g->codes[code].prefix >= 0) + stbi__out_gif_code(g, g->codes[code].prefix); + + if (g->cur_y >= g->max_y) return; + + p = &g->out[g->cur_x + g->cur_y]; + c = &g->color_table[g->codes[code].suffix * 4]; + + if (c[3] >= 128) { + p[0] = c[2]; + p[1] = c[1]; + p[2] = c[0]; + p[3] = c[3]; + } + g->cur_x += 4; + + if (g->cur_x >= g->max_x) { + g->cur_x = g->start_x; + g->cur_y += g->step; + + while (g->cur_y >= g->max_y && g->parse > 0) { + g->step = (1 << g->parse) * g->line_size; + g->cur_y = g->start_y + (g->step >> 1); + --g->parse; + } + } +} + +static stbi_uc *stbi__process_gif_raster(stbi__context *s, stbi__gif *g) +{ + stbi_uc lzw_cs; + stbi__int32 len, init_code; + stbi__uint32 first; + stbi__int32 codesize, codemask, avail, oldcode, bits, valid_bits, clear; + stbi__gif_lzw *p; + + lzw_cs = stbi__get8(s); + if (lzw_cs > 12) return NULL; + clear = 1 << lzw_cs; + first = 1; + codesize = lzw_cs + 1; + codemask = (1 << codesize) - 1; + bits = 0; + valid_bits = 0; + for (init_code = 0; init_code < clear; init_code++) { + g->codes[init_code].prefix = -1; + g->codes[init_code].first = (stbi_uc) init_code; + g->codes[init_code].suffix = (stbi_uc) init_code; + } + + // support no starting clear code + avail = clear+2; + oldcode = -1; + + len = 0; + for(;;) { + if (valid_bits < codesize) { + if (len == 0) { + len = stbi__get8(s); // start new block + if (len == 0) + return g->out; + } + --len; + bits |= (stbi__int32) stbi__get8(s) << valid_bits; + valid_bits += 8; + } else { + stbi__int32 code = bits & codemask; + bits >>= codesize; + valid_bits -= codesize; + // @OPTIMIZE: is there some way we can accelerate the non-clear path? + if (code == clear) { // clear code + codesize = lzw_cs + 1; + codemask = (1 << codesize) - 1; + avail = clear + 2; + oldcode = -1; + first = 0; + } else if (code == clear + 1) { // end of stream code + stbi__skip(s, len); + while ((len = stbi__get8(s)) > 0) + stbi__skip(s,len); + return g->out; + } else if (code <= avail) { + if (first) return stbi__errpuc("no clear code", "Corrupt GIF"); + + if (oldcode >= 0) { + p = &g->codes[avail++]; + if (avail > 4096) return stbi__errpuc("too many codes", "Corrupt GIF"); + p->prefix = (stbi__int16) oldcode; + p->first = g->codes[oldcode].first; + p->suffix = (code == avail) ? p->first : g->codes[code].first; + } else if (code == avail) + return stbi__errpuc("illegal code in raster", "Corrupt GIF"); + + stbi__out_gif_code(g, (stbi__uint16) code); + + if ((avail & codemask) == 0 && avail <= 0x0FFF) { + codesize++; + codemask = (1 << codesize) - 1; + } + + oldcode = code; + } else { + return stbi__errpuc("illegal code in raster", "Corrupt GIF"); + } + } + } +} + +static void stbi__fill_gif_background(stbi__gif *g, int x0, int y0, int x1, int y1) +{ + int x, y; + stbi_uc *c = g->pal[g->bgindex]; + for (y = y0; y < y1; y += 4 * g->w) { + for (x = x0; x < x1; x += 4) { + stbi_uc *p = &g->out[y + x]; + p[0] = c[2]; + p[1] = c[1]; + p[2] = c[0]; + p[3] = 0; + } + } +} + +// this function is designed to support animated gifs, although stb_image doesn't support it +static stbi_uc *stbi__gif_load_next(stbi__context *s, stbi__gif *g, int *comp, int req_comp) +{ + int i; + stbi_uc *prev_out = 0; + + if (g->out == 0 && !stbi__gif_header(s, g, comp,0)) + return 0; // stbi__g_failure_reason set by stbi__gif_header + + prev_out = g->out; + g->out = (stbi_uc *) stbi__malloc(4 * g->w * g->h); + if (g->out == 0) return stbi__errpuc("outofmem", "Out of memory"); + + switch ((g->eflags & 0x1C) >> 2) { + case 0: // unspecified (also always used on 1st frame) + stbi__fill_gif_background(g, 0, 0, 4 * g->w, 4 * g->w * g->h); + break; + case 1: // do not dispose + if (prev_out) memcpy(g->out, prev_out, 4 * g->w * g->h); + g->old_out = prev_out; + break; + case 2: // dispose to background + if (prev_out) memcpy(g->out, prev_out, 4 * g->w * g->h); + stbi__fill_gif_background(g, g->start_x, g->start_y, g->max_x, g->max_y); + break; + case 3: // dispose to previous + if (g->old_out) { + for (i = g->start_y; i < g->max_y; i += 4 * g->w) + memcpy(&g->out[i + g->start_x], &g->old_out[i + g->start_x], g->max_x - g->start_x); + } + break; + } + + for (;;) { + switch (stbi__get8(s)) { + case 0x2C: /* Image Descriptor */ + { + int prev_trans = -1; + stbi__int32 x, y, w, h; + stbi_uc *o; + + x = stbi__get16le(s); + y = stbi__get16le(s); + w = stbi__get16le(s); + h = stbi__get16le(s); + if (((x + w) > (g->w)) || ((y + h) > (g->h))) + return stbi__errpuc("bad Image Descriptor", "Corrupt GIF"); + + g->line_size = g->w * 4; + g->start_x = x * 4; + g->start_y = y * g->line_size; + g->max_x = g->start_x + w * 4; + g->max_y = g->start_y + h * g->line_size; + g->cur_x = g->start_x; + g->cur_y = g->start_y; + + g->lflags = stbi__get8(s); + + if (g->lflags & 0x40) { + g->step = 8 * g->line_size; // first interlaced spacing + g->parse = 3; + } else { + g->step = g->line_size; + g->parse = 0; + } + + if (g->lflags & 0x80) { + stbi__gif_parse_colortable(s,g->lpal, 2 << (g->lflags & 7), g->eflags & 0x01 ? g->transparent : -1); + g->color_table = (stbi_uc *) g->lpal; + } else if (g->flags & 0x80) { + if (g->transparent >= 0 && (g->eflags & 0x01)) { + prev_trans = g->pal[g->transparent][3]; + g->pal[g->transparent][3] = 0; + } + g->color_table = (stbi_uc *) g->pal; + } else + return stbi__errpuc("missing color table", "Corrupt GIF"); + + o = stbi__process_gif_raster(s, g); + if (o == NULL) return NULL; + + if (prev_trans != -1) + g->pal[g->transparent][3] = (stbi_uc) prev_trans; + + return o; + } + + case 0x21: // Comment Extension. + { + int len; + if (stbi__get8(s) == 0xF9) { // Graphic Control Extension. + len = stbi__get8(s); + if (len == 4) { + g->eflags = stbi__get8(s); + g->delay = stbi__get16le(s); + g->transparent = stbi__get8(s); + } else { + stbi__skip(s, len); + break; + } + } + while ((len = stbi__get8(s)) != 0) + stbi__skip(s, len); + break; + } + + case 0x3B: // gif stream termination code + return (stbi_uc *) s; // using '1' causes warning on some compilers + + default: + return stbi__errpuc("unknown code", "Corrupt GIF"); + } + } + + STBI_NOTUSED(req_comp); +} + +static stbi_uc *stbi__gif_load(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + stbi_uc *u = 0; + stbi__gif* g = (stbi__gif*) stbi__malloc(sizeof(stbi__gif)); + memset(g, 0, sizeof(*g)); + + u = stbi__gif_load_next(s, g, comp, req_comp); + if (u == (stbi_uc *) s) u = 0; // end of animated gif marker + if (u) { + *x = g->w; + *y = g->h; + if (req_comp && req_comp != 4) + u = stbi__convert_format(u, 4, req_comp, g->w, g->h); + } + else if (g->out) + STBI_FREE(g->out); + STBI_FREE(g); + return u; +} + +static int stbi__gif_info(stbi__context *s, int *x, int *y, int *comp) +{ + return stbi__gif_info_raw(s,x,y,comp); +} +#endif + +// ************************************************************************************************* +// Radiance RGBE HDR loader +// originally by Nicolas Schulz +#ifndef STBI_NO_HDR +static int stbi__hdr_test_core(stbi__context *s) +{ + const char *signature = "#?RADIANCE\n"; + int i; + for (i=0; signature[i]; ++i) + if (stbi__get8(s) != signature[i]) + return 0; + return 1; +} + +static int stbi__hdr_test(stbi__context* s) +{ + int r = stbi__hdr_test_core(s); + stbi__rewind(s); + return r; +} + +#define STBI__HDR_BUFLEN 1024 +static char *stbi__hdr_gettoken(stbi__context *z, char *buffer) +{ + int len=0; + char c = '\0'; + + c = (char) stbi__get8(z); + + while (!stbi__at_eof(z) && c != '\n') { + buffer[len++] = c; + if (len == STBI__HDR_BUFLEN-1) { + // flush to end of line + while (!stbi__at_eof(z) && stbi__get8(z) != '\n') + ; + break; + } + c = (char) stbi__get8(z); + } + + buffer[len] = 0; + return buffer; +} + +static void stbi__hdr_convert(float *output, stbi_uc *input, int req_comp) +{ + if ( input[3] != 0 ) { + float f1; + // Exponent + f1 = (float) ldexp(1.0f, input[3] - (int)(128 + 8)); + if (req_comp <= 2) + output[0] = (input[0] + input[1] + input[2]) * f1 / 3; + else { + output[0] = input[0] * f1; + output[1] = input[1] * f1; + output[2] = input[2] * f1; + } + if (req_comp == 2) output[1] = 1; + if (req_comp == 4) output[3] = 1; + } else { + switch (req_comp) { + case 4: output[3] = 1; /* fallthrough */ + case 3: output[0] = output[1] = output[2] = 0; + break; + case 2: output[1] = 1; /* fallthrough */ + case 1: output[0] = 0; + break; + } + } +} + +static float *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + char buffer[STBI__HDR_BUFLEN]; + char *token; + int valid = 0; + int width, height; + stbi_uc *scanline; + float *hdr_data; + int len; + unsigned char count, value; + int i, j, k, c1,c2, z; + + + // Check identifier + if (strcmp(stbi__hdr_gettoken(s,buffer), "#?RADIANCE") != 0) + return stbi__errpf("not HDR", "Corrupt HDR image"); + + // Parse header + for(;;) { + token = stbi__hdr_gettoken(s,buffer); + if (token[0] == 0) break; + if (strcmp(token, "FORMAT=32-bit_rle_rgbe") == 0) valid = 1; + } + + if (!valid) return stbi__errpf("unsupported format", "Unsupported HDR format"); + + // Parse width and height + // can't use sscanf() if we're not using stdio! + token = stbi__hdr_gettoken(s,buffer); + if (strncmp(token, "-Y ", 3)) return stbi__errpf("unsupported data layout", "Unsupported HDR format"); + token += 3; + height = (int) strtol(token, &token, 10); + while (*token == ' ') ++token; + if (strncmp(token, "+X ", 3)) return stbi__errpf("unsupported data layout", "Unsupported HDR format"); + token += 3; + width = (int) strtol(token, NULL, 10); + + *x = width; + *y = height; + + if (comp) *comp = 3; + if (req_comp == 0) req_comp = 3; + + // Read data + hdr_data = (float *) stbi__malloc(height * width * req_comp * sizeof(float)); + + // Load image data + // image data is stored as some number of sca + if ( width < 8 || width >= 32768) { + // Read flat data + for (j=0; j < height; ++j) { + for (i=0; i < width; ++i) { + stbi_uc rgbe[4]; + main_decode_loop: + stbi__getn(s, rgbe, 4); + stbi__hdr_convert(hdr_data + j * width * req_comp + i * req_comp, rgbe, req_comp); + } + } + } else { + // Read RLE-encoded data + scanline = NULL; + + for (j = 0; j < height; ++j) { + c1 = stbi__get8(s); + c2 = stbi__get8(s); + len = stbi__get8(s); + if (c1 != 2 || c2 != 2 || (len & 0x80)) { + // not run-length encoded, so we have to actually use THIS data as a decoded + // pixel (note this can't be a valid pixel--one of RGB must be >= 128) + stbi_uc rgbe[4]; + rgbe[0] = (stbi_uc) c1; + rgbe[1] = (stbi_uc) c2; + rgbe[2] = (stbi_uc) len; + rgbe[3] = (stbi_uc) stbi__get8(s); + stbi__hdr_convert(hdr_data, rgbe, req_comp); + i = 1; + j = 0; + STBI_FREE(scanline); + goto main_decode_loop; // yes, this makes no sense + } + len <<= 8; + len |= stbi__get8(s); + if (len != width) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("invalid decoded scanline length", "corrupt HDR"); } + if (scanline == NULL) scanline = (stbi_uc *) stbi__malloc(width * 4); + + for (k = 0; k < 4; ++k) { + i = 0; + while (i < width) { + count = stbi__get8(s); + if (count > 128) { + // Run + value = stbi__get8(s); + count -= 128; + for (z = 0; z < count; ++z) + scanline[i++ * 4 + k] = value; + } else { + // Dump + for (z = 0; z < count; ++z) + scanline[i++ * 4 + k] = stbi__get8(s); + } + } + } + for (i=0; i < width; ++i) + stbi__hdr_convert(hdr_data+(j*width + i)*req_comp, scanline + i*4, req_comp); + } + STBI_FREE(scanline); + } + + return hdr_data; +} + +static int stbi__hdr_info(stbi__context *s, int *x, int *y, int *comp) +{ + char buffer[STBI__HDR_BUFLEN]; + char *token; + int valid = 0; + + if (stbi__hdr_test(s) == 0) { + stbi__rewind( s ); + return 0; + } + + for(;;) { + token = stbi__hdr_gettoken(s,buffer); + if (token[0] == 0) break; + if (strcmp(token, "FORMAT=32-bit_rle_rgbe") == 0) valid = 1; + } + + if (!valid) { + stbi__rewind( s ); + return 0; + } + token = stbi__hdr_gettoken(s,buffer); + if (strncmp(token, "-Y ", 3)) { + stbi__rewind( s ); + return 0; + } + token += 3; + *y = (int) strtol(token, &token, 10); + while (*token == ' ') ++token; + if (strncmp(token, "+X ", 3)) { + stbi__rewind( s ); + return 0; + } + token += 3; + *x = (int) strtol(token, NULL, 10); + *comp = 3; + return 1; +} +#endif // STBI_NO_HDR + +#ifndef STBI_NO_BMP +static int stbi__bmp_info(stbi__context *s, int *x, int *y, int *comp) +{ + void *p; + stbi__bmp_data info; + + info.all_a = 255; + p = stbi__bmp_parse_header(s, &info); + stbi__rewind( s ); + if (p == NULL) + return 0; + *x = s->img_x; + *y = s->img_y; + *comp = info.ma ? 4 : 3; + return 1; +} +#endif + +#ifndef STBI_NO_PSD +static int stbi__psd_info(stbi__context *s, int *x, int *y, int *comp) +{ + int channelCount; + if (stbi__get32be(s) != 0x38425053) { + stbi__rewind( s ); + return 0; + } + if (stbi__get16be(s) != 1) { + stbi__rewind( s ); + return 0; + } + stbi__skip(s, 6); + channelCount = stbi__get16be(s); + if (channelCount < 0 || channelCount > 16) { + stbi__rewind( s ); + return 0; + } + *y = stbi__get32be(s); + *x = stbi__get32be(s); + if (stbi__get16be(s) != 8) { + stbi__rewind( s ); + return 0; + } + if (stbi__get16be(s) != 3) { + stbi__rewind( s ); + return 0; + } + *comp = 4; + return 1; +} +#endif + +#ifndef STBI_NO_PIC +static int stbi__pic_info(stbi__context *s, int *x, int *y, int *comp) +{ + int act_comp=0,num_packets=0,chained; + stbi__pic_packet packets[10]; + + if (!stbi__pic_is4(s,"\x53\x80\xF6\x34")) { + stbi__rewind(s); + return 0; + } + + stbi__skip(s, 88); + + *x = stbi__get16be(s); + *y = stbi__get16be(s); + if (stbi__at_eof(s)) { + stbi__rewind( s); + return 0; + } + if ( (*x) != 0 && (1 << 28) / (*x) < (*y)) { + stbi__rewind( s ); + return 0; + } + + stbi__skip(s, 8); + + do { + stbi__pic_packet *packet; + + if (num_packets==sizeof(packets)/sizeof(packets[0])) + return 0; + + packet = &packets[num_packets++]; + chained = stbi__get8(s); + packet->size = stbi__get8(s); + packet->type = stbi__get8(s); + packet->channel = stbi__get8(s); + act_comp |= packet->channel; + + if (stbi__at_eof(s)) { + stbi__rewind( s ); + return 0; + } + if (packet->size != 8) { + stbi__rewind( s ); + return 0; + } + } while (chained); + + *comp = (act_comp & 0x10 ? 4 : 3); + + return 1; +} +#endif + +// ************************************************************************************************* +// Portable Gray Map and Portable Pixel Map loader +// by Ken Miller +// +// PGM: http://netpbm.sourceforge.net/doc/pgm.html +// PPM: http://netpbm.sourceforge.net/doc/ppm.html +// +// Known limitations: +// Does not support comments in the header section +// Does not support ASCII image data (formats P2 and P3) +// Does not support 16-bit-per-channel + +#ifndef STBI_NO_PNM + +static int stbi__pnm_test(stbi__context *s) +{ + char p, t; + p = (char) stbi__get8(s); + t = (char) stbi__get8(s); + if (p != 'P' || (t != '5' && t != '6')) { + stbi__rewind( s ); + return 0; + } + return 1; +} + +static stbi_uc *stbi__pnm_load(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + stbi_uc *out; + if (!stbi__pnm_info(s, (int *)&s->img_x, (int *)&s->img_y, (int *)&s->img_n)) + return 0; + *x = s->img_x; + *y = s->img_y; + *comp = s->img_n; + + out = (stbi_uc *) stbi__malloc(s->img_n * s->img_x * s->img_y); + if (!out) return stbi__errpuc("outofmem", "Out of memory"); + stbi__getn(s, out, s->img_n * s->img_x * s->img_y); + + if (req_comp && req_comp != s->img_n) { + out = stbi__convert_format(out, s->img_n, req_comp, s->img_x, s->img_y); + if (out == NULL) return out; // stbi__convert_format frees input on failure + } + return out; +} + +static int stbi__pnm_isspace(char c) +{ + return c == ' ' || c == '\t' || c == '\n' || c == '\v' || c == '\f' || c == '\r'; +} + +static void stbi__pnm_skip_whitespace(stbi__context *s, char *c) +{ + for (;;) { + while (!stbi__at_eof(s) && stbi__pnm_isspace(*c)) + *c = (char) stbi__get8(s); + + if (stbi__at_eof(s) || *c != '#') + break; + + while (!stbi__at_eof(s) && *c != '\n' && *c != '\r' ) + *c = (char) stbi__get8(s); + } +} + +static int stbi__pnm_isdigit(char c) +{ + return c >= '0' && c <= '9'; +} + +static int stbi__pnm_getinteger(stbi__context *s, char *c) +{ + int value = 0; + + while (!stbi__at_eof(s) && stbi__pnm_isdigit(*c)) { + value = value*10 + (*c - '0'); + *c = (char) stbi__get8(s); + } + + return value; +} + +static int stbi__pnm_info(stbi__context *s, int *x, int *y, int *comp) +{ + int maxv; + char c, p, t; + + stbi__rewind( s ); + + // Get identifier + p = (char) stbi__get8(s); + t = (char) stbi__get8(s); + if (p != 'P' || (t != '5' && t != '6')) { + stbi__rewind( s ); + return 0; + } + + *comp = (t == '6') ? 3 : 1; // '5' is 1-component .pgm; '6' is 3-component .ppm + + c = (char) stbi__get8(s); + stbi__pnm_skip_whitespace(s, &c); + + *x = stbi__pnm_getinteger(s, &c); // read width + stbi__pnm_skip_whitespace(s, &c); + + *y = stbi__pnm_getinteger(s, &c); // read height + stbi__pnm_skip_whitespace(s, &c); + + maxv = stbi__pnm_getinteger(s, &c); // read max value + + if (maxv > 255) + return stbi__err("max value > 255", "PPM image not 8-bit"); + else + return 1; +} +#endif + +static int stbi__info_main(stbi__context *s, int *x, int *y, int *comp) +{ + #ifndef STBI_NO_JPEG + if (stbi__jpeg_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PNG + if (stbi__png_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_GIF + if (stbi__gif_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_BMP + if (stbi__bmp_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PSD + if (stbi__psd_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PIC + if (stbi__pic_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PNM + if (stbi__pnm_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_HDR + if (stbi__hdr_info(s, x, y, comp)) return 1; + #endif + + // test tga last because it's a crappy test! + #ifndef STBI_NO_TGA + if (stbi__tga_info(s, x, y, comp)) + return 1; + #endif + return stbi__err("unknown image type", "Image not of any known type, or corrupt"); +} + +#ifndef STBI_NO_STDIO +STBIDEF int stbi_info(char const *filename, int *x, int *y, int *comp) +{ + FILE *f = stbi__fopen(filename, "rb"); + int result; + if (!f) return stbi__err("can't fopen", "Unable to open file"); + result = stbi_info_from_file(f, x, y, comp); + fclose(f); + return result; +} + +STBIDEF int stbi_info_from_file(FILE *f, int *x, int *y, int *comp) +{ + int r; + stbi__context s; + long pos = ftell(f); + stbi__start_file(&s, f); + r = stbi__info_main(&s,x,y,comp); + fseek(f,pos,SEEK_SET); + return r; +} +#endif // !STBI_NO_STDIO + +STBIDEF int stbi_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__info_main(&s,x,y,comp); +} + +STBIDEF int stbi_info_from_callbacks(stbi_io_callbacks const *c, void *user, int *x, int *y, int *comp) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) c, user); + return stbi__info_main(&s,x,y,comp); +} + +#endif // STB_IMAGE_IMPLEMENTATION + +/* + revision history: + 2.12 (2016-04-02) fix typo in 2.11 PSD fix that caused crashes + 2.11 (2016-04-02) allocate large structures on the stack + remove white matting for transparent PSD + fix reported channel count for PNG & BMP + re-enable SSE2 in non-gcc 64-bit + support RGB-formatted JPEG + read 16-bit PNGs (only as 8-bit) + 2.10 (2016-01-22) avoid warning introduced in 2.09 by STBI_REALLOC_SIZED + 2.09 (2016-01-16) allow comments in PNM files + 16-bit-per-pixel TGA (not bit-per-component) + info() for TGA could break due to .hdr handling + info() for BMP to shares code instead of sloppy parse + can use STBI_REALLOC_SIZED if allocator doesn't support realloc + code cleanup + 2.08 (2015-09-13) fix to 2.07 cleanup, reading RGB PSD as RGBA + 2.07 (2015-09-13) fix compiler warnings + partial animated GIF support + limited 16-bpc PSD support + #ifdef unused functions + bug with < 92 byte PIC,PNM,HDR,TGA + 2.06 (2015-04-19) fix bug where PSD returns wrong '*comp' value + 2.05 (2015-04-19) fix bug in progressive JPEG handling, fix warning + 2.04 (2015-04-15) try to re-enable SIMD on MinGW 64-bit + 2.03 (2015-04-12) extra corruption checking (mmozeiko) + stbi_set_flip_vertically_on_load (nguillemot) + fix NEON support; fix mingw support + 2.02 (2015-01-19) fix incorrect assert, fix warning + 2.01 (2015-01-17) fix various warnings; suppress SIMD on gcc 32-bit without -msse2 + 2.00b (2014-12-25) fix STBI_MALLOC in progressive JPEG + 2.00 (2014-12-25) optimize JPG, including x86 SSE2 & NEON SIMD (ryg) + progressive JPEG (stb) + PGM/PPM support (Ken Miller) + STBI_MALLOC,STBI_REALLOC,STBI_FREE + GIF bugfix -- seemingly never worked + STBI_NO_*, STBI_ONLY_* + 1.48 (2014-12-14) fix incorrectly-named assert() + 1.47 (2014-12-14) 1/2/4-bit PNG support, both direct and paletted (Omar Cornut & stb) + optimize PNG (ryg) + fix bug in interlaced PNG with user-specified channel count (stb) + 1.46 (2014-08-26) + fix broken tRNS chunk (colorkey-style transparency) in non-paletted PNG + 1.45 (2014-08-16) + fix MSVC-ARM internal compiler error by wrapping malloc + 1.44 (2014-08-07) + various warning fixes from Ronny Chevalier + 1.43 (2014-07-15) + fix MSVC-only compiler problem in code changed in 1.42 + 1.42 (2014-07-09) + don't define _CRT_SECURE_NO_WARNINGS (affects user code) + fixes to stbi__cleanup_jpeg path + added STBI_ASSERT to avoid requiring assert.h + 1.41 (2014-06-25) + fix search&replace from 1.36 that messed up comments/error messages + 1.40 (2014-06-22) + fix gcc struct-initialization warning + 1.39 (2014-06-15) + fix to TGA optimization when req_comp != number of components in TGA; + fix to GIF loading because BMP wasn't rewinding (whoops, no GIFs in my test suite) + add support for BMP version 5 (more ignored fields) + 1.38 (2014-06-06) + suppress MSVC warnings on integer casts truncating values + fix accidental rename of 'skip' field of I/O + 1.37 (2014-06-04) + remove duplicate typedef + 1.36 (2014-06-03) + convert to header file single-file library + if de-iphone isn't set, load iphone images color-swapped instead of returning NULL + 1.35 (2014-05-27) + various warnings + fix broken STBI_SIMD path + fix bug where stbi_load_from_file no longer left file pointer in correct place + fix broken non-easy path for 32-bit BMP (possibly never used) + TGA optimization by Arseny Kapoulkine + 1.34 (unknown) + use STBI_NOTUSED in stbi__resample_row_generic(), fix one more leak in tga failure case + 1.33 (2011-07-14) + make stbi_is_hdr work in STBI_NO_HDR (as specified), minor compiler-friendly improvements + 1.32 (2011-07-13) + support for "info" function for all supported filetypes (SpartanJ) + 1.31 (2011-06-20) + a few more leak fixes, bug in PNG handling (SpartanJ) + 1.30 (2011-06-11) + added ability to load files via callbacks to accomidate custom input streams (Ben Wenger) + removed deprecated format-specific test/load functions + removed support for installable file formats (stbi_loader) -- would have been broken for IO callbacks anyway + error cases in bmp and tga give messages and don't leak (Raymond Barbiero, grisha) + fix inefficiency in decoding 32-bit BMP (David Woo) + 1.29 (2010-08-16) + various warning fixes from Aurelien Pocheville + 1.28 (2010-08-01) + fix bug in GIF palette transparency (SpartanJ) + 1.27 (2010-08-01) + cast-to-stbi_uc to fix warnings + 1.26 (2010-07-24) + fix bug in file buffering for PNG reported by SpartanJ + 1.25 (2010-07-17) + refix trans_data warning (Won Chun) + 1.24 (2010-07-12) + perf improvements reading from files on platforms with lock-heavy fgetc() + minor perf improvements for jpeg + deprecated type-specific functions so we'll get feedback if they're needed + attempt to fix trans_data warning (Won Chun) + 1.23 fixed bug in iPhone support + 1.22 (2010-07-10) + removed image *writing* support + stbi_info support from Jetro Lauha + GIF support from Jean-Marc Lienher + iPhone PNG-extensions from James Brown + warning-fixes from Nicolas Schulz and Janez Zemva (i.stbi__err. Janez (U+017D)emva) + 1.21 fix use of 'stbi_uc' in header (reported by jon blow) + 1.20 added support for Softimage PIC, by Tom Seddon + 1.19 bug in interlaced PNG corruption check (found by ryg) + 1.18 (2008-08-02) + fix a threading bug (local mutable static) + 1.17 support interlaced PNG + 1.16 major bugfix - stbi__convert_format converted one too many pixels + 1.15 initialize some fields for thread safety + 1.14 fix threadsafe conversion bug + header-file-only version (#define STBI_HEADER_FILE_ONLY before including) + 1.13 threadsafe + 1.12 const qualifiers in the API + 1.11 Support installable IDCT, colorspace conversion routines + 1.10 Fixes for 64-bit (don't use "unsigned long") + optimized upsampling by Fabian "ryg" Giesen + 1.09 Fix format-conversion for PSD code (bad global variables!) + 1.08 Thatcher Ulrich's PSD code integrated by Nicolas Schulz + 1.07 attempt to fix C++ warning/errors again + 1.06 attempt to fix C++ warning/errors again + 1.05 fix TGA loading to return correct *comp and use good luminance calc + 1.04 default float alpha is 1, not 255; use 'void *' for stbi_image_free + 1.03 bugfixes to STBI_NO_STDIO, STBI_NO_HDR + 1.02 support for (subset of) HDR files, float interface for preferred access to them + 1.01 fix bug: possible bug in handling right-side up bmps... not sure + fix bug: the stbi__bmp_load() and stbi__tga_load() functions didn't work at all + 1.00 interface to zlib that skips zlib header + 0.99 correct handling of alpha in palette + 0.98 TGA loader by lonesock; dynamically add loaders (untested) + 0.97 jpeg errors on too large a file; also catch another malloc failure + 0.96 fix detection of invalid v value - particleman@mollyrocket forum + 0.95 during header scan, seek to markers in case of padding + 0.94 STBI_NO_STDIO to disable stdio usage; rename all #defines the same + 0.93 handle jpegtran output; verbose errors + 0.92 read 4,8,16,24,32-bit BMP files of several formats + 0.91 output 24-bit Windows 3.0 BMP files + 0.90 fix a few more warnings; bump version number to approach 1.0 + 0.61 bugfixes due to Marc LeBlanc, Christopher Lloyd + 0.60 fix compiling as c++ + 0.59 fix warnings: merge Dave Moore's -Wall fixes + 0.58 fix bug: zlib uncompressed mode len/nlen was wrong endian + 0.57 fix bug: jpg last huffman symbol before marker was >9 bits but less than 16 available + 0.56 fix bug: zlib uncompressed mode len vs. nlen + 0.55 fix bug: restart_interval not initialized to 0 + 0.54 allow NULL for 'int *comp' + 0.53 fix bug in png 3->4; speedup png decoding + 0.52 png handles req_comp=3,4 directly; minor cleanup; jpeg comments + 0.51 obey req_comp requests, 1-component jpegs return as 1-component, + on 'test' only check type, not whether we support this variant + 0.50 (2006-11-19) + first released version +*/ diff --git a/main.c b/main.c new file mode 100644 index 0000000..46d91d5 --- /dev/null +++ b/main.c @@ -0,0 +1,206 @@ +/* + * This screensaver demonstrates 2D shader effects through multiple textures. + */ + +#define SGP_UNIFORM_CONTENT_SLOTS 16 +#define SOKOL_IMPL +#include "sokol/sokol_gfx.h" +#include "sokol/sokol_gp.h" +#include "sokol/sokol_app.h" +#include "sokol/sokol_glue.h" +#include "sokol/sokol_log.h" +#include "sokol/sokol_time.h" + +#define STB_IMAGE_IMPLEMENTATION +#define STB_IMAGE_STATIC +#define STBI_NO_SIMD +#define STBI_ONLY_PNG +#include "stb/stb_image.h" + +#define SOKOL_SHDC_IMPL +#include "sample-effect.glsl.h" + +#ifdef __APPLE__ +#include +#include + +static void set_macos_working_directory(void) { + CFBundleRef bundle = CFBundleGetMainBundle(); + if (bundle) { + CFStringRef id = CFBundleGetIdentifier(bundle); + if (id) { + CFURLRef resourcesURL = CFBundleCopyResourcesDirectoryURL(bundle); + if (resourcesURL) { + char path[PATH_MAX]; + if (CFURLGetFileSystemRepresentation(resourcesURL, true, (UInt8*)path, PATH_MAX)) { + chdir(path); + } + CFRelease(resourcesURL); + } + } + } +} +#endif + +static sg_pipeline pip; +static sg_shader shd; +static sg_image image; +static sg_sampler linear_sampler; +static sg_image perlin_image; + +static void event(const sapp_event* ev) { + switch (ev->type) { + case SAPP_EVENTTYPE_KEY_DOWN: + if (ev->key_code == SAPP_KEYCODE_ESCAPE) + sapp_request_quit(); + break; + + default: + break; + } +} + +static void frame(void) { + // begin draw commands queue + int window_width = sapp_width(), window_height = sapp_height(); + sgp_begin(window_width, window_height); + + float secs = sapp_frame_count() * sapp_frame_duration(); + sg_image_desc image_desc = sg_query_image_desc(image); + float window_ratio = window_width / (float)window_height; + float image_ratio = image_desc.width / (float)image_desc.height; + effect_fs_uniforms_t uniforms = {0}; + uniforms.iVelocity.x = 0.02f; + uniforms.iVelocity.y = 0.01f; + uniforms.iPressure = 0.3f; + uniforms.iTime = secs; + uniforms.iWarpiness = 0.2f; + uniforms.iRatio = image_ratio; + uniforms.iZoom = 0.4f; + uniforms.iLevel = 1.0f; + sgp_set_pipeline(pip); + sgp_set_pipeline(pip); + sgp_set_uniform(NULL, 0, &uniforms, sizeof(effect_fs_uniforms_t)); + sgp_set_image(IMG_iTexChannel0, image); + sgp_set_image(IMG_iTexChannel1, perlin_image); + sgp_set_sampler(SMP_iSmpChannel0, linear_sampler); + sgp_set_sampler(SMP_iSmpChannel1, linear_sampler); + float width = (window_ratio >= image_ratio) ? window_width : image_ratio*window_height; + float height = (window_ratio >= image_ratio) ? window_width/image_ratio : window_height; + sgp_draw_filled_rect(0, 0, width, height); + sgp_reset_image(IMG_iTexChannel0); + sgp_reset_image(IMG_iTexChannel1); + sgp_reset_sampler(SMP_iSmpChannel0); + sgp_reset_sampler(SMP_iSmpChannel1); + sgp_reset_pipeline(); + + // dispatch draw commands + sg_pass pass = {.swapchain = sglue_swapchain()}; + sg_begin_pass(&pass); + sgp_flush(); + sgp_end(); + sg_end_pass(); + sg_commit(); +} + + +static sg_image load_image(const char *filename) { + int width, height, channels; + uint8_t* data = stbi_load(filename, &width, &height, &channels, 4); + sg_image img = {SG_INVALID_ID}; + if(!data) + return img; + sg_image_desc image_desc = {0}; + image_desc.width = width; + image_desc.height = height; + image_desc.data.subimage[0][0].ptr = data; + image_desc.data.subimage[0][0].size = (size_t)(width * height * 4); + img = sg_make_image(&image_desc); + stbi_image_free(data); + return img; +} + +static void init(void) { +#ifdef __APPLE__ + set_macos_working_directory(); +#endif + + // Initialize Sokol GFX + sg_desc sgdesc = { + .environment = sglue_environment(), + .logger.func = slog_func + }; + sg_setup(&sgdesc); + if (!sg_isvalid()) { + fprintf(stderr, "Failed to create Sokol GFX context!\n"); + exit(-1); + } + + // Initialize Sokol GP + sgp_desc sgpdesc = {0}; + sgp_setup(&sgpdesc); + if (!sgp_is_valid()) { + fprintf(stderr, "Failed to create Sokol GP context: %s\n", sgp_get_error_message(sgp_get_last_error())); + exit(-1); + } + + // Load image + image = load_image("data/images/majora.png"); + perlin_image = load_image("data/images/perlin.png"); + if (sg_query_image_state(image) != SG_RESOURCESTATE_VALID || sg_query_image_state(perlin_image) != SG_RESOURCESTATE_VALID) { + fprintf(stderr, "failed to load images"); + exit(-1); + } + + // Create linear sampler + sg_sampler_desc linear_sampler_desc = { + .min_filter = SG_FILTER_LINEAR, + .mag_filter = SG_FILTER_LINEAR, + .wrap_u = SG_WRAP_REPEAT, + .wrap_v = SG_WRAP_REPEAT, + }; + linear_sampler = sg_make_sampler(&linear_sampler_desc); + if (sg_query_sampler_state(linear_sampler) != SG_RESOURCESTATE_VALID) { + fprintf(stderr, "failed to create linear sampler"); + exit(-1); + } + + // Initialize shader + shd = sg_make_shader(effect_program_shader_desc(sg_query_backend())); + if (sg_query_shader_state(shd) != SG_RESOURCESTATE_VALID) { + fprintf(stderr, "failed to make custom pipeline shader\n"); + exit(-1); + } + sgp_pipeline_desc pip_desc = {0}; + pip_desc.shader = shd; + pip_desc.has_vs_color = true; + pip = sgp_make_pipeline(&pip_desc); + if (sg_query_pipeline_state(pip) != SG_RESOURCESTATE_VALID) { + fprintf(stderr, "failed to make custom pipeline\n"); + exit(-1); + } +} + +static void cleanup(void) { + sg_destroy_image(image); + sg_destroy_image(perlin_image); + sg_destroy_pipeline(pip); + sgp_shutdown(); + sg_shutdown(); +} + +sapp_desc sokol_main(int argc, char* argv[]) { + (void)argc; + (void)argv; + return (sapp_desc){ + .window_title = "Majora's Screensaver", + .width = 700, + .height = 720, + .high_dpi = true, + .init_cb = init, + .event_cb = event, + .frame_cb = frame, + .cleanup_cb = cleanup, + .logger.func = slog_func, + }; +}

  • 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� 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