xbintsc 0.3.15 → 0.3.18
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.
- package/dist/src/cli/main.js +4 -4
- package/dist/src/cli/main.js.map +1 -1
- package/dist/src/codegen/generator/state.js +8 -0
- package/dist/src/codegen/generator/state.js.map +1 -1
- package/dist/src/codegen/generator/tables.js +8 -0
- package/dist/src/codegen/generator/tables.js.map +1 -1
- package/dist/src/driver/bundler/graph.d.ts +8 -0
- package/dist/src/driver/bundler/graph.js +85 -0
- package/dist/src/driver/bundler/graph.js.map +1 -0
- package/dist/src/driver/bundler/merge.d.ts +11 -0
- package/dist/src/driver/bundler/merge.js +288 -0
- package/dist/src/driver/bundler/merge.js.map +1 -0
- package/dist/src/driver/bundler/resolve.d.ts +12 -0
- package/dist/src/driver/bundler/resolve.js +246 -0
- package/dist/src/driver/bundler/resolve.js.map +1 -0
- package/dist/src/driver/bundler/symbols.d.ts +15 -0
- package/dist/src/driver/bundler/symbols.js +51 -0
- package/dist/src/driver/bundler/symbols.js.map +1 -0
- package/dist/src/driver/bundler/types.d.ts +36 -0
- package/dist/src/driver/bundler/types.js +5 -0
- package/dist/src/driver/bundler/types.js.map +1 -0
- package/dist/src/driver/modules.d.ts +9 -16
- package/dist/src/driver/modules.js +8 -644
- package/dist/src/driver/modules.js.map +1 -1
- package/dist/src/extensions/catalog.js +2 -1
- package/dist/src/extensions/catalog.js.map +1 -1
- package/dist/src/extensions/gui/index.d.ts +24 -0
- package/dist/src/extensions/gui/index.js +101 -0
- package/dist/src/extensions/gui/index.js.map +1 -0
- package/dist/tests/e2e/compile-basics.test.d.ts +4 -0
- package/dist/tests/e2e/compile-basics.test.js +134 -0
- package/dist/tests/e2e/compile-basics.test.js.map +1 -0
- package/dist/tests/e2e/compile-builtins.test.d.ts +4 -0
- package/dist/tests/e2e/compile-builtins.test.js +191 -0
- package/dist/tests/e2e/compile-builtins.test.js.map +1 -0
- package/dist/tests/e2e/compile-control-flow.test.d.ts +4 -0
- package/dist/tests/e2e/compile-control-flow.test.js +190 -0
- package/dist/tests/e2e/compile-control-flow.test.js.map +1 -0
- package/dist/tests/e2e/compile-modules.test.d.ts +4 -0
- package/dist/tests/e2e/compile-modules.test.js +48 -0
- package/dist/tests/e2e/compile-modules.test.js.map +1 -0
- package/dist/tests/e2e/compile-objects-classes.test.d.ts +4 -0
- package/dist/tests/e2e/compile-objects-classes.test.js +362 -0
- package/dist/tests/e2e/compile-objects-classes.test.js.map +1 -0
- package/dist/tests/e2e/differential-helpers.d.ts +12 -0
- package/dist/tests/e2e/differential-helpers.js +16 -0
- package/dist/tests/e2e/differential-helpers.js.map +1 -0
- package/dist/tests/e2e/differential-language.test.d.ts +4 -0
- package/dist/tests/e2e/{differential.test.js → differential-language.test.js} +5 -287
- package/dist/tests/e2e/differential-language.test.js.map +1 -0
- package/dist/tests/e2e/differential-symbols.test.d.ts +4 -0
- package/dist/tests/e2e/differential-symbols.test.js +92 -0
- package/dist/tests/e2e/differential-symbols.test.js.map +1 -0
- package/dist/tests/e2e/differential-values.test.d.ts +4 -0
- package/dist/tests/e2e/differential-values.test.js +197 -0
- package/dist/tests/e2e/differential-values.test.js.map +1 -0
- package/dist/tests/e2e/gui.test.d.ts +12 -0
- package/dist/tests/e2e/gui.test.js +493 -0
- package/dist/tests/e2e/gui.test.js.map +1 -0
- package/dist/tests/e2e/runtime-coverage-buffer.test.d.ts +4 -0
- package/dist/tests/e2e/runtime-coverage-buffer.test.js +97 -0
- package/dist/tests/e2e/runtime-coverage-buffer.test.js.map +1 -0
- package/dist/tests/e2e/runtime-coverage-globals.test.d.ts +4 -0
- package/dist/tests/e2e/runtime-coverage-globals.test.js +149 -0
- package/dist/tests/e2e/runtime-coverage-globals.test.js.map +1 -0
- package/dist/tests/e2e/runtime-coverage-node-modules.test.d.ts +4 -0
- package/dist/tests/e2e/runtime-coverage-node-modules.test.js +177 -0
- package/dist/tests/e2e/runtime-coverage-node-modules.test.js.map +1 -0
- package/dist/tests/e2e/runtime-coverage-streams.test.d.ts +4 -0
- package/dist/tests/e2e/runtime-coverage-streams.test.js +161 -0
- package/dist/tests/e2e/runtime-coverage-streams.test.js.map +1 -0
- package/dist/tests/e2e/runtime-coverage-system.test.d.ts +4 -0
- package/dist/tests/e2e/runtime-coverage-system.test.js +91 -0
- package/dist/tests/e2e/runtime-coverage-system.test.js.map +1 -0
- package/dist/tests/e2e/timers.test.d.ts +7 -0
- package/dist/tests/e2e/timers.test.js +63 -0
- package/dist/tests/e2e/timers.test.js.map +1 -0
- package/package.json +2 -1
- package/runtime/ext_gui/css.cpp +743 -0
- package/runtime/ext_gui/css.h +98 -0
- package/runtime/ext_gui/document.cpp +264 -0
- package/runtime/ext_gui/document.h +89 -0
- package/runtime/ext_gui/dom.cpp +472 -0
- package/runtime/ext_gui/dom.h +71 -0
- package/runtime/ext_gui/gui.cpp +457 -0
- package/runtime/ext_gui/gui_engine.h +81 -0
- package/runtime/ext_gui/image.cpp +125 -0
- package/runtime/ext_gui/image.h +42 -0
- package/runtime/ext_gui/layout.cpp +748 -0
- package/runtime/ext_gui/layout.h +93 -0
- package/runtime/ext_gui/paint.cpp +137 -0
- package/runtime/ext_gui/paint.h +75 -0
- package/runtime/ext_gui/renderer.cpp +959 -0
- package/runtime/ext_gui/renderer.h +68 -0
- package/runtime/ext_gui/stb_image.h +7988 -0
- package/runtime/ext_gui/style.cpp +771 -0
- package/runtime/ext_gui/style.h +117 -0
- package/runtime/ext_gui/text.cpp +378 -0
- package/runtime/ext_gui/text.h +78 -0
- package/runtime/ext_gui/window.cpp +405 -0
- package/runtime/ext_node/fs/fs_common.h +13 -527
- package/runtime/ext_node/fs/parts/buffer-bridge.inc +6 -0
- package/runtime/ext_node/fs/parts/builtins.inc +69 -0
- package/runtime/ext_node/fs/parts/dir.inc +47 -0
- package/runtime/ext_node/fs/parts/encoding.inc +186 -0
- package/runtime/ext_node/fs/parts/errors.inc +66 -0
- package/runtime/ext_node/fs/parts/flags.inc +39 -0
- package/runtime/ext_node/fs/parts/platform.inc +86 -0
- package/runtime/ext_node/fs/parts/promisify.inc +15 -0
- package/runtime/ext_node/fs/parts/time.inc +9 -0
- package/runtime/rt.h +33 -0
- package/runtime/xt_builtins.c +4 -0
- package/runtime/xt_containers/access.inc +168 -0
- package/runtime/xt_containers/arrays.inc +108 -0
- package/runtime/xt_containers/boxes.inc +11 -0
- package/runtime/xt_containers/objects.inc +326 -0
- package/runtime/xt_containers.c +8 -618
- package/runtime/xt_io/base64.inc +87 -0
- package/runtime/xt_io/console.inc +119 -0
- package/runtime/xt_io/exception.inc +142 -0
- package/runtime/xt_io/inspect.inc +212 -0
- package/runtime/xt_io.c +8 -553
- package/runtime/xt_loop.c +262 -24
- package/runtime/xt_regex/api.inc +58 -0
- package/runtime/xt_regex/helpers.inc +68 -0
- package/runtime/xt_regex/matcher.inc +248 -0
- package/runtime/xt_regex/parser.inc +236 -0
- package/runtime/xt_regex/types.inc +58 -0
- package/runtime/xt_regex.h +9 -672
- package/runtime/xt_values/arithmetic.inc +150 -0
- package/runtime/xt_values/comparison.inc +112 -0
- package/runtime/xt_values/constructors.inc +41 -0
- package/runtime/xt_values/conversions.inc +115 -0
- package/runtime/xt_values/number-format.inc +180 -0
- package/runtime/xt_values/strings.inc +22 -0
- package/runtime/xt_values.c +10 -626
- package/scripts/build-gui.ts +317 -0
- package/src/cli/main.ts +4 -3
- package/src/codegen/generator/state.ts +8 -0
- package/src/codegen/generator/tables.ts +8 -0
- package/src/driver/bundler/graph.ts +95 -0
- package/src/driver/bundler/merge.ts +312 -0
- package/src/driver/bundler/resolve.ts +240 -0
- package/src/driver/bundler/symbols.ts +55 -0
- package/src/driver/bundler/types.ts +37 -0
- package/src/driver/modules.ts +9 -693
- package/src/extensions/catalog.ts +2 -1
- package/src/extensions/gui/index.ts +107 -0
- package/dist/tests/e2e/compile.test.d.ts +0 -8
- package/dist/tests/e2e/compile.test.js +0 -893
- package/dist/tests/e2e/compile.test.js.map +0 -1
- package/dist/tests/e2e/differential.test.d.ts +0 -7
- package/dist/tests/e2e/differential.test.js.map +0 -1
- package/dist/tests/e2e/runtime-coverage.test.d.ts +0 -11
- package/dist/tests/e2e/runtime-coverage.test.js +0 -646
- package/dist/tests/e2e/runtime-coverage.test.js.map +0 -1
|
@@ -0,0 +1,959 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* xbintsc GUI engine — SDL_GPU 2D renderer implementation.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
#include "renderer.h"
|
|
6
|
+
|
|
7
|
+
#include <algorithm>
|
|
8
|
+
#include <cstddef>
|
|
9
|
+
#include <cstdint>
|
|
10
|
+
#include <cstdio>
|
|
11
|
+
#include <cstring>
|
|
12
|
+
#include <functional>
|
|
13
|
+
#include <unordered_map>
|
|
14
|
+
#include <vector>
|
|
15
|
+
|
|
16
|
+
#include "paint.h"
|
|
17
|
+
#include "image.h"
|
|
18
|
+
#include "text.h"
|
|
19
|
+
|
|
20
|
+
namespace {
|
|
21
|
+
|
|
22
|
+
/* Rounded-rectangle fill. Vertex attributes (matching SDL_GPUVertexAttribute):
|
|
23
|
+
* 0 -> position (pixels, viewport-relative)
|
|
24
|
+
* 1 -> local (pixels, relative to the rectangle centre)
|
|
25
|
+
* 2 -> half (rectangle half extents, pixels)
|
|
26
|
+
* 3 -> radius (pixels)
|
|
27
|
+
* 4 -> colour
|
|
28
|
+
* The push constant is the viewport size in logical pixels. */
|
|
29
|
+
const char *const kFillMSL = R"MSL(
|
|
30
|
+
#include <metal_stdlib>
|
|
31
|
+
using namespace metal;
|
|
32
|
+
|
|
33
|
+
struct FillIn {
|
|
34
|
+
float2 position [[attribute(0)]];
|
|
35
|
+
float2 local [[attribute(1)]];
|
|
36
|
+
float2 halfsize [[attribute(2)]];
|
|
37
|
+
float radius [[attribute(3)]];
|
|
38
|
+
float4 color [[attribute(4)]];
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
struct FillOut {
|
|
42
|
+
float4 position [[position]];
|
|
43
|
+
float2 local;
|
|
44
|
+
float2 halfsize;
|
|
45
|
+
float radius;
|
|
46
|
+
float4 color;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
vertex FillOut vs_main(FillIn in [[stage_in]],
|
|
50
|
+
constant float2 &viewport [[buffer(0)]]) {
|
|
51
|
+
FillOut out;
|
|
52
|
+
float2 ndc = float2(in.position.x / viewport.x * 2.0 - 1.0,
|
|
53
|
+
1.0 - in.position.y / viewport.y * 2.0);
|
|
54
|
+
out.position = float4(ndc, 0.0, 1.0);
|
|
55
|
+
out.local = in.local;
|
|
56
|
+
out.halfsize = in.halfsize;
|
|
57
|
+
out.radius = in.radius;
|
|
58
|
+
out.color = in.color;
|
|
59
|
+
return out;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
fragment float4 fs_main(FillOut in [[stage_in]]) {
|
|
63
|
+
float2 d = abs(in.local) - in.halfsize + in.radius;
|
|
64
|
+
float dist = min(max(d.x, d.y), 0.0) + length(max(d, 0.0)) - in.radius;
|
|
65
|
+
float aa = max(fwidth(dist), 0.0001);
|
|
66
|
+
float alpha = clamp(0.5 - dist / aa, 0.0, 1.0);
|
|
67
|
+
return float4(in.color.rgb, in.color.a * alpha);
|
|
68
|
+
}
|
|
69
|
+
)MSL";
|
|
70
|
+
|
|
71
|
+
/* Glyph quads. Attributes:
|
|
72
|
+
* 0 -> position (pixels, viewport-relative)
|
|
73
|
+
* 1 -> uv (atlas coordinates in [0,1])
|
|
74
|
+
* 2 -> colour
|
|
75
|
+
* The fragment shader samples the single-channel atlas and modulates alpha. */
|
|
76
|
+
const char *const kTextMSL = R"MSL(
|
|
77
|
+
#include <metal_stdlib>
|
|
78
|
+
using namespace metal;
|
|
79
|
+
|
|
80
|
+
struct TextIn {
|
|
81
|
+
float2 position [[attribute(0)]];
|
|
82
|
+
float2 uv [[attribute(1)]];
|
|
83
|
+
float4 color [[attribute(2)]];
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
struct TextOut {
|
|
87
|
+
float4 position [[position]];
|
|
88
|
+
float2 uv;
|
|
89
|
+
float4 color;
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
vertex TextOut vs_text(TextIn in [[stage_in]],
|
|
93
|
+
constant float2 &viewport [[buffer(0)]]) {
|
|
94
|
+
TextOut out;
|
|
95
|
+
float2 ndc = float2(in.position.x / viewport.x * 2.0 - 1.0,
|
|
96
|
+
1.0 - in.position.y / viewport.y * 2.0);
|
|
97
|
+
out.position = float4(ndc, 0.0, 1.0);
|
|
98
|
+
out.uv = in.uv;
|
|
99
|
+
out.color = in.color;
|
|
100
|
+
return out;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
fragment float4 fs_text(TextOut in [[stage_in]],
|
|
104
|
+
texture2d<float> atlas [[texture(0)]],
|
|
105
|
+
sampler samp [[sampler(0)]]) {
|
|
106
|
+
float coverage = atlas.sample(samp, in.uv).r;
|
|
107
|
+
return float4(in.color.rgb, in.color.a * coverage);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/* Image quads reuse the text vertex layout but sample an RGBA texture. */
|
|
111
|
+
fragment float4 fs_image(TextOut in [[stage_in]],
|
|
112
|
+
texture2d<float> tex [[texture(0)]],
|
|
113
|
+
sampler samp [[sampler(0)]]) {
|
|
114
|
+
float4 c = tex.sample(samp, in.uv);
|
|
115
|
+
return float4(c.rgb * in.color.rgb, c.a * in.color.a);
|
|
116
|
+
}
|
|
117
|
+
)MSL";
|
|
118
|
+
|
|
119
|
+
struct Vertex {
|
|
120
|
+
float x, y;
|
|
121
|
+
float lx, ly;
|
|
122
|
+
float hx, hy;
|
|
123
|
+
float radius;
|
|
124
|
+
float r, g, b, a;
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
struct TextVertex {
|
|
128
|
+
float x, y;
|
|
129
|
+
float u, v;
|
|
130
|
+
float r, g, b, a;
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
SDL_GPUGraphicsPipeline *g_pipeline = nullptr;
|
|
134
|
+
SDL_GPUGraphicsPipeline *g_text_pipeline = nullptr;
|
|
135
|
+
SDL_GPUGraphicsPipeline *g_image_pipeline = nullptr;
|
|
136
|
+
SDL_GPUSampler *g_sampler = nullptr;
|
|
137
|
+
int g_renderer_failed = 0;
|
|
138
|
+
bool g_text_ok = false;
|
|
139
|
+
|
|
140
|
+
/* Image quads are batched by texture; textures are cached by source path. */
|
|
141
|
+
struct ImageBatch {
|
|
142
|
+
SDL_GPUTexture *texture = nullptr;
|
|
143
|
+
uint32_t first = 0;
|
|
144
|
+
uint32_t count = 0;
|
|
145
|
+
};
|
|
146
|
+
std::vector<ImageBatch> g_image_batches;
|
|
147
|
+
std::unordered_map<std::string, SDL_GPUTexture *> g_image_textures;
|
|
148
|
+
|
|
149
|
+
/* -- glyph atlas ---------------------------------------------------------- */
|
|
150
|
+
|
|
151
|
+
constexpr int kAtlasSize = 2048;
|
|
152
|
+
|
|
153
|
+
struct GlyphEntry {
|
|
154
|
+
float u0 = 0, v0 = 0, u1 = 0, v1 = 0;
|
|
155
|
+
int width = 0, height = 0, left = 0, top = 0;
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
struct GlyphKey {
|
|
159
|
+
const void *font = nullptr;
|
|
160
|
+
uint32_t glyph = 0;
|
|
161
|
+
bool operator==(const GlyphKey &other) const {
|
|
162
|
+
return font == other.font && glyph == other.glyph;
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
struct GlyphKeyHash {
|
|
167
|
+
size_t operator()(const GlyphKey &key) const {
|
|
168
|
+
return std::hash<const void *>()(key.font) * 1099511628211ull ^
|
|
169
|
+
(size_t)(key.glyph * 2654435761u);
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
SDL_GPUTexture *g_atlas_texture = nullptr;
|
|
174
|
+
std::vector<unsigned char> g_atlas_mirror;
|
|
175
|
+
std::unordered_map<GlyphKey, GlyphEntry, GlyphKeyHash> g_glyphs;
|
|
176
|
+
int g_atlas_x = 0;
|
|
177
|
+
int g_atlas_y = 0;
|
|
178
|
+
int g_atlas_row_height = 0;
|
|
179
|
+
bool g_atlas_dirty = false;
|
|
180
|
+
int g_atlas_dirty_x0 = 0, g_atlas_dirty_y0 = 0, g_atlas_dirty_x1 = 0, g_atlas_dirty_y1 = 0;
|
|
181
|
+
bool g_atlas_full_logged = false;
|
|
182
|
+
|
|
183
|
+
void pushRect(std::vector<Vertex> &verts, const xtgui::PaintRect &rect) {
|
|
184
|
+
float hw = rect.width * 0.5f;
|
|
185
|
+
float hh = rect.height * 0.5f;
|
|
186
|
+
float cx = rect.x + hw;
|
|
187
|
+
float cy = rect.y + hh;
|
|
188
|
+
float radius = std::min(rect.radius, std::min(hw, hh));
|
|
189
|
+
|
|
190
|
+
const float local_x[4] = {-hw, hw, hw, -hw};
|
|
191
|
+
const float local_y[4] = {-hh, -hh, hh, hh};
|
|
192
|
+
Vertex corners[4];
|
|
193
|
+
for (int i = 0; i < 4; i++) {
|
|
194
|
+
corners[i].x = cx + local_x[i];
|
|
195
|
+
corners[i].y = cy + local_y[i];
|
|
196
|
+
corners[i].lx = local_x[i];
|
|
197
|
+
corners[i].ly = local_y[i];
|
|
198
|
+
corners[i].hx = hw;
|
|
199
|
+
corners[i].hy = hh;
|
|
200
|
+
corners[i].radius = radius;
|
|
201
|
+
corners[i].r = rect.color.r;
|
|
202
|
+
corners[i].g = rect.color.g;
|
|
203
|
+
corners[i].b = rect.color.b;
|
|
204
|
+
corners[i].a = rect.color.a;
|
|
205
|
+
}
|
|
206
|
+
static const int order[6] = {0, 1, 2, 0, 2, 3};
|
|
207
|
+
for (int i = 0; i < 6; i++) verts.push_back(corners[order[i]]);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
void pushGlyph(std::vector<TextVertex> &verts, float x, float y, float width, float height,
|
|
211
|
+
const GlyphEntry &entry, const xtgui::Color &color) {
|
|
212
|
+
const float corners_x[4] = {x, x + width, x + width, x};
|
|
213
|
+
const float corners_y[4] = {y, y, y + height, y + height};
|
|
214
|
+
const float u[4] = {entry.u0, entry.u1, entry.u1, entry.u0};
|
|
215
|
+
const float v[4] = {entry.v0, entry.v0, entry.v1, entry.v1};
|
|
216
|
+
TextVertex corners[4];
|
|
217
|
+
for (int i = 0; i < 4; i++) {
|
|
218
|
+
corners[i].x = corners_x[i];
|
|
219
|
+
corners[i].y = corners_y[i];
|
|
220
|
+
corners[i].u = u[i];
|
|
221
|
+
corners[i].v = v[i];
|
|
222
|
+
corners[i].r = color.r;
|
|
223
|
+
corners[i].g = color.g;
|
|
224
|
+
corners[i].b = color.b;
|
|
225
|
+
corners[i].a = color.a;
|
|
226
|
+
}
|
|
227
|
+
static const int order[6] = {0, 1, 2, 0, 2, 3};
|
|
228
|
+
for (int i = 0; i < 6; i++) verts.push_back(corners[order[i]]);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
bool ensureAtlasTexture(SDL_GPUDevice *device) {
|
|
232
|
+
if (g_atlas_texture != nullptr) return true;
|
|
233
|
+
SDL_GPUTextureCreateInfo info;
|
|
234
|
+
memset(&info, 0, sizeof(info));
|
|
235
|
+
info.type = SDL_GPU_TEXTURETYPE_2D;
|
|
236
|
+
info.format = SDL_GPU_TEXTUREFORMAT_R8_UNORM;
|
|
237
|
+
info.usage = SDL_GPU_TEXTUREUSAGE_SAMPLER;
|
|
238
|
+
info.width = kAtlasSize;
|
|
239
|
+
info.height = kAtlasSize;
|
|
240
|
+
info.layer_count_or_depth = 1;
|
|
241
|
+
info.num_levels = 1;
|
|
242
|
+
info.sample_count = SDL_GPU_SAMPLECOUNT_1;
|
|
243
|
+
g_atlas_texture = SDL_CreateGPUTexture(device, &info);
|
|
244
|
+
if (g_atlas_texture == nullptr) {
|
|
245
|
+
fprintf(stderr, "xt_gui: glyph atlas creation failed: %s\n", SDL_GetError());
|
|
246
|
+
return false;
|
|
247
|
+
}
|
|
248
|
+
g_atlas_mirror.assign((size_t)kAtlasSize * kAtlasSize, 0);
|
|
249
|
+
return true;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
void markAtlasDirty(int x, int y, int width, int height) {
|
|
253
|
+
if (!g_atlas_dirty) {
|
|
254
|
+
g_atlas_dirty_x0 = x;
|
|
255
|
+
g_atlas_dirty_y0 = y;
|
|
256
|
+
g_atlas_dirty_x1 = x + width;
|
|
257
|
+
g_atlas_dirty_y1 = y + height;
|
|
258
|
+
g_atlas_dirty = true;
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
g_atlas_dirty_x0 = std::min(g_atlas_dirty_x0, x);
|
|
262
|
+
g_atlas_dirty_y0 = std::min(g_atlas_dirty_y0, y);
|
|
263
|
+
g_atlas_dirty_x1 = std::max(g_atlas_dirty_x1, x + width);
|
|
264
|
+
g_atlas_dirty_y1 = std::max(g_atlas_dirty_y1, y + height);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/** Rasterise + pack `glyph` if new. Returns true and fills `out` when the glyph
|
|
268
|
+
* has a bitmap; false for blank glyphs (space, missing) or a full atlas. */
|
|
269
|
+
bool ensureGlyph(SDL_GPUDevice *device, xtgui::Font *font, uint32_t glyph, GlyphEntry &out) {
|
|
270
|
+
GlyphKey key{font, glyph};
|
|
271
|
+
auto found = g_glyphs.find(key);
|
|
272
|
+
if (found != g_glyphs.end()) {
|
|
273
|
+
out = found->second;
|
|
274
|
+
return out.width > 0 && out.height > 0;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
xtgui::GlyphImage image;
|
|
278
|
+
if (!xtgui::xt_text_rasterize(font, glyph, image) || image.width <= 0 || image.height <= 0) {
|
|
279
|
+
g_glyphs.emplace(key, GlyphEntry{});
|
|
280
|
+
return false;
|
|
281
|
+
}
|
|
282
|
+
if (!ensureAtlasTexture(device)) return false;
|
|
283
|
+
|
|
284
|
+
if (g_atlas_x + image.width > kAtlasSize) {
|
|
285
|
+
g_atlas_x = 0;
|
|
286
|
+
g_atlas_y += g_atlas_row_height + 1;
|
|
287
|
+
g_atlas_row_height = 0;
|
|
288
|
+
}
|
|
289
|
+
if (g_atlas_y + image.height > kAtlasSize) {
|
|
290
|
+
if (!g_atlas_full_logged) {
|
|
291
|
+
fprintf(stderr, "xt_gui: glyph atlas full; some glyphs will not be drawn\n");
|
|
292
|
+
g_atlas_full_logged = true;
|
|
293
|
+
}
|
|
294
|
+
g_glyphs.emplace(key, GlyphEntry{});
|
|
295
|
+
return false;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
int px = g_atlas_x;
|
|
299
|
+
int py = g_atlas_y;
|
|
300
|
+
for (int row = 0; row < image.height; row++) {
|
|
301
|
+
std::memcpy(&g_atlas_mirror[(size_t)(py + row) * kAtlasSize + px],
|
|
302
|
+
image.pixels.data() + (size_t)row * image.width, (size_t)image.width);
|
|
303
|
+
}
|
|
304
|
+
markAtlasDirty(px, py, image.width, image.height);
|
|
305
|
+
|
|
306
|
+
GlyphEntry entry;
|
|
307
|
+
entry.u0 = (float)px / kAtlasSize;
|
|
308
|
+
entry.v0 = (float)py / kAtlasSize;
|
|
309
|
+
entry.u1 = (float)(px + image.width) / kAtlasSize;
|
|
310
|
+
entry.v1 = (float)(py + image.height) / kAtlasSize;
|
|
311
|
+
entry.width = image.width;
|
|
312
|
+
entry.height = image.height;
|
|
313
|
+
entry.left = image.left;
|
|
314
|
+
entry.top = image.top;
|
|
315
|
+
g_glyphs.emplace(key, entry);
|
|
316
|
+
|
|
317
|
+
g_atlas_x += image.width + 1;
|
|
318
|
+
g_atlas_row_height = std::max(g_atlas_row_height, image.height);
|
|
319
|
+
out = entry;
|
|
320
|
+
return true;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
void flushAtlas(SDL_GPUDevice *device) {
|
|
324
|
+
if (!g_atlas_dirty || g_atlas_texture == nullptr) return;
|
|
325
|
+
g_atlas_dirty = false;
|
|
326
|
+
int x0 = g_atlas_dirty_x0;
|
|
327
|
+
int y0 = g_atlas_dirty_y0;
|
|
328
|
+
int width = g_atlas_dirty_x1 - g_atlas_dirty_x0;
|
|
329
|
+
int height = g_atlas_dirty_y1 - g_atlas_dirty_y0;
|
|
330
|
+
if (width <= 0 || height <= 0) return;
|
|
331
|
+
|
|
332
|
+
uint32_t bytes = (uint32_t)(width * height);
|
|
333
|
+
SDL_GPUTransferBufferCreateInfo transfer_info;
|
|
334
|
+
memset(&transfer_info, 0, sizeof(transfer_info));
|
|
335
|
+
transfer_info.usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD;
|
|
336
|
+
transfer_info.size = bytes;
|
|
337
|
+
SDL_GPUTransferBuffer *transfer = SDL_CreateGPUTransferBuffer(device, &transfer_info);
|
|
338
|
+
if (transfer == nullptr) return;
|
|
339
|
+
unsigned char *mapped = (unsigned char *)SDL_MapGPUTransferBuffer(device, transfer, false);
|
|
340
|
+
if (mapped == nullptr) {
|
|
341
|
+
SDL_ReleaseGPUTransferBuffer(device, transfer);
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
for (int row = 0; row < height; row++) {
|
|
345
|
+
std::memcpy(mapped + (size_t)row * width,
|
|
346
|
+
&g_atlas_mirror[(size_t)(y0 + row) * kAtlasSize + x0], (size_t)width);
|
|
347
|
+
}
|
|
348
|
+
SDL_UnmapGPUTransferBuffer(device, transfer);
|
|
349
|
+
|
|
350
|
+
SDL_GPUCommandBuffer *cmd = SDL_AcquireGPUCommandBuffer(device);
|
|
351
|
+
if (cmd == nullptr) {
|
|
352
|
+
SDL_ReleaseGPUTransferBuffer(device, transfer);
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
SDL_GPUTextureTransferInfo source;
|
|
356
|
+
memset(&source, 0, sizeof(source));
|
|
357
|
+
source.transfer_buffer = transfer;
|
|
358
|
+
source.offset = 0;
|
|
359
|
+
source.pixels_per_row = (Uint32)width;
|
|
360
|
+
source.rows_per_layer = (Uint32)height;
|
|
361
|
+
SDL_GPUTextureRegion destination;
|
|
362
|
+
memset(&destination, 0, sizeof(destination));
|
|
363
|
+
destination.texture = g_atlas_texture;
|
|
364
|
+
destination.mip_level = 0;
|
|
365
|
+
destination.layer = 0;
|
|
366
|
+
destination.x = (Uint32)x0;
|
|
367
|
+
destination.y = (Uint32)y0;
|
|
368
|
+
destination.z = 0;
|
|
369
|
+
destination.w = (Uint32)width;
|
|
370
|
+
destination.h = (Uint32)height;
|
|
371
|
+
destination.d = 1;
|
|
372
|
+
SDL_GPUCopyPass *copy = SDL_BeginGPUCopyPass(cmd);
|
|
373
|
+
SDL_UploadToGPUTexture(copy, &source, &destination, false);
|
|
374
|
+
SDL_EndGPUCopyPass(copy);
|
|
375
|
+
SDL_SubmitGPUCommandBuffer(cmd);
|
|
376
|
+
SDL_ReleaseGPUTransferBuffer(device, transfer);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/** Upload `count` vertices (of `stride` bytes) into `geometry`, reusing or
|
|
380
|
+
* growing its GPU buffer. Does nothing when `count` is zero. */
|
|
381
|
+
void uploadVertices(SDL_GPUDevice *device, XtGuiGeometry *geometry, const void *data,
|
|
382
|
+
uint32_t count, uint32_t stride) {
|
|
383
|
+
geometry->vertex_count = count;
|
|
384
|
+
if (count == 0) return;
|
|
385
|
+
uint32_t bytes = count * stride;
|
|
386
|
+
if (geometry->buffer == nullptr || geometry->capacity_bytes < bytes) {
|
|
387
|
+
if (geometry->buffer != nullptr) SDL_ReleaseGPUBuffer(device, geometry->buffer);
|
|
388
|
+
SDL_GPUBufferCreateInfo buffer_info;
|
|
389
|
+
memset(&buffer_info, 0, sizeof(buffer_info));
|
|
390
|
+
buffer_info.usage = SDL_GPU_BUFFERUSAGE_VERTEX;
|
|
391
|
+
buffer_info.size = bytes;
|
|
392
|
+
geometry->buffer = SDL_CreateGPUBuffer(device, &buffer_info);
|
|
393
|
+
geometry->capacity_bytes = bytes;
|
|
394
|
+
}
|
|
395
|
+
if (geometry->buffer == nullptr) {
|
|
396
|
+
fprintf(stderr, "xt_gui: vertex buffer creation failed: %s\n", SDL_GetError());
|
|
397
|
+
geometry->vertex_count = 0;
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
SDL_GPUTransferBufferCreateInfo transfer_info;
|
|
402
|
+
memset(&transfer_info, 0, sizeof(transfer_info));
|
|
403
|
+
transfer_info.usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD;
|
|
404
|
+
transfer_info.size = bytes;
|
|
405
|
+
SDL_GPUTransferBuffer *transfer = SDL_CreateGPUTransferBuffer(device, &transfer_info);
|
|
406
|
+
if (transfer == nullptr) {
|
|
407
|
+
fprintf(stderr, "xt_gui: transfer buffer creation failed: %s\n", SDL_GetError());
|
|
408
|
+
geometry->vertex_count = 0;
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
void *mapped = SDL_MapGPUTransferBuffer(device, transfer, false);
|
|
412
|
+
std::memcpy(mapped, data, bytes);
|
|
413
|
+
SDL_UnmapGPUTransferBuffer(device, transfer);
|
|
414
|
+
|
|
415
|
+
SDL_GPUCommandBuffer *cmd = SDL_AcquireGPUCommandBuffer(device);
|
|
416
|
+
if (cmd == nullptr) {
|
|
417
|
+
SDL_ReleaseGPUTransferBuffer(device, transfer);
|
|
418
|
+
geometry->vertex_count = 0;
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
SDL_GPUCopyPass *copy = SDL_BeginGPUCopyPass(cmd);
|
|
422
|
+
SDL_GPUTransferBufferLocation source;
|
|
423
|
+
source.transfer_buffer = transfer;
|
|
424
|
+
source.offset = 0;
|
|
425
|
+
SDL_GPUBufferRegion destination;
|
|
426
|
+
destination.buffer = geometry->buffer;
|
|
427
|
+
destination.offset = 0;
|
|
428
|
+
destination.size = bytes;
|
|
429
|
+
SDL_UploadToGPUBuffer(copy, &source, &destination, false);
|
|
430
|
+
SDL_EndGPUCopyPass(copy);
|
|
431
|
+
SDL_SubmitGPUCommandBuffer(cmd);
|
|
432
|
+
SDL_ReleaseGPUTransferBuffer(device, transfer);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
SDL_GPUGraphicsPipeline *createTextPipeline(SDL_GPUDevice *device, SDL_Window *reference) {
|
|
436
|
+
SDL_GPUShaderCreateInfo vs_info;
|
|
437
|
+
memset(&vs_info, 0, sizeof(vs_info));
|
|
438
|
+
vs_info.code_size = strlen(kTextMSL);
|
|
439
|
+
vs_info.code = (const Uint8 *)kTextMSL;
|
|
440
|
+
vs_info.entrypoint = "vs_text";
|
|
441
|
+
vs_info.format = SDL_GPU_SHADERFORMAT_MSL;
|
|
442
|
+
vs_info.stage = SDL_GPU_SHADERSTAGE_VERTEX;
|
|
443
|
+
vs_info.num_uniform_buffers = 1;
|
|
444
|
+
SDL_GPUShader *vs = SDL_CreateGPUShader(device, &vs_info);
|
|
445
|
+
|
|
446
|
+
SDL_GPUShaderCreateInfo fs_info;
|
|
447
|
+
memset(&fs_info, 0, sizeof(fs_info));
|
|
448
|
+
fs_info.code_size = strlen(kTextMSL);
|
|
449
|
+
fs_info.code = (const Uint8 *)kTextMSL;
|
|
450
|
+
fs_info.entrypoint = "fs_text";
|
|
451
|
+
fs_info.format = SDL_GPU_SHADERFORMAT_MSL;
|
|
452
|
+
fs_info.stage = SDL_GPU_SHADERSTAGE_FRAGMENT;
|
|
453
|
+
fs_info.num_samplers = 1;
|
|
454
|
+
SDL_GPUShader *fs = SDL_CreateGPUShader(device, &fs_info);
|
|
455
|
+
|
|
456
|
+
if (vs == nullptr || fs == nullptr) {
|
|
457
|
+
fprintf(stderr, "xt_gui: text shader creation failed: %s\n", SDL_GetError());
|
|
458
|
+
if (vs != nullptr) SDL_ReleaseGPUShader(device, vs);
|
|
459
|
+
if (fs != nullptr) SDL_ReleaseGPUShader(device, fs);
|
|
460
|
+
return nullptr;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
SDL_GPUVertexBufferDescription vb_desc;
|
|
464
|
+
memset(&vb_desc, 0, sizeof(vb_desc));
|
|
465
|
+
vb_desc.slot = 0;
|
|
466
|
+
vb_desc.pitch = sizeof(TextVertex);
|
|
467
|
+
vb_desc.input_rate = SDL_GPU_VERTEXINPUTRATE_VERTEX;
|
|
468
|
+
|
|
469
|
+
SDL_GPUVertexAttribute attrs[3];
|
|
470
|
+
memset(attrs, 0, sizeof(attrs));
|
|
471
|
+
attrs[0] = {0, 0, SDL_GPU_VERTEXELEMENTFORMAT_FLOAT2, (Uint32)offsetof(TextVertex, x)};
|
|
472
|
+
attrs[1] = {1, 0, SDL_GPU_VERTEXELEMENTFORMAT_FLOAT2, (Uint32)offsetof(TextVertex, u)};
|
|
473
|
+
attrs[2] = {2, 0, SDL_GPU_VERTEXELEMENTFORMAT_FLOAT4, (Uint32)offsetof(TextVertex, r)};
|
|
474
|
+
|
|
475
|
+
SDL_GPUColorTargetDescription color_target;
|
|
476
|
+
memset(&color_target, 0, sizeof(color_target));
|
|
477
|
+
color_target.format = SDL_GetGPUSwapchainTextureFormat(device, reference);
|
|
478
|
+
color_target.blend_state.enable_blend = true;
|
|
479
|
+
color_target.blend_state.src_color_blendfactor = SDL_GPU_BLENDFACTOR_SRC_ALPHA;
|
|
480
|
+
color_target.blend_state.dst_color_blendfactor = SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA;
|
|
481
|
+
color_target.blend_state.color_blend_op = SDL_GPU_BLENDOP_ADD;
|
|
482
|
+
color_target.blend_state.src_alpha_blendfactor = SDL_GPU_BLENDFACTOR_ONE;
|
|
483
|
+
color_target.blend_state.dst_alpha_blendfactor = SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA;
|
|
484
|
+
color_target.blend_state.alpha_blend_op = SDL_GPU_BLENDOP_ADD;
|
|
485
|
+
|
|
486
|
+
SDL_GPUGraphicsPipelineCreateInfo info;
|
|
487
|
+
memset(&info, 0, sizeof(info));
|
|
488
|
+
info.vertex_shader = vs;
|
|
489
|
+
info.fragment_shader = fs;
|
|
490
|
+
info.vertex_input_state.vertex_buffer_descriptions = &vb_desc;
|
|
491
|
+
info.vertex_input_state.num_vertex_buffers = 1;
|
|
492
|
+
info.vertex_input_state.vertex_attributes = attrs;
|
|
493
|
+
info.vertex_input_state.num_vertex_attributes = 3;
|
|
494
|
+
info.primitive_type = SDL_GPU_PRIMITIVETYPE_TRIANGLELIST;
|
|
495
|
+
info.rasterizer_state.fill_mode = SDL_GPU_FILLMODE_FILL;
|
|
496
|
+
info.rasterizer_state.cull_mode = SDL_GPU_CULLMODE_NONE;
|
|
497
|
+
info.rasterizer_state.front_face = SDL_GPU_FRONTFACE_COUNTER_CLOCKWISE;
|
|
498
|
+
info.multisample_state.sample_count = SDL_GPU_SAMPLECOUNT_1;
|
|
499
|
+
info.target_info.color_target_descriptions = &color_target;
|
|
500
|
+
info.target_info.num_color_targets = 1;
|
|
501
|
+
|
|
502
|
+
SDL_GPUGraphicsPipeline *pipeline = SDL_CreateGPUGraphicsPipeline(device, &info);
|
|
503
|
+
SDL_ReleaseGPUShader(device, vs);
|
|
504
|
+
SDL_ReleaseGPUShader(device, fs);
|
|
505
|
+
if (pipeline == nullptr) {
|
|
506
|
+
fprintf(stderr, "xt_gui: text pipeline creation failed: %s\n", SDL_GetError());
|
|
507
|
+
}
|
|
508
|
+
return pipeline;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
SDL_GPUSampler *createSampler(SDL_GPUDevice *device) {
|
|
512
|
+
SDL_GPUSamplerCreateInfo info;
|
|
513
|
+
memset(&info, 0, sizeof(info));
|
|
514
|
+
info.min_filter = SDL_GPU_FILTER_LINEAR;
|
|
515
|
+
info.mag_filter = SDL_GPU_FILTER_LINEAR;
|
|
516
|
+
info.mipmap_mode = SDL_GPU_SAMPLERMIPMAPMODE_NEAREST;
|
|
517
|
+
info.address_mode_u = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE;
|
|
518
|
+
info.address_mode_v = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE;
|
|
519
|
+
info.address_mode_w = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE;
|
|
520
|
+
SDL_GPUSampler *sampler = SDL_CreateGPUSampler(device, &info);
|
|
521
|
+
if (sampler == nullptr) {
|
|
522
|
+
fprintf(stderr, "xt_gui: glyph sampler creation failed: %s\n", SDL_GetError());
|
|
523
|
+
}
|
|
524
|
+
return sampler;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
SDL_GPUGraphicsPipeline *createImagePipeline(SDL_GPUDevice *device, SDL_Window *reference) {
|
|
528
|
+
SDL_GPUShaderCreateInfo vs_info;
|
|
529
|
+
memset(&vs_info, 0, sizeof(vs_info));
|
|
530
|
+
vs_info.code_size = strlen(kTextMSL);
|
|
531
|
+
vs_info.code = (const Uint8 *)kTextMSL;
|
|
532
|
+
vs_info.entrypoint = "vs_text";
|
|
533
|
+
vs_info.format = SDL_GPU_SHADERFORMAT_MSL;
|
|
534
|
+
vs_info.stage = SDL_GPU_SHADERSTAGE_VERTEX;
|
|
535
|
+
vs_info.num_uniform_buffers = 1;
|
|
536
|
+
SDL_GPUShader *vs = SDL_CreateGPUShader(device, &vs_info);
|
|
537
|
+
|
|
538
|
+
SDL_GPUShaderCreateInfo fs_info;
|
|
539
|
+
memset(&fs_info, 0, sizeof(fs_info));
|
|
540
|
+
fs_info.code_size = strlen(kTextMSL);
|
|
541
|
+
fs_info.code = (const Uint8 *)kTextMSL;
|
|
542
|
+
fs_info.entrypoint = "fs_image";
|
|
543
|
+
fs_info.format = SDL_GPU_SHADERFORMAT_MSL;
|
|
544
|
+
fs_info.stage = SDL_GPU_SHADERSTAGE_FRAGMENT;
|
|
545
|
+
fs_info.num_samplers = 1;
|
|
546
|
+
SDL_GPUShader *fs = SDL_CreateGPUShader(device, &fs_info);
|
|
547
|
+
|
|
548
|
+
if (vs == nullptr || fs == nullptr) {
|
|
549
|
+
fprintf(stderr, "xt_gui: image shader creation failed: %s\n", SDL_GetError());
|
|
550
|
+
if (vs != nullptr) SDL_ReleaseGPUShader(device, vs);
|
|
551
|
+
if (fs != nullptr) SDL_ReleaseGPUShader(device, fs);
|
|
552
|
+
return nullptr;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
SDL_GPUVertexBufferDescription vb_desc;
|
|
556
|
+
memset(&vb_desc, 0, sizeof(vb_desc));
|
|
557
|
+
vb_desc.slot = 0;
|
|
558
|
+
vb_desc.pitch = sizeof(TextVertex);
|
|
559
|
+
vb_desc.input_rate = SDL_GPU_VERTEXINPUTRATE_VERTEX;
|
|
560
|
+
|
|
561
|
+
SDL_GPUVertexAttribute attrs[3];
|
|
562
|
+
memset(attrs, 0, sizeof(attrs));
|
|
563
|
+
attrs[0] = {0, 0, SDL_GPU_VERTEXELEMENTFORMAT_FLOAT2, (Uint32)offsetof(TextVertex, x)};
|
|
564
|
+
attrs[1] = {1, 0, SDL_GPU_VERTEXELEMENTFORMAT_FLOAT2, (Uint32)offsetof(TextVertex, u)};
|
|
565
|
+
attrs[2] = {2, 0, SDL_GPU_VERTEXELEMENTFORMAT_FLOAT4, (Uint32)offsetof(TextVertex, r)};
|
|
566
|
+
|
|
567
|
+
SDL_GPUColorTargetDescription color_target;
|
|
568
|
+
memset(&color_target, 0, sizeof(color_target));
|
|
569
|
+
color_target.format = SDL_GetGPUSwapchainTextureFormat(device, reference);
|
|
570
|
+
color_target.blend_state.enable_blend = true;
|
|
571
|
+
color_target.blend_state.src_color_blendfactor = SDL_GPU_BLENDFACTOR_SRC_ALPHA;
|
|
572
|
+
color_target.blend_state.dst_color_blendfactor = SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA;
|
|
573
|
+
color_target.blend_state.color_blend_op = SDL_GPU_BLENDOP_ADD;
|
|
574
|
+
color_target.blend_state.src_alpha_blendfactor = SDL_GPU_BLENDFACTOR_ONE;
|
|
575
|
+
color_target.blend_state.dst_alpha_blendfactor = SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA;
|
|
576
|
+
color_target.blend_state.alpha_blend_op = SDL_GPU_BLENDOP_ADD;
|
|
577
|
+
|
|
578
|
+
SDL_GPUGraphicsPipelineCreateInfo info;
|
|
579
|
+
memset(&info, 0, sizeof(info));
|
|
580
|
+
info.vertex_shader = vs;
|
|
581
|
+
info.fragment_shader = fs;
|
|
582
|
+
info.vertex_input_state.vertex_buffer_descriptions = &vb_desc;
|
|
583
|
+
info.vertex_input_state.num_vertex_buffers = 1;
|
|
584
|
+
info.vertex_input_state.vertex_attributes = attrs;
|
|
585
|
+
info.vertex_input_state.num_vertex_attributes = 3;
|
|
586
|
+
info.primitive_type = SDL_GPU_PRIMITIVETYPE_TRIANGLELIST;
|
|
587
|
+
info.rasterizer_state.fill_mode = SDL_GPU_FILLMODE_FILL;
|
|
588
|
+
info.rasterizer_state.cull_mode = SDL_GPU_CULLMODE_NONE;
|
|
589
|
+
info.rasterizer_state.front_face = SDL_GPU_FRONTFACE_COUNTER_CLOCKWISE;
|
|
590
|
+
info.multisample_state.sample_count = SDL_GPU_SAMPLECOUNT_1;
|
|
591
|
+
info.target_info.color_target_descriptions = &color_target;
|
|
592
|
+
info.target_info.num_color_targets = 1;
|
|
593
|
+
|
|
594
|
+
SDL_GPUGraphicsPipeline *pipeline = SDL_CreateGPUGraphicsPipeline(device, &info);
|
|
595
|
+
SDL_ReleaseGPUShader(device, vs);
|
|
596
|
+
SDL_ReleaseGPUShader(device, fs);
|
|
597
|
+
if (pipeline == nullptr) {
|
|
598
|
+
fprintf(stderr, "xt_gui: image pipeline creation failed: %s\n", SDL_GetError());
|
|
599
|
+
}
|
|
600
|
+
return pipeline;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
/** Decode `src` (cached) and upload it as an RGBA texture (cached by path). */
|
|
604
|
+
SDL_GPUTexture *ensureImageTexture(SDL_GPUDevice *device, const std::string &src) {
|
|
605
|
+
auto found = g_image_textures.find(src);
|
|
606
|
+
if (found != g_image_textures.end()) return found->second;
|
|
607
|
+
|
|
608
|
+
const xtgui::Image *image = xtgui::xt_image_load(src);
|
|
609
|
+
if (image == nullptr || image->width <= 0 || image->height <= 0) {
|
|
610
|
+
fprintf(stderr, "xt_gui: could not decode image '%s'\n", src.c_str());
|
|
611
|
+
g_image_textures.emplace(src, nullptr);
|
|
612
|
+
return nullptr;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
SDL_GPUTextureCreateInfo info;
|
|
616
|
+
memset(&info, 0, sizeof(info));
|
|
617
|
+
info.type = SDL_GPU_TEXTURETYPE_2D;
|
|
618
|
+
info.format = SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM;
|
|
619
|
+
info.usage = SDL_GPU_TEXTUREUSAGE_SAMPLER;
|
|
620
|
+
info.width = (Uint32)image->width;
|
|
621
|
+
info.height = (Uint32)image->height;
|
|
622
|
+
info.layer_count_or_depth = 1;
|
|
623
|
+
info.num_levels = 1;
|
|
624
|
+
info.sample_count = SDL_GPU_SAMPLECOUNT_1;
|
|
625
|
+
SDL_GPUTexture *texture = SDL_CreateGPUTexture(device, &info);
|
|
626
|
+
if (texture == nullptr) {
|
|
627
|
+
fprintf(stderr, "xt_gui: image texture creation failed: %s\n", SDL_GetError());
|
|
628
|
+
g_image_textures.emplace(src, nullptr);
|
|
629
|
+
return nullptr;
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
uint32_t bytes = (uint32_t)(image->pixels.size());
|
|
633
|
+
SDL_GPUTransferBufferCreateInfo transfer_info;
|
|
634
|
+
memset(&transfer_info, 0, sizeof(transfer_info));
|
|
635
|
+
transfer_info.usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD;
|
|
636
|
+
transfer_info.size = bytes;
|
|
637
|
+
SDL_GPUTransferBuffer *transfer = SDL_CreateGPUTransferBuffer(device, &transfer_info);
|
|
638
|
+
if (transfer == nullptr) {
|
|
639
|
+
SDL_ReleaseGPUTexture(device, texture);
|
|
640
|
+
g_image_textures.emplace(src, nullptr);
|
|
641
|
+
return nullptr;
|
|
642
|
+
}
|
|
643
|
+
void *mapped = SDL_MapGPUTransferBuffer(device, transfer, false);
|
|
644
|
+
std::memcpy(mapped, image->pixels.data(), bytes);
|
|
645
|
+
SDL_UnmapGPUTransferBuffer(device, transfer);
|
|
646
|
+
|
|
647
|
+
SDL_GPUCommandBuffer *cmd = SDL_AcquireGPUCommandBuffer(device);
|
|
648
|
+
if (cmd == nullptr) {
|
|
649
|
+
SDL_ReleaseGPUTransferBuffer(device, transfer);
|
|
650
|
+
SDL_ReleaseGPUTexture(device, texture);
|
|
651
|
+
g_image_textures.emplace(src, nullptr);
|
|
652
|
+
return nullptr;
|
|
653
|
+
}
|
|
654
|
+
SDL_GPUTextureTransferInfo source;
|
|
655
|
+
memset(&source, 0, sizeof(source));
|
|
656
|
+
source.transfer_buffer = transfer;
|
|
657
|
+
source.offset = 0;
|
|
658
|
+
source.pixels_per_row = (Uint32)image->width;
|
|
659
|
+
source.rows_per_layer = (Uint32)image->height;
|
|
660
|
+
SDL_GPUTextureRegion destination;
|
|
661
|
+
memset(&destination, 0, sizeof(destination));
|
|
662
|
+
destination.texture = texture;
|
|
663
|
+
destination.mip_level = 0;
|
|
664
|
+
destination.layer = 0;
|
|
665
|
+
destination.x = 0;
|
|
666
|
+
destination.y = 0;
|
|
667
|
+
destination.z = 0;
|
|
668
|
+
destination.w = (Uint32)image->width;
|
|
669
|
+
destination.h = (Uint32)image->height;
|
|
670
|
+
destination.d = 1;
|
|
671
|
+
SDL_GPUCopyPass *copy = SDL_BeginGPUCopyPass(cmd);
|
|
672
|
+
SDL_UploadToGPUTexture(copy, &source, &destination, false);
|
|
673
|
+
SDL_EndGPUCopyPass(copy);
|
|
674
|
+
SDL_SubmitGPUCommandBuffer(cmd);
|
|
675
|
+
SDL_ReleaseGPUTransferBuffer(device, transfer);
|
|
676
|
+
|
|
677
|
+
g_image_textures.emplace(src, texture);
|
|
678
|
+
return texture;
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
} // namespace
|
|
682
|
+
|
|
683
|
+
bool xt_gui_renderer_ensure(SDL_GPUDevice *device, SDL_Window *reference) {
|
|
684
|
+
if (g_pipeline != nullptr) return true;
|
|
685
|
+
if (g_renderer_failed) return false;
|
|
686
|
+
if ((SDL_GetGPUShaderFormats(device) & SDL_GPU_SHADERFORMAT_MSL) == 0) {
|
|
687
|
+
fprintf(stderr,
|
|
688
|
+
"xt_gui: backend has no MSL shader support; shape/text painting disabled "
|
|
689
|
+
"(Vulkan/D3D12 shader blobs are a TODO)\n");
|
|
690
|
+
g_renderer_failed = 1;
|
|
691
|
+
return false;
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
SDL_GPUShaderCreateInfo vs_info;
|
|
695
|
+
memset(&vs_info, 0, sizeof(vs_info));
|
|
696
|
+
vs_info.code_size = strlen(kFillMSL);
|
|
697
|
+
vs_info.code = (const Uint8 *)kFillMSL;
|
|
698
|
+
vs_info.entrypoint = "vs_main";
|
|
699
|
+
vs_info.format = SDL_GPU_SHADERFORMAT_MSL;
|
|
700
|
+
vs_info.stage = SDL_GPU_SHADERSTAGE_VERTEX;
|
|
701
|
+
vs_info.num_uniform_buffers = 1;
|
|
702
|
+
SDL_GPUShader *vs = SDL_CreateGPUShader(device, &vs_info);
|
|
703
|
+
|
|
704
|
+
SDL_GPUShaderCreateInfo fs_info;
|
|
705
|
+
memset(&fs_info, 0, sizeof(fs_info));
|
|
706
|
+
fs_info.code_size = strlen(kFillMSL);
|
|
707
|
+
fs_info.code = (const Uint8 *)kFillMSL;
|
|
708
|
+
fs_info.entrypoint = "fs_main";
|
|
709
|
+
fs_info.format = SDL_GPU_SHADERFORMAT_MSL;
|
|
710
|
+
fs_info.stage = SDL_GPU_SHADERSTAGE_FRAGMENT;
|
|
711
|
+
SDL_GPUShader *fs = SDL_CreateGPUShader(device, &fs_info);
|
|
712
|
+
|
|
713
|
+
if (vs == nullptr || fs == nullptr) {
|
|
714
|
+
fprintf(stderr, "xt_gui: shader creation failed: %s\n", SDL_GetError());
|
|
715
|
+
if (vs != nullptr) SDL_ReleaseGPUShader(device, vs);
|
|
716
|
+
if (fs != nullptr) SDL_ReleaseGPUShader(device, fs);
|
|
717
|
+
g_renderer_failed = 1;
|
|
718
|
+
return false;
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
SDL_GPUVertexBufferDescription vb_desc;
|
|
722
|
+
memset(&vb_desc, 0, sizeof(vb_desc));
|
|
723
|
+
vb_desc.slot = 0;
|
|
724
|
+
vb_desc.pitch = sizeof(Vertex);
|
|
725
|
+
vb_desc.input_rate = SDL_GPU_VERTEXINPUTRATE_VERTEX;
|
|
726
|
+
|
|
727
|
+
SDL_GPUVertexAttribute attrs[5];
|
|
728
|
+
memset(attrs, 0, sizeof(attrs));
|
|
729
|
+
attrs[0] = {0, 0, SDL_GPU_VERTEXELEMENTFORMAT_FLOAT2, (Uint32)offsetof(Vertex, x)};
|
|
730
|
+
attrs[1] = {1, 0, SDL_GPU_VERTEXELEMENTFORMAT_FLOAT2, (Uint32)offsetof(Vertex, lx)};
|
|
731
|
+
attrs[2] = {2, 0, SDL_GPU_VERTEXELEMENTFORMAT_FLOAT2, (Uint32)offsetof(Vertex, hx)};
|
|
732
|
+
attrs[3] = {3, 0, SDL_GPU_VERTEXELEMENTFORMAT_FLOAT, (Uint32)offsetof(Vertex, radius)};
|
|
733
|
+
attrs[4] = {4, 0, SDL_GPU_VERTEXELEMENTFORMAT_FLOAT4, (Uint32)offsetof(Vertex, r)};
|
|
734
|
+
|
|
735
|
+
SDL_GPUColorTargetDescription color_target;
|
|
736
|
+
memset(&color_target, 0, sizeof(color_target));
|
|
737
|
+
color_target.format = SDL_GetGPUSwapchainTextureFormat(device, reference);
|
|
738
|
+
color_target.blend_state.enable_blend = true;
|
|
739
|
+
color_target.blend_state.src_color_blendfactor = SDL_GPU_BLENDFACTOR_SRC_ALPHA;
|
|
740
|
+
color_target.blend_state.dst_color_blendfactor = SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA;
|
|
741
|
+
color_target.blend_state.color_blend_op = SDL_GPU_BLENDOP_ADD;
|
|
742
|
+
color_target.blend_state.src_alpha_blendfactor = SDL_GPU_BLENDFACTOR_ONE;
|
|
743
|
+
color_target.blend_state.dst_alpha_blendfactor = SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA;
|
|
744
|
+
color_target.blend_state.alpha_blend_op = SDL_GPU_BLENDOP_ADD;
|
|
745
|
+
|
|
746
|
+
SDL_GPUGraphicsPipelineCreateInfo info;
|
|
747
|
+
memset(&info, 0, sizeof(info));
|
|
748
|
+
info.vertex_shader = vs;
|
|
749
|
+
info.fragment_shader = fs;
|
|
750
|
+
info.vertex_input_state.vertex_buffer_descriptions = &vb_desc;
|
|
751
|
+
info.vertex_input_state.num_vertex_buffers = 1;
|
|
752
|
+
info.vertex_input_state.vertex_attributes = attrs;
|
|
753
|
+
info.vertex_input_state.num_vertex_attributes = 5;
|
|
754
|
+
info.primitive_type = SDL_GPU_PRIMITIVETYPE_TRIANGLELIST;
|
|
755
|
+
info.rasterizer_state.fill_mode = SDL_GPU_FILLMODE_FILL;
|
|
756
|
+
info.rasterizer_state.cull_mode = SDL_GPU_CULLMODE_NONE;
|
|
757
|
+
info.rasterizer_state.front_face = SDL_GPU_FRONTFACE_COUNTER_CLOCKWISE;
|
|
758
|
+
info.multisample_state.sample_count = SDL_GPU_SAMPLECOUNT_1;
|
|
759
|
+
info.target_info.color_target_descriptions = &color_target;
|
|
760
|
+
info.target_info.num_color_targets = 1;
|
|
761
|
+
|
|
762
|
+
g_pipeline = SDL_CreateGPUGraphicsPipeline(device, &info);
|
|
763
|
+
SDL_ReleaseGPUShader(device, vs);
|
|
764
|
+
SDL_ReleaseGPUShader(device, fs);
|
|
765
|
+
if (g_pipeline == nullptr) {
|
|
766
|
+
fprintf(stderr, "xt_gui: pipeline creation failed: %s\n", SDL_GetError());
|
|
767
|
+
g_renderer_failed = 1;
|
|
768
|
+
return false;
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
g_text_pipeline = createTextPipeline(device, reference);
|
|
772
|
+
g_image_pipeline = createImagePipeline(device, reference);
|
|
773
|
+
g_sampler = createSampler(device);
|
|
774
|
+
g_text_ok = g_text_pipeline != nullptr && g_sampler != nullptr;
|
|
775
|
+
return true;
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
void xt_gui_renderer_destroy(SDL_GPUDevice *device) {
|
|
779
|
+
if (g_pipeline != nullptr) {
|
|
780
|
+
SDL_ReleaseGPUGraphicsPipeline(device, g_pipeline);
|
|
781
|
+
g_pipeline = nullptr;
|
|
782
|
+
}
|
|
783
|
+
if (g_text_pipeline != nullptr) {
|
|
784
|
+
SDL_ReleaseGPUGraphicsPipeline(device, g_text_pipeline);
|
|
785
|
+
g_text_pipeline = nullptr;
|
|
786
|
+
}
|
|
787
|
+
if (g_image_pipeline != nullptr) {
|
|
788
|
+
SDL_ReleaseGPUGraphicsPipeline(device, g_image_pipeline);
|
|
789
|
+
g_image_pipeline = nullptr;
|
|
790
|
+
}
|
|
791
|
+
if (g_sampler != nullptr) {
|
|
792
|
+
SDL_ReleaseGPUSampler(device, g_sampler);
|
|
793
|
+
g_sampler = nullptr;
|
|
794
|
+
}
|
|
795
|
+
for (auto &entry : g_image_textures) {
|
|
796
|
+
if (entry.second != nullptr) SDL_ReleaseGPUTexture(device, entry.second);
|
|
797
|
+
}
|
|
798
|
+
g_image_textures.clear();
|
|
799
|
+
g_image_batches.clear();
|
|
800
|
+
if (g_atlas_texture != nullptr) {
|
|
801
|
+
SDL_ReleaseGPUTexture(device, g_atlas_texture);
|
|
802
|
+
g_atlas_texture = nullptr;
|
|
803
|
+
}
|
|
804
|
+
g_atlas_mirror.clear();
|
|
805
|
+
g_glyphs.clear();
|
|
806
|
+
g_atlas_x = 0;
|
|
807
|
+
g_atlas_y = 0;
|
|
808
|
+
g_atlas_row_height = 0;
|
|
809
|
+
g_atlas_dirty = false;
|
|
810
|
+
g_atlas_full_logged = false;
|
|
811
|
+
g_text_ok = false;
|
|
812
|
+
g_renderer_failed = 0;
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
void xt_gui_geometry_destroy(SDL_GPUDevice *device, XtGuiGeometry *geometry) {
|
|
816
|
+
if (geometry->buffer != nullptr) {
|
|
817
|
+
SDL_ReleaseGPUBuffer(device, geometry->buffer);
|
|
818
|
+
geometry->buffer = nullptr;
|
|
819
|
+
}
|
|
820
|
+
geometry->capacity_bytes = 0;
|
|
821
|
+
geometry->vertex_count = 0;
|
|
822
|
+
geometry->dirty = true;
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
void xt_gui_geometry_upload(SDL_GPUDevice *device, XtGuiGeometry *geometry,
|
|
826
|
+
const xtgui::DisplayList &list) {
|
|
827
|
+
std::vector<Vertex> verts;
|
|
828
|
+
verts.reserve(list.rects.size() * 6);
|
|
829
|
+
for (const xtgui::PaintRect &rect : list.rects) pushRect(verts, rect);
|
|
830
|
+
uploadVertices(device, geometry, verts.data(), (uint32_t)verts.size(), sizeof(Vertex));
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
void xt_gui_text_upload(SDL_GPUDevice *device, XtGuiGeometry *geometry,
|
|
834
|
+
const xtgui::DisplayList &list, float scale) {
|
|
835
|
+
if (!g_text_ok) {
|
|
836
|
+
geometry->vertex_count = 0;
|
|
837
|
+
return;
|
|
838
|
+
}
|
|
839
|
+
if (scale <= 0.0f) scale = 1.0f;
|
|
840
|
+
|
|
841
|
+
std::vector<TextVertex> verts;
|
|
842
|
+
for (const xtgui::PaintText &run : list.texts) {
|
|
843
|
+
if (run.text.empty() || run.color.a <= 0.0f) continue;
|
|
844
|
+
xtgui::Font *logical = xtgui::xt_text_resolve(run.font);
|
|
845
|
+
if (logical == nullptr) continue;
|
|
846
|
+
xtgui::FontSpec raster_spec = run.font;
|
|
847
|
+
raster_spec.pixel_size = run.font.pixel_size * scale;
|
|
848
|
+
xtgui::Font *raster = xtgui::xt_text_resolve(raster_spec);
|
|
849
|
+
if (raster == nullptr) raster = logical;
|
|
850
|
+
|
|
851
|
+
std::vector<xtgui::ShapedGlyph> glyphs;
|
|
852
|
+
xtgui::xt_text_shape_run(run.text, logical, glyphs);
|
|
853
|
+
float pen_x = run.x;
|
|
854
|
+
for (const xtgui::ShapedGlyph &glyph : glyphs) {
|
|
855
|
+
if (glyph.glyph != 0) {
|
|
856
|
+
GlyphEntry entry;
|
|
857
|
+
if (ensureGlyph(device, raster, glyph.glyph, entry)) {
|
|
858
|
+
float quad_x = pen_x + glyph.x_offset + (float)entry.left / scale;
|
|
859
|
+
float quad_y = run.baseline + glyph.y_offset - (float)entry.top / scale;
|
|
860
|
+
float quad_w = (float)entry.width / scale;
|
|
861
|
+
float quad_h = (float)entry.height / scale;
|
|
862
|
+
pushGlyph(verts, quad_x, quad_y, quad_w, quad_h, entry, run.color);
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
pen_x += glyph.x_advance;
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
uploadVertices(device, geometry, verts.data(), (uint32_t)verts.size(), sizeof(TextVertex));
|
|
869
|
+
flushAtlas(device);
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
void xt_gui_renderer_draw(SDL_GPUCommandBuffer *cmd, SDL_GPURenderPass *pass,
|
|
873
|
+
XtGuiGeometry *geometry, float viewport_width, float viewport_height) {
|
|
874
|
+
if (g_pipeline == nullptr || geometry->buffer == nullptr || geometry->vertex_count == 0) return;
|
|
875
|
+
if (viewport_width <= 0.0f || viewport_height <= 0.0f) return;
|
|
876
|
+
SDL_BindGPUGraphicsPipeline(pass, g_pipeline);
|
|
877
|
+
SDL_GPUBufferBinding binding;
|
|
878
|
+
binding.buffer = geometry->buffer;
|
|
879
|
+
binding.offset = 0;
|
|
880
|
+
SDL_BindGPUVertexBuffers(pass, 0, &binding, 1);
|
|
881
|
+
float viewport[2] = {viewport_width, viewport_height};
|
|
882
|
+
SDL_PushGPUVertexUniformData(cmd, 0, viewport, sizeof(viewport));
|
|
883
|
+
SDL_DrawGPUPrimitives(pass, geometry->vertex_count, 1, 0, 0);
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
void xt_gui_text_draw(SDL_GPUCommandBuffer *cmd, SDL_GPURenderPass *pass, XtGuiGeometry *geometry,
|
|
887
|
+
float viewport_width, float viewport_height) {
|
|
888
|
+
if (!g_text_ok || geometry->buffer == nullptr || geometry->vertex_count == 0) return;
|
|
889
|
+
if (g_atlas_texture == nullptr) return;
|
|
890
|
+
if (viewport_width <= 0.0f || viewport_height <= 0.0f) return;
|
|
891
|
+
SDL_BindGPUGraphicsPipeline(pass, g_text_pipeline);
|
|
892
|
+
SDL_GPUBufferBinding binding;
|
|
893
|
+
binding.buffer = geometry->buffer;
|
|
894
|
+
binding.offset = 0;
|
|
895
|
+
SDL_BindGPUVertexBuffers(pass, 0, &binding, 1);
|
|
896
|
+
SDL_GPUTextureSamplerBinding atlas_binding;
|
|
897
|
+
atlas_binding.texture = g_atlas_texture;
|
|
898
|
+
atlas_binding.sampler = g_sampler;
|
|
899
|
+
SDL_BindGPUFragmentSamplers(pass, 0, &atlas_binding, 1);
|
|
900
|
+
float viewport[2] = {viewport_width, viewport_height};
|
|
901
|
+
SDL_PushGPUVertexUniformData(cmd, 0, viewport, sizeof(viewport));
|
|
902
|
+
SDL_DrawGPUPrimitives(pass, geometry->vertex_count, 1, 0, 0);
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
void xt_gui_image_upload(SDL_GPUDevice *device, XtGuiGeometry *geometry,
|
|
906
|
+
const xtgui::DisplayList &list) {
|
|
907
|
+
g_image_batches.clear();
|
|
908
|
+
if (g_image_pipeline == nullptr) {
|
|
909
|
+
geometry->vertex_count = 0;
|
|
910
|
+
return;
|
|
911
|
+
}
|
|
912
|
+
std::vector<TextVertex> verts;
|
|
913
|
+
verts.reserve(list.images.size() * 6);
|
|
914
|
+
xtgui::Color white{1.0f, 1.0f, 1.0f, 1.0f};
|
|
915
|
+
GlyphEntry full;
|
|
916
|
+
full.u0 = 0.0f;
|
|
917
|
+
full.v0 = 0.0f;
|
|
918
|
+
full.u1 = 1.0f;
|
|
919
|
+
full.v1 = 1.0f;
|
|
920
|
+
for (const xtgui::PaintImage &image : list.images) {
|
|
921
|
+
if (image.width <= 0.0f || image.height <= 0.0f) continue;
|
|
922
|
+
SDL_GPUTexture *texture = ensureImageTexture(device, image.src);
|
|
923
|
+
if (texture == nullptr) continue;
|
|
924
|
+
uint32_t first = (uint32_t)verts.size();
|
|
925
|
+
pushGlyph(verts, image.x, image.y, image.width, image.height, full, white);
|
|
926
|
+
g_image_batches.push_back(ImageBatch{texture, first, 6});
|
|
927
|
+
}
|
|
928
|
+
uploadVertices(device, geometry, verts.data(), (uint32_t)verts.size(), sizeof(TextVertex));
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
void xt_gui_image_draw(SDL_GPUCommandBuffer *cmd, SDL_GPURenderPass *pass, XtGuiGeometry *geometry,
|
|
932
|
+
float viewport_width, float viewport_height) {
|
|
933
|
+
if (g_image_pipeline == nullptr || g_sampler == nullptr) return;
|
|
934
|
+
if (geometry->buffer == nullptr || geometry->vertex_count == 0 || g_image_batches.empty()) return;
|
|
935
|
+
if (viewport_width <= 0.0f || viewport_height <= 0.0f) return;
|
|
936
|
+
SDL_BindGPUGraphicsPipeline(pass, g_image_pipeline);
|
|
937
|
+
SDL_GPUBufferBinding binding;
|
|
938
|
+
binding.buffer = geometry->buffer;
|
|
939
|
+
binding.offset = 0;
|
|
940
|
+
SDL_BindGPUVertexBuffers(pass, 0, &binding, 1);
|
|
941
|
+
float viewport[2] = {viewport_width, viewport_height};
|
|
942
|
+
SDL_PushGPUVertexUniformData(cmd, 0, viewport, sizeof(viewport));
|
|
943
|
+
for (const ImageBatch &batch : g_image_batches) {
|
|
944
|
+
if (batch.texture == nullptr) continue;
|
|
945
|
+
SDL_GPUTextureSamplerBinding texture_binding;
|
|
946
|
+
texture_binding.texture = batch.texture;
|
|
947
|
+
texture_binding.sampler = g_sampler;
|
|
948
|
+
SDL_BindGPUFragmentSamplers(pass, 0, &texture_binding, 1);
|
|
949
|
+
SDL_DrawGPUPrimitives(pass, batch.count, 1, batch.first, 0);
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
void xt_gui_image_textures_destroy(SDL_GPUDevice *device) {
|
|
954
|
+
for (auto &entry : g_image_textures) {
|
|
955
|
+
if (entry.second != nullptr) SDL_ReleaseGPUTexture(device, entry.second);
|
|
956
|
+
}
|
|
957
|
+
g_image_textures.clear();
|
|
958
|
+
g_image_batches.clear();
|
|
959
|
+
}
|