xbintsc 0.5.31 → 0.5.32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (181) hide show
  1. package/README.md +16 -10
  2. package/dist/src/binder/references.js +14 -1
  3. package/dist/src/binder/references.js.map +1 -1
  4. package/dist/src/cli/help.d.ts +1 -1
  5. package/dist/src/codegen/generator/module.d.ts +1 -0
  6. package/dist/src/codegen/generator/module.js +54 -0
  7. package/dist/src/codegen/generator/module.js.map +1 -1
  8. package/dist/src/codegen/generator/statements/variables.js +6 -4
  9. package/dist/src/codegen/generator/statements/variables.js.map +1 -1
  10. package/dist/src/driver/version.d.ts +1 -1
  11. package/dist/src/driver/version.js +1 -1
  12. package/dist/src/extensions/gui/css.d.ts +26 -0
  13. package/dist/src/extensions/gui/css.js +100 -0
  14. package/dist/src/extensions/gui/css.js.map +1 -0
  15. package/dist/src/extensions/gui/html.js +9 -2
  16. package/dist/src/extensions/gui/html.js.map +1 -1
  17. package/dist/src/extensions/gui/index.js +16 -2
  18. package/dist/src/extensions/gui/index.js.map +1 -1
  19. package/dist/src/extensions/gui/three.d.ts +16 -0
  20. package/dist/src/extensions/gui/three.js +22 -0
  21. package/dist/src/extensions/gui/three.js.map +1 -0
  22. package/dist/src/extensions/media/index.d.ts +6 -5
  23. package/dist/src/extensions/media/index.js +12 -14
  24. package/dist/src/extensions/media/index.js.map +1 -1
  25. package/dist/tests/e2e/compile-builtins.test.js +24 -0
  26. package/dist/tests/e2e/compile-builtins.test.js.map +1 -1
  27. package/dist/tests/e2e/compile-objects-classes.test.js +23 -0
  28. package/dist/tests/e2e/compile-objects-classes.test.js.map +1 -1
  29. package/dist/tests/e2e/gui-browser.test.js +1 -1
  30. package/dist/tests/e2e/gui-browser.test.js.map +1 -1
  31. package/dist/tests/e2e/gui-effects.test.d.ts +6 -0
  32. package/dist/tests/e2e/gui-effects.test.js +262 -0
  33. package/dist/tests/e2e/gui-effects.test.js.map +1 -0
  34. package/dist/tests/e2e/gui-example-webgl.test.d.ts +10 -0
  35. package/dist/tests/e2e/gui-example-webgl.test.js +75 -0
  36. package/dist/tests/e2e/gui-example-webgl.test.js.map +1 -0
  37. package/dist/tests/e2e/gui-example-webgl2.test.d.ts +8 -0
  38. package/dist/tests/e2e/gui-example-webgl2.test.js +72 -0
  39. package/dist/tests/e2e/gui-example-webgl2.test.js.map +1 -0
  40. package/dist/tests/e2e/gui-layout.test.js +48 -0
  41. package/dist/tests/e2e/gui-layout.test.js.map +1 -1
  42. package/dist/tests/e2e/gui-media.test.js +5 -4
  43. package/dist/tests/e2e/gui-media.test.js.map +1 -1
  44. package/dist/tests/e2e/gui-webgl.test.d.ts +11 -0
  45. package/dist/tests/e2e/gui-webgl.test.js +179 -0
  46. package/dist/tests/e2e/gui-webgl.test.js.map +1 -0
  47. package/dist/tests/e2e/gui-webgl2.test.d.ts +10 -0
  48. package/dist/tests/e2e/gui-webgl2.test.js +238 -0
  49. package/dist/tests/e2e/gui-webgl2.test.js.map +1 -0
  50. package/dist/tests/extensions/gui.test.js +41 -0
  51. package/dist/tests/extensions/gui.test.js.map +1 -1
  52. package/dist/tests/extensions/media.test.js +7 -3
  53. package/dist/tests/extensions/media.test.js.map +1 -1
  54. package/doc/ai/extensions.md +29 -9
  55. package/doc/ai/troubleshooting.md +12 -8
  56. package/doc/ai/zh-CN/extensions.md +26 -8
  57. package/doc/ai/zh-CN/troubleshooting.md +10 -6
  58. package/doc/gui/api.md +3 -2
  59. package/doc/gui/build.md +72 -16
  60. package/doc/gui/canvas.md +11 -3
  61. package/doc/gui/dom.md +33 -18
  62. package/doc/gui/known-issues.md +57 -24
  63. package/doc/gui/layout.md +18 -0
  64. package/doc/gui/media.md +17 -13
  65. package/doc/gui/rendering.md +50 -3
  66. package/doc/gui/webgl.md +256 -0
  67. package/doc/gui.md +108 -17
  68. package/doc/npm-package.md +6 -4
  69. package/doc/requirements.md +18 -9
  70. package/doc/zh-CN/gui/api.md +3 -2
  71. package/doc/zh-CN/gui/build.md +64 -15
  72. package/doc/zh-CN/gui/canvas.md +9 -2
  73. package/doc/zh-CN/gui/dom.md +20 -7
  74. package/doc/zh-CN/gui/known-issues.md +45 -19
  75. package/doc/zh-CN/gui/layout.md +14 -0
  76. package/doc/zh-CN/gui/media.md +15 -11
  77. package/doc/zh-CN/gui/rendering.md +41 -4
  78. package/doc/zh-CN/gui/webgl.md +115 -0
  79. package/doc/zh-CN/gui.md +89 -14
  80. package/doc/zh-CN/npm-package.md +9 -6
  81. package/doc/zh-CN/requirements.md +12 -6
  82. package/llms.txt +2 -0
  83. package/package.json +1 -1
  84. package/runtime/ext_gui/browser.cpp +39 -13
  85. package/runtime/ext_gui/canvas.cpp +3 -18
  86. package/runtime/ext_gui/canvas_internal.h +10 -0
  87. package/runtime/ext_gui/canvas_raster.cpp +60 -0
  88. package/runtime/ext_gui/clip_values.cpp +246 -0
  89. package/runtime/ext_gui/css.cpp +94 -0
  90. package/runtime/ext_gui/css.h +22 -1
  91. package/runtime/ext_gui/css_match.cpp +3 -0
  92. package/runtime/ext_gui/css_selector.cpp +9 -0
  93. package/runtime/ext_gui/css_sheet.cpp +89 -45
  94. package/runtime/ext_gui/document.cpp +4 -1
  95. package/runtime/ext_gui/document.h +2 -0
  96. package/runtime/ext_gui/dom.h +16 -0
  97. package/runtime/ext_gui/dom_api.cpp +1 -0
  98. package/runtime/ext_gui/dom_api_canvas.cpp +17 -80
  99. package/runtime/ext_gui/dom_api_canvas_extra.cpp +130 -1
  100. package/runtime/ext_gui/dom_api_canvas_pending.cpp +41 -0
  101. package/runtime/ext_gui/dom_api_internal.h +259 -0
  102. package/runtime/ext_gui/dom_api_webgl.cpp +376 -0
  103. package/runtime/ext_gui/dom_api_webgl2.cpp +434 -0
  104. package/runtime/ext_gui/dom_api_webgl2_draw.cpp +269 -0
  105. package/runtime/ext_gui/dom_api_webgl_buffer.cpp +212 -0
  106. package/runtime/ext_gui/dom_api_webgl_rt.h +30 -0
  107. package/runtime/ext_gui/dom_api_webgl_shader.cpp +443 -0
  108. package/runtime/ext_gui/dom_api_webgl_state.cpp +444 -0
  109. package/runtime/ext_gui/dom_api_webgl_texture.cpp +445 -0
  110. package/runtime/ext_gui/effect_values.cpp +312 -0
  111. package/runtime/ext_gui/gradient_values.cpp +476 -0
  112. package/runtime/ext_gui/grid_values.cpp +230 -0
  113. package/runtime/ext_gui/gui_canvas.cpp +50 -2
  114. package/runtime/ext_gui/gui_engine.h +27 -0
  115. package/runtime/ext_gui/gui_events.cpp +4 -1
  116. package/runtime/ext_gui/gui_media.cpp +1 -1
  117. package/runtime/ext_gui/gui_webgl.cpp +83 -0
  118. package/runtime/ext_gui/layout.cpp +21 -0
  119. package/runtime/ext_gui/layout_flow.cpp +81 -4
  120. package/runtime/ext_gui/layout_flow_internal.h +31 -0
  121. package/runtime/ext_gui/layout_grid.cpp +374 -0
  122. package/runtime/ext_gui/media.h +1 -1
  123. package/runtime/ext_gui/media_api.h +2 -2
  124. package/runtime/ext_gui/media_query.cpp +211 -0
  125. package/runtime/ext_gui/paint.cpp +111 -77
  126. package/runtime/ext_gui/paint.h +33 -3
  127. package/runtime/ext_gui/paint_background.cpp +229 -0
  128. package/runtime/ext_gui/paint_clip.cpp +268 -0
  129. package/runtime/ext_gui/paint_effects.cpp +324 -0
  130. package/runtime/ext_gui/paint_internal.h +85 -0
  131. package/runtime/ext_gui/renderer.cpp +8 -31
  132. package/runtime/ext_gui/renderer_clip.cpp +196 -0
  133. package/runtime/ext_gui/renderer_image.cpp +13 -8
  134. package/runtime/ext_gui/renderer_internal.h +15 -8
  135. package/runtime/ext_gui/renderer_text.cpp +6 -31
  136. package/runtime/ext_gui/style.cpp +343 -20
  137. package/runtime/ext_gui/style.h +144 -3
  138. package/runtime/ext_gui/style_print.cpp +134 -1
  139. package/runtime/ext_gui/style_values.cpp +152 -126
  140. package/runtime/ext_gui/style_values_internal.h +35 -0
  141. package/runtime/ext_gui/webgl.h +466 -0
  142. package/runtime/ext_gui/webgl2_constants.h +274 -0
  143. package/runtime/ext_gui/webgl_buffer.cpp +565 -0
  144. package/runtime/ext_gui/webgl_constants.h +317 -0
  145. package/runtime/ext_gui/webgl_context.cpp +466 -0
  146. package/runtime/ext_gui/webgl_draw.cpp +495 -0
  147. package/runtime/ext_gui/webgl_glsl.h +125 -0
  148. package/runtime/ext_gui/webgl_glsl_compile.cpp +431 -0
  149. package/runtime/ext_gui/webgl_glsl_rewrite.cpp +349 -0
  150. package/runtime/ext_gui/webgl_glsl_scan.cpp +581 -0
  151. package/runtime/ext_gui/webgl_msl.cpp +62 -0
  152. package/runtime/ext_gui/webgl_msl.h +33 -0
  153. package/runtime/ext_gui/webgl_pipeline.cpp +274 -0
  154. package/runtime/ext_gui/webgl_program_internal.h +18 -0
  155. package/runtime/ext_gui/webgl_shader.cpp +582 -0
  156. package/runtime/ext_gui/webgl_texture.cpp +293 -0
  157. package/runtime/ext_gui/webgl_uniform.cpp +167 -0
  158. package/runtime/ext_gui/webgl_webgl2.cpp +224 -0
  159. package/runtime/ext_media/media.cpp +1 -1
  160. package/runtime/ext_media/media_bridge.c +9 -3
  161. package/runtime/rt_internal.h +1 -0
  162. package/runtime/xt_containers/access.inc +8 -0
  163. package/runtime/xt_regex/api.inc +2 -0
  164. package/runtime/xt_regex/matcher.inc +16 -3
  165. package/runtime/xt_regex/parser.inc +8 -2
  166. package/runtime/xt_regex/types.inc +3 -0
  167. package/runtime/xt_stdlib2/regexp-match.inc +60 -21
  168. package/runtime/xt_stdlib2/regexp.inc +1 -0
  169. package/runtime/xt_typed_array.c +1 -1
  170. package/scripts/build-gui.ts +90 -3
  171. package/scripts/build-media.ts +323 -58
  172. package/scripts/setup-vs-toolchain.ps1 +152 -0
  173. package/src/binder/references.ts +13 -1
  174. package/src/codegen/generator/module.ts +50 -0
  175. package/src/codegen/generator/statements/variables.ts +6 -3
  176. package/src/driver/version.ts +1 -1
  177. package/src/extensions/gui/css.ts +113 -0
  178. package/src/extensions/gui/html.ts +9 -2
  179. package/src/extensions/gui/index.ts +16 -2
  180. package/src/extensions/gui/three.ts +25 -0
  181. package/src/extensions/media/index.ts +13 -16
@@ -0,0 +1,582 @@
1
+ /*
2
+ * xbintsc GUI engine — WebGL shaders, programs, linking and uniforms.
3
+ *
4
+ * `compileShader` performs a standalone translation so `COMPILE_STATUS` and the
5
+ * info log work before linking. `linkProgram` then translates both stages again
6
+ * with one shared attribute/varying location map, compiles them with glslang
7
+ * and reflects the std140 block out of the SPIR-V. The SDL_GPU shader then
8
+ * takes whichever form the device accepts: the SPIR-V module itself on Vulkan,
9
+ * or the same module cross-compiled to MSL by SPIRV-Cross on Metal.
10
+ */
11
+
12
+ #include "webgl.h"
13
+
14
+ #include <algorithm>
15
+ #include <cctype>
16
+ #include <cstring>
17
+
18
+ #include "gui_internal.h"
19
+ #include "webgl_msl.h"
20
+ #include "webgl_program_internal.h"
21
+
22
+ namespace xtgui {
23
+ namespace {
24
+
25
+ int gl_type_of(const std::string &type) {
26
+ if (type == "float") return GL_FLOAT;
27
+ if (type == "vec2") return GL_FLOAT_VEC2;
28
+ if (type == "vec3") return GL_FLOAT_VEC3;
29
+ if (type == "vec4") return GL_FLOAT_VEC4;
30
+ if (type == "int" || type == "uint") return GL_INT;
31
+ if (type == "ivec2" || type == "uvec2") return GL_INT_VEC2;
32
+ if (type == "ivec3" || type == "uvec3") return GL_INT_VEC3;
33
+ if (type == "ivec4" || type == "uvec4") return GL_INT_VEC4;
34
+ if (type == "bool") return GL_BOOL;
35
+ if (type == "bvec2") return GL_BOOL_VEC2;
36
+ if (type == "bvec3") return GL_BOOL_VEC3;
37
+ if (type == "bvec4") return GL_BOOL_VEC4;
38
+ if (type == "mat2") return GL_FLOAT_MAT2;
39
+ if (type == "mat3") return GL_FLOAT_MAT3;
40
+ if (type == "mat4") return GL_FLOAT_MAT4;
41
+ if (type == "sampler2D") return GL_SAMPLER_2D;
42
+ if (type == "samplerCube") return GL_SAMPLER_CUBE;
43
+ return 0;
44
+ }
45
+
46
+ /** How many consecutive attribute/varying locations a type consumes. */
47
+ int location_slots(const std::string &type) {
48
+ if (type == "mat2") return 2;
49
+ if (type == "mat3") return 3;
50
+ if (type == "mat4") return 4;
51
+ return 1;
52
+ }
53
+
54
+ /** Merge uniforms declared by both stages, in declaration order. */
55
+ bool merge_uniforms(const GlslShaderInfo &vs, const GlslShaderInfo &fs,
56
+ std::vector<GlslUniformDecl> *out, std::string *error) {
57
+ for (const GlslShaderInfo *info : {&vs, &fs}) {
58
+ for (const GlslUniformDecl &decl : info->uniforms) {
59
+ auto found = std::find_if(out->begin(), out->end(),
60
+ [&](const GlslUniformDecl &existing) {
61
+ return existing.name == decl.name;
62
+ });
63
+ if (found == out->end()) {
64
+ out->push_back(decl);
65
+ } else if (found->type != decl.type) {
66
+ *error = "uniform '" + decl.name + "' is declared with two different types";
67
+ return false;
68
+ }
69
+ }
70
+ }
71
+ return true;
72
+ }
73
+
74
+ void assign_locations(const std::vector<GlslUniformDecl> &decls,
75
+ const std::unordered_map<std::string, int> &overrides,
76
+ std::unordered_map<std::string, int> *out) {
77
+ int next = 0;
78
+ for (const GlslUniformDecl &decl : decls) {
79
+ auto override_it = overrides.find(decl.name);
80
+ if (override_it != overrides.end()) {
81
+ (*out)[decl.name] = override_it->second;
82
+ next = std::max(next, override_it->second + location_slots(decl.type));
83
+ continue;
84
+ }
85
+ while (true) {
86
+ bool used = false;
87
+ for (const auto &entry : *out) {
88
+ const int first = entry.second;
89
+ if (next >= first && next < first + location_slots(decl.type)) used = true;
90
+ }
91
+ if (!used) break;
92
+ next++;
93
+ }
94
+ (*out)[decl.name] = next;
95
+ next += location_slots(decl.type);
96
+ }
97
+ }
98
+
99
+ /** One SDL_GPU shader stage in the format the device accepts. */
100
+ struct StageCode {
101
+ SDL_GPUShaderFormat format = SDL_GPU_SHADERFORMAT_SPIRV;
102
+ const Uint8 *code = nullptr;
103
+ size_t code_size = 0;
104
+ std::string entry = "main";
105
+ /** Owns the text when the backend consumes MSL rather than a binary. */
106
+ std::string text;
107
+ };
108
+
109
+ /**
110
+ * Turn a compiled SPIR-V stage into the form the device consumes.
111
+ *
112
+ * Metal has no SPIR-V loader, so the module is cross-compiled to MSL; Vulkan
113
+ * takes the SPIR-V directly. `code` points into `text` for MSL, so the caller
114
+ * must keep the `StageCode` alive until `SDL_CreateGPUShader` has run.
115
+ */
116
+ bool prepare_stage(const std::vector<uint32_t> &spirv, bool fragment,
117
+ SDL_GPUShaderFormat formats, StageCode *stage, std::string *log) {
118
+ if ((formats & SDL_GPU_SHADERFORMAT_MSL) != 0) {
119
+ if (!cross_compile_msl(spirv, fragment, &stage->text, &stage->entry, log)) return false;
120
+ stage->format = SDL_GPU_SHADERFORMAT_MSL;
121
+ stage->code = (const Uint8 *)stage->text.data();
122
+ stage->code_size = stage->text.size();
123
+ return true;
124
+ }
125
+ if ((formats & SDL_GPU_SHADERFORMAT_SPIRV) != 0) {
126
+ stage->format = SDL_GPU_SHADERFORMAT_SPIRV;
127
+ stage->code = (const Uint8 *)spirv.data();
128
+ stage->code_size = spirv.size() * sizeof(uint32_t);
129
+ stage->entry = "main";
130
+ return true;
131
+ }
132
+ *log = "this GPU backend consumes neither Metal nor Vulkan shaders";
133
+ return false;
134
+ }
135
+
136
+ } // namespace
137
+
138
+ /* -- shaders ------------------------------------------------------------- */
139
+
140
+ uint32_t WebGLContext::create_shader(int type) {
141
+ if (type != GL_VERTEX_SHADER && type != GL_FRAGMENT_SHADER) {
142
+ set_error(GL_INVALID_ENUM);
143
+ return 0;
144
+ }
145
+ auto record = std::make_unique<WebGLShader>();
146
+ record->id = next_object_id_++;
147
+ record->type = type;
148
+ const uint32_t id = record->id;
149
+ shaders_[id] = std::move(record);
150
+ return id;
151
+ }
152
+
153
+ void WebGLContext::delete_shader(uint32_t id) {
154
+ auto found = shaders_.find(id);
155
+ if (found == shaders_.end()) return;
156
+ if (found->second->attachments > 0) {
157
+ found->second->delete_requested = true;
158
+ return;
159
+ }
160
+ shaders_.erase(found);
161
+ }
162
+
163
+ void WebGLContext::shader_source(uint32_t id, const std::string &source) {
164
+ WebGLShader *record = shader(id);
165
+ if (record == nullptr) return;
166
+ record->source = source;
167
+ record->compiled = false;
168
+ record->log.clear();
169
+ }
170
+
171
+ bool WebGLContext::compile_shader(uint32_t id) {
172
+ WebGLShader *record = shader(id);
173
+ if (record == nullptr) return false;
174
+ const bool fragment = record->type == GL_FRAGMENT_SHADER;
175
+ GlslShaderInfo info;
176
+ scan_glsl(record->source, fragment, &info);
177
+ if (!info.unsupported.empty()) {
178
+ record->log = info.unsupported;
179
+ record->compiled = false;
180
+ return false;
181
+ }
182
+ GlslLinkLayout layout;
183
+ assign_locations(info.attributes, {}, &layout.attributes);
184
+ assign_locations(info.varyings, {}, &layout.varyings);
185
+ assign_locations(info.samplers, {}, &layout.samplers);
186
+ layout.block_members = info.uniforms;
187
+ const std::string source = rewrite_glsl(record->source, fragment, layout, info);
188
+ std::vector<uint32_t> spirv;
189
+ record->compiled = compile_glsl(source, fragment, &spirv, &record->log);
190
+ return record->compiled;
191
+ }
192
+
193
+ std::string WebGLContext::shader_info_log(uint32_t id) {
194
+ WebGLShader *record = shader(id);
195
+ return record != nullptr ? record->log : std::string();
196
+ }
197
+
198
+ bool WebGLContext::shader_compiled(uint32_t id) {
199
+ WebGLShader *record = shader(id);
200
+ return record != nullptr && record->compiled;
201
+ }
202
+
203
+ std::string WebGLContext::shader_source_text(uint32_t id) {
204
+ WebGLShader *record = shader(id);
205
+ return record != nullptr ? record->source : std::string();
206
+ }
207
+
208
+ /* -- programs ------------------------------------------------------------ */
209
+
210
+ uint32_t WebGLContext::create_program() {
211
+ auto record = std::make_unique<WebGLProgram>();
212
+ record->id = next_object_id_++;
213
+ const uint32_t id = record->id;
214
+ programs_[id] = std::move(record);
215
+ return id;
216
+ }
217
+
218
+ void WebGLContext::release_program_pipelines(WebGLProgram *program) {
219
+ if (program == nullptr || guidev::g_device == nullptr) return;
220
+ for (SDL_GPUGraphicsPipeline *pipeline : program->pipelines) {
221
+ SDL_ReleaseGPUGraphicsPipeline(guidev::g_device, pipeline);
222
+ }
223
+ program->pipelines.clear();
224
+ program->pipeline_cache.clear();
225
+ if (program->vs_shader != nullptr) SDL_ReleaseGPUShader(guidev::g_device, program->vs_shader);
226
+ if (program->fs_shader != nullptr) SDL_ReleaseGPUShader(guidev::g_device, program->fs_shader);
227
+ program->vs_shader = nullptr;
228
+ program->fs_shader = nullptr;
229
+ }
230
+
231
+ void WebGLContext::delete_program(uint32_t id) {
232
+ auto found = programs_.find(id);
233
+ if (found == programs_.end()) return;
234
+ if (current_program_ == id) current_program_ = 0;
235
+ WebGLProgram *record = found->second.get();
236
+ if (record->vs != 0) detach_shader(id, record->vs);
237
+ if (record->fs != 0) detach_shader(id, record->fs);
238
+ if (guidev::g_device != nullptr) {
239
+ submit(); SDL_WaitForGPUIdle(guidev::g_device);
240
+ release_program_pipelines(record);
241
+ }
242
+ programs_.erase(found);
243
+ }
244
+
245
+ bool WebGLContext::attach_shader(uint32_t program_id, uint32_t shader_id) {
246
+ WebGLProgram *record = program(program_id);
247
+ WebGLShader *shader_record = shader(shader_id);
248
+ if (record == nullptr || shader_record == nullptr) {
249
+ set_error(GL_INVALID_OPERATION);
250
+ return false;
251
+ }
252
+ uint32_t *slot = shader_record->type == GL_VERTEX_SHADER ? &record->vs : &record->fs;
253
+ if (*slot == shader_id) return true;
254
+ if (shader_record->type != GL_VERTEX_SHADER && shader_record->type != GL_FRAGMENT_SHADER) {
255
+ return false;
256
+ }
257
+ *slot = shader_id;
258
+ shader_record->attachments++;
259
+ return true;
260
+ }
261
+
262
+ bool WebGLContext::detach_shader(uint32_t program_id, uint32_t shader_id) {
263
+ WebGLProgram *record = program(program_id);
264
+ WebGLShader *shader_record = shader(shader_id);
265
+ if (record == nullptr || shader_record == nullptr) return false;
266
+ if (record->vs == shader_id) record->vs = 0;
267
+ if (record->fs == shader_id) record->fs = 0;
268
+ if (shader_record->attachments > 0) shader_record->attachments--;
269
+ if (shader_record->attachments == 0 && shader_record->delete_requested) {
270
+ shaders_.erase(shader_id);
271
+ }
272
+ return true;
273
+ }
274
+
275
+ bool WebGLContext::link_program(uint32_t id) {
276
+ WebGLProgram *record = program(id);
277
+ if (record == nullptr || record->vs == 0 || record->fs == 0) {
278
+ if (record != nullptr) record->log = "link needs one vertex and one fragment shader";
279
+ set_error(GL_INVALID_OPERATION);
280
+ return false;
281
+ }
282
+ WebGLShader *vertex = shader(record->vs);
283
+ WebGLShader *fragment = shader(record->fs);
284
+ if (vertex == nullptr || fragment == nullptr || !vertex->compiled || !fragment->compiled) {
285
+ record->log = "link needs both shaders to compile successfully";
286
+ record->linked = false;
287
+ return false;
288
+ }
289
+
290
+ GlslShaderInfo vs_info;
291
+ GlslShaderInfo fs_info;
292
+ scan_glsl(vertex->source, false, &vs_info);
293
+ scan_glsl(fragment->source, true, &fs_info);
294
+ if (!vs_info.unsupported.empty() || !fs_info.unsupported.empty()) {
295
+ record->log = vs_info.unsupported.empty() ? fs_info.unsupported : vs_info.unsupported;
296
+ record->linked = false;
297
+ return false;
298
+ }
299
+
300
+ std::vector<GlslUniformDecl> block_members;
301
+ if (!merge_uniforms(vs_info, fs_info, &block_members, &record->log)) {
302
+ record->linked = false;
303
+ return false;
304
+ }
305
+
306
+ /* The merged block is emitted in both stages, so a struct declared only in
307
+ * one of them (three.js light structs are fragment-only) must be hoisted
308
+ * into the other too. Deduplicate by struct name. */
309
+ auto struct_name = [](const std::string &definition) {
310
+ size_t at = definition.find("struct");
311
+ if (at == std::string::npos) return std::string();
312
+ at += 6;
313
+ while (at < definition.size() && std::isspace((unsigned char)definition[at]) != 0) at++;
314
+ size_t end = at;
315
+ while (end < definition.size() &&
316
+ (std::isalnum((unsigned char)definition[end]) != 0 || definition[end] == '_')) {
317
+ end++;
318
+ }
319
+ return definition.substr(at, end - at);
320
+ };
321
+ std::vector<std::string> structs = vs_info.struct_definitions;
322
+ for (const std::string &definition : fs_info.struct_definitions) {
323
+ const std::string name = struct_name(definition);
324
+ bool present = false;
325
+ for (const std::string &existing : structs) {
326
+ if (!name.empty() && struct_name(existing) == name) {
327
+ present = true;
328
+ break;
329
+ }
330
+ }
331
+ if (!present) structs.push_back(definition);
332
+ }
333
+ vs_info.struct_definitions = structs;
334
+ fs_info.struct_definitions = structs;
335
+
336
+ GlslLinkLayout vs_layout;
337
+ GlslLinkLayout fs_layout;
338
+ vs_layout.block_members = block_members;
339
+ fs_layout.block_members = block_members;
340
+ assign_locations(vs_info.attributes, record->attrib_bindings, &vs_layout.attributes);
341
+ fs_layout.attributes = vs_layout.attributes;
342
+ assign_locations(vs_info.varyings, {}, &vs_layout.varyings);
343
+ fs_layout.varyings = vs_layout.varyings;
344
+ assign_locations(fs_info.samplers, {}, &fs_layout.samplers);
345
+ assign_locations(vs_info.samplers, {}, &vs_layout.samplers);
346
+
347
+ /* A fragment input with no vertex output is undefined in Vulkan, but a
348
+ * material may declare one conditionally and never read it (three.js does
349
+ * this for `vNormal` on unlit materials). Give it a location and let the
350
+ * compiler drop it rather than failing the whole program. */
351
+ for (const GlslUniformDecl &varying : fs_info.varyings) {
352
+ if (vs_layout.varyings.find(varying.name) == vs_layout.varyings.end()) {
353
+ int next = 0;
354
+ for (const auto &entry : vs_layout.varyings) {
355
+ if (entry.second >= next) next = entry.second + 1;
356
+ }
357
+ vs_layout.varyings[varying.name] = next;
358
+ }
359
+ }
360
+ fs_layout.varyings = vs_layout.varyings;
361
+
362
+ const std::string vs_source = rewrite_glsl(vertex->source, false, vs_layout, vs_info);
363
+ const std::string fs_source = rewrite_glsl(fragment->source, true, fs_layout, fs_info);
364
+ std::vector<uint32_t> vs_spirv;
365
+ std::vector<uint32_t> fs_spirv;
366
+ std::string log;
367
+ if (!compile_glsl(vs_source, false, &vs_spirv, &log)) {
368
+ record->log = "vertex shader:\n" + log;
369
+ record->linked = false;
370
+ return false;
371
+ }
372
+ if (!compile_glsl(fs_source, true, &fs_spirv, &log)) {
373
+ record->log = "fragment shader:\n" + log;
374
+ record->linked = false;
375
+ return false;
376
+ }
377
+
378
+ /* Reflect the std140 block (members survive even when unused). */
379
+ std::vector<GlslBlockMember> members;
380
+ reflect_uniform_block(vs_spirv, &members);
381
+ std::vector<GlslBlockMember> fragment_members;
382
+ reflect_uniform_block(fs_spirv, &fragment_members);
383
+ if (members.empty()) members = fragment_members;
384
+
385
+ record->uniforms.clear();
386
+ record->uniform_index.clear();
387
+ /* Only attributes that survived preprocessing and reached the SPIR-V are
388
+ * active. three.js emits many conditional `#ifdef USE_…` attribute
389
+ * declarations, so the source scan over-reports; use the reflection and keep
390
+ * the declaration order from the source for stable `getActiveAttrib` order. */
391
+ std::unordered_map<std::string, int> active_attributes;
392
+ reflect_attributes(vs_spirv, &active_attributes);
393
+ if (!active_attributes.empty()) {
394
+ std::vector<std::pair<int, GlslUniformDecl>> ordered;
395
+ for (const GlslUniformDecl &decl : vs_info.attributes) {
396
+ auto found = active_attributes.find(decl.name);
397
+ if (found != active_attributes.end()) ordered.push_back({found->second, decl});
398
+ }
399
+ std::sort(ordered.begin(), ordered.end(),
400
+ [](const std::pair<int, GlslUniformDecl> &a,
401
+ const std::pair<int, GlslUniformDecl> &b) { return a.first < b.first; });
402
+ record->attribs.clear();
403
+ for (const auto &entry : ordered) record->attribs.push_back(entry.second);
404
+ record->attrib_locations = active_attributes;
405
+ } else {
406
+ record->attribs = vs_info.attributes;
407
+ record->attrib_locations = vs_layout.attributes;
408
+ }
409
+ record->vs_sampler_uniforms.clear();
410
+ record->fs_sampler_uniforms.clear();
411
+
412
+ for (const GlslBlockMember &member : members) {
413
+ WebGLUniform uniform;
414
+ uniform.name = member.name;
415
+ uniform.layout = member;
416
+ record->uniform_index[member.name] = (int)record->uniforms.size();
417
+ record->uniforms.push_back(uniform);
418
+ }
419
+ int block_size = 0;
420
+ for (const WebGLUniform &uniform : record->uniforms) {
421
+ block_size = std::max(block_size, uniform.layout.offset + webgl_detail::member_bytes(uniform.layout));
422
+ }
423
+ record->block_size = (block_size + 15) & ~15;
424
+ record->block.assign(record->block_size, 0);
425
+ record->block_dirty = true;
426
+
427
+ auto add_sampler = [](WebGLProgram *program, std::vector<int> *stage,
428
+ const GlslUniformDecl &decl) {
429
+ int index = -1;
430
+ auto found = program->uniform_index.find(decl.name);
431
+ if (found != program->uniform_index.end()) {
432
+ index = found->second;
433
+ } else {
434
+ WebGLUniform uniform;
435
+ uniform.name = decl.name;
436
+ uniform.sampler = true;
437
+ uniform.unit = 0;
438
+ uniform.layout.gl_type = gl_type_of(decl.type);
439
+ index = (int)program->uniforms.size();
440
+ program->uniform_index[decl.name] = index;
441
+ program->uniforms.push_back(uniform);
442
+ }
443
+ stage->push_back(index);
444
+ };
445
+ for (const GlslUniformDecl &decl : vs_info.samplers) add_sampler(record, &record->vs_sampler_uniforms, decl);
446
+ for (const GlslUniformDecl &decl : fs_info.samplers) add_sampler(record, &record->fs_sampler_uniforms, decl);
447
+
448
+ /* (Re)create the SDL shaders. */
449
+ if (guidev::g_device == nullptr) {
450
+ record->log = "no GPU device";
451
+ record->linked = false;
452
+ return false;
453
+ }
454
+ submit(); SDL_WaitForGPUIdle(guidev::g_device);
455
+ release_program_pipelines(record);
456
+
457
+ const SDL_GPUShaderFormat formats = SDL_GetGPUShaderFormats(guidev::g_device);
458
+ StageCode vs_code;
459
+ StageCode fs_code;
460
+ if (!prepare_stage(vs_spirv, false, formats, &vs_code, &log)) {
461
+ record->log = "vertex shader:\n" + log;
462
+ record->linked = false;
463
+ return false;
464
+ }
465
+ if (!prepare_stage(fs_spirv, true, formats, &fs_code, &log)) {
466
+ record->log = "fragment shader:\n" + log;
467
+ record->linked = false;
468
+ return false;
469
+ }
470
+
471
+ SDL_GPUShaderCreateInfo vs_create;
472
+ memset(&vs_create, 0, sizeof(vs_create));
473
+ vs_create.code = vs_code.code;
474
+ vs_create.code_size = vs_code.code_size;
475
+ vs_create.entrypoint = vs_code.entry.c_str();
476
+ vs_create.format = vs_code.format;
477
+ vs_create.stage = SDL_GPU_SHADERSTAGE_VERTEX;
478
+ record->vs_has_block = !vs_info.uniforms.empty();
479
+ vs_create.num_uniform_buffers = record->vs_has_block ? 1 : 0;
480
+ vs_create.num_samplers = (Uint32)record->vs_sampler_uniforms.size();
481
+ record->vs_shader = SDL_CreateGPUShader(guidev::g_device, &vs_create);
482
+
483
+ SDL_GPUShaderCreateInfo fs_create;
484
+ memset(&fs_create, 0, sizeof(fs_create));
485
+ fs_create.code = fs_code.code;
486
+ fs_create.code_size = fs_code.code_size;
487
+ fs_create.entrypoint = fs_code.entry.c_str();
488
+ fs_create.format = fs_code.format;
489
+ fs_create.stage = SDL_GPU_SHADERSTAGE_FRAGMENT;
490
+ record->fs_has_block = !fs_info.uniforms.empty();
491
+ fs_create.num_uniform_buffers = record->fs_has_block ? 1 : 0;
492
+ fs_create.num_samplers = (Uint32)record->fs_sampler_uniforms.size();
493
+ record->fs_shader = SDL_CreateGPUShader(guidev::g_device, &fs_create);
494
+ if (record->vs_shader == nullptr || record->fs_shader == nullptr) {
495
+ record->log = std::string("SDL_GPU shader creation failed: ") + SDL_GetError();
496
+ release_program_pipelines(record);
497
+ record->linked = false;
498
+ return false;
499
+ }
500
+ record->linked = true;
501
+ record->log.clear();
502
+ return true;
503
+ }
504
+
505
+ void WebGLContext::use_program(uint32_t id) {
506
+ if (id != 0 && (programs_.find(id) == programs_.end() || !programs_[id]->linked)) {
507
+ set_error(GL_INVALID_OPERATION);
508
+ return;
509
+ }
510
+ current_program_ = id;
511
+ }
512
+
513
+ bool WebGLContext::program_linked(uint32_t id) {
514
+ WebGLProgram *record = program(id);
515
+ return record != nullptr && record->linked;
516
+ }
517
+
518
+ std::string WebGLContext::program_info_log(uint32_t id) {
519
+ WebGLProgram *record = program(id);
520
+ return record != nullptr ? record->log : std::string();
521
+ }
522
+
523
+ std::vector<uint32_t> WebGLContext::attached_shaders(uint32_t id) {
524
+ std::vector<uint32_t> result;
525
+ WebGLProgram *record = program(id);
526
+ if (record == nullptr) return result;
527
+ if (record->vs != 0) result.push_back(record->vs);
528
+ if (record->fs != 0) result.push_back(record->fs);
529
+ return result;
530
+ }
531
+
532
+ int WebGLContext::active_uniform_count(uint32_t id) {
533
+ WebGLProgram *record = program(id);
534
+ return record != nullptr ? (int)record->uniforms.size() : 0;
535
+ }
536
+
537
+ int WebGLContext::active_attrib_count(uint32_t id) {
538
+ WebGLProgram *record = program(id);
539
+ return record != nullptr ? (int)record->attribs.size() : 0;
540
+ }
541
+
542
+ const WebGLUniform *WebGLContext::active_uniform(uint32_t id, int index) {
543
+ WebGLProgram *record = program(id);
544
+ if (record == nullptr || index < 0 || index >= (int)record->uniforms.size()) return nullptr;
545
+ return &record->uniforms[index];
546
+ }
547
+
548
+ const GlslUniformDecl *WebGLContext::active_attrib(uint32_t id, int index) {
549
+ WebGLProgram *record = program(id);
550
+ if (record == nullptr || index < 0 || index >= (int)record->attribs.size()) return nullptr;
551
+ return &record->attribs[index];
552
+ }
553
+
554
+ int WebGLContext::uniform_location(uint32_t id, const std::string &name) {
555
+ WebGLProgram *record = program(id);
556
+ if (record == nullptr) return -1;
557
+ auto found = record->uniform_index.find(name);
558
+ if (found != record->uniform_index.end()) return found->second;
559
+ const size_t bracket = name.find('[');
560
+ if (bracket != std::string::npos) {
561
+ found = record->uniform_index.find(name.substr(0, bracket));
562
+ if (found != record->uniform_index.end()) return found->second;
563
+ }
564
+ return -1;
565
+ }
566
+
567
+ int WebGLContext::attrib_location(uint32_t id, const std::string &name) {
568
+ WebGLProgram *record = program(id);
569
+ if (record == nullptr) return -1;
570
+ auto found = record->attrib_locations.find(name);
571
+ return found == record->attrib_locations.end() ? -1 : found->second;
572
+ }
573
+
574
+ void WebGLContext::bind_attrib_location(uint32_t id, int index, const std::string &name) {
575
+ WebGLProgram *record = program(id);
576
+ if (record == nullptr || index < 0 || index >= kWebGLMaxAttribs) return;
577
+ record->attrib_bindings[name] = index;
578
+ }
579
+
580
+ /* -- uniform uploads ----------------------------------------------------- */
581
+
582
+ } // namespace xtgui