@@ -37,6 +37,47 @@ export function Rect (x, y, w, h) {
37
37
}
38
38
39
39
40
+ export function View (x, y, w, h, scale, offset) {
41
+ this.left = x;
42
+ this.right = x + w;
43
+ this.top = y;
44
+ this.bottom = y + h;
45
+ this.width = w;
46
+ this.height = h;
47
+ this.scaleX = scale[0];
48
+ this.scaleY = scale[1];
49
+ this.offsetX = offset[0];
50
+ this.offsetY = offset[1];
51
+
52
+ this.transformPosX = function(p) {
53
+ return (p - this.left - this.offsetX) * this.scaleX + this.left;
54
+ }
55
+
56
+ this.transformPosY = function(p) {
57
+ return (p - this.top - this.offsetY) * this.scaleY + this.top;
58
+ }
59
+
60
+ this.transformDistX = function(d) {
61
+ return d * this.scaleX;
62
+ }
63
+
64
+ this.transformDistY = function(d) {
65
+ return d * this.scaleY;
66
+ }
67
+
68
+ this.transformRect = function(r) {
69
+ return new Rect(
70
+ this.transformPosX(r.left),
71
+ this.transformPosY(r.top),
72
+ this.transformDistX(r.width),
73
+ this.transformDistY(r.height));
74
+ }
75
+ this.getXYScale = function() {
76
+ return Math.min(this.scaleX, this.scaleY);
77
+ }
78
+ }
79
+
80
+
40
81
export function UIRenderer(canvas, redrawCallback) {
41
82
42
83
// Rendering context
@@ -50,10 +91,7 @@ export function UIRenderer(canvas, redrawCallback) {
50
91
this.redrawCallback = redrawCallback;
51
92
52
93
// Viewport transform
53
- this.transform = [ 1, 0, 0, 0,
54
- 0, 1, 0, 0,
55
- 0, 0, 1, 0,
56
- 0, 0, 0, 1 ];
94
+ this.views = [];
57
95
58
96
// Shader data
59
97
this.shaderInfo = {};
@@ -72,6 +110,7 @@ export function UIRenderer(canvas, redrawCallback) {
72
110
const CMD_RECT = 3;
73
111
const CMD_FRAME = 4;
74
112
const CMD_IMAGE = 5;
113
+ const CMD_CLIP = 9;
75
114
76
115
// Style
77
116
this.styleDataStartIdx = (MAX_CMD_DATA - MAX_STYLE_CMDS) * 4; // Start writing style to the last cmd data texture line.
@@ -99,32 +138,38 @@ export function UIRenderer(canvas, redrawCallback) {
99
138
let bounds = new Rect(p1[0], p1[1], 0, 0);
100
139
bounds.encapsulate(p2);
101
140
bounds.widen(Math.round(width * 0.5 + 0.01));
102
- let w = this.addPrimitiveShape(CMD_LINE, bounds, color, width, null);
103
- // Data 3 - Shape parameters
104
- this.cmdData[w++] = p1[0];
105
- this.cmdData[w++] = p1[1];
106
- this.cmdData[w++] = p2[0];
107
- this.cmdData[w++] = p2[1];
108
-
109
- this.cmdDataIdx = w;
141
+ if (this.addPrimitiveShape(CMD_LINE, bounds, color, width, null)) {
142
+ let w = this.cmdDataIdx;
143
+ const v = this.getView();
144
+ // Data 3 - Shape parameters
145
+ this.cmdData[w++] = v ? v.transformPosX(p1[0]) : p1[0];
146
+ this.cmdData[w++] = v ? v.transformPosY(p1[1]) : p1[1];
147
+ this.cmdData[w++] = v ? v.transformPosX(p2[0]) : p2[0];
148
+ this.cmdData[w++] = v ? v.transformPosY(p2[1]) : p2[1];
149
+
150
+ this.cmdDataIdx = w;
151
+ }
110
152
}
111
153
112
154
this.addTriangle = function (p1, p2, p3, color) {
113
155
let bounds = new Rect(p1[0], p1[1], 0, 0);
114
156
bounds.encapsulate(p2);
115
157
bounds.encapsulate(p3);
116
- let w = this.addPrimitiveShape(CMD_TRIANGLE, bounds, color, null, null);
117
- // Data 3 - Shape parameters
118
- this.cmdData[w++] = p1[0];
119
- this.cmdData[w++] = p1[1];
120
- this.cmdData[w++] = p2[0];
121
- this.cmdData[w++] = p2[1];
122
- // Data 4 - Shape parameters II
123
- this.cmdData[w++] = p3[0];
124
- this.cmdData[w++] = p3[1];
125
- w+=2;
126
-
127
- this.cmdDataIdx = w;
158
+ if (this.addPrimitiveShape(CMD_TRIANGLE, bounds, color, null, null)) {
159
+ let w = this.cmdDataIdx;
160
+ const v = this.getView();
161
+ // Data 3 - Shape parameters
162
+ this.cmdData[w++] = v ? v.transformPosX(p1[0]) : p1[0];
163
+ this.cmdData[w++] = v ? v.transformPosY(p1[1]) : p1[1];
164
+ this.cmdData[w++] = v ? v.transformPosX(p2[0]) : p2[0];
165
+ this.cmdData[w++] = v ? v.transformPosY(p2[1]) : p2[1];
166
+ // Data 4 - Shape parameters II
167
+ this.cmdData[w++] = v ? v.transformPosX(p3[0]) : p3[0];
168
+ this.cmdData[w++] = v ? v.transformPosY(p3[1]) : p3[1];
169
+ w += 2;
170
+
171
+ this.cmdDataIdx = w;
172
+ }
128
173
}
129
174
130
175
this.addCircle = function (p1, radius, color) {
@@ -194,16 +239,29 @@ export function UIRenderer(canvas, redrawCallback) {
194
239
}
195
240
196
241
this.addPrimitiveShape = function (cmdType, bounds, color, lineWidth, corner) {
242
+
243
+ const v = this.getView();
244
+ bounds = v ? v.transformRect(bounds) : bounds;
245
+
246
+ // Clip bounds.
247
+ if (v &&
248
+ (bounds.right < v.left || bounds.left > v.right
249
+ || bounds.bottom < v.top || bounds.top > v.bottom)) {
250
+ return false;
251
+ }
252
+
253
+ corner = v ? corner * v.getXYScale() : corner;
254
+ lineWidth = v ? lineWidth * v.getXYScale() : lineWidth;
255
+
197
256
let w = this.cmdDataIdx;
198
257
// Check for at least 4 free command slots as that's the maximum a shape might need.
199
258
if (w/4 + 4 > MAX_SHAPE_CMDS) {
200
259
console.warn("Too many shapes to draw.", w/4 + 4, "of", MAX_SHAPE_CMDS);
201
- // Overwrite the start of the command buffer.
202
- return 0;
260
+ return false;
203
261
}
204
262
205
263
// Check for a change of style and push a new style if needed.
206
- if (!this.stateColor.every((v , i) => v === color[i]) // Is color array different?
264
+ if (!this.stateColor.every((c , i) => c === color[i]) // Is color array different?
207
265
|| (lineWidth !== null && this.stateLineWidth !== lineWidth) // Is line width used for this shape and different?
208
266
|| (corner !== null && this.stateCorner !== corner)
209
267
) {
@@ -244,6 +302,20 @@ export function UIRenderer(canvas, redrawCallback) {
244
302
return w;
245
303
}
246
304
305
+ this.addClipRect = function (left, top, right, bottom) {
306
+ // Write clip rect information for the shader.
307
+ let w = this.cmdDataIdx;
308
+ // Data 0 - Header
309
+ this.cmdData[w++] = CMD_CLIP;
310
+ w += 3;
311
+ // Data 1 - Bounds
312
+ this.cmdData[w++] = left;
313
+ this.cmdData[w++] = top;
314
+ this.cmdData[w++] = right;
315
+ this.cmdData[w++] = bottom;
316
+ this.cmdDataIdx = w;
317
+ }
318
+
247
319
// Create a GPU texture object (returns the ID, usable immediately) and
248
320
// asynchronously load the image data from the given url onto it.
249
321
this.loadImage = function (url) {
@@ -327,6 +399,24 @@ export function UIRenderer(canvas, redrawCallback) {
327
399
return textureID;
328
400
}
329
401
402
+ this.getView = function() {
403
+ return this.views.length ? this.views[this.views.length - 1] : null;
404
+ }
405
+
406
+ this.pushView = function(x, y, w, h, scale, offset) {
407
+ const view = new View(x, y, w, h, scale, offset);
408
+ this.views.push(view);
409
+ this.addClipRect(x +1, y +1, x + w -1, y + h -1);
410
+ return view;
411
+ }
412
+
413
+ this.popView = function() {
414
+ this.views.pop();
415
+ const v = this.getView();
416
+ if (v) { this.addClipRect(v.left, v.top, v.right, v.bottom); }
417
+ else { this.addClipRect(0, 0, this.gl.canvas.width, this.gl.canvas.height); }
418
+ }
419
+
330
420
// Draw a frame with the current primitive commands.
331
421
this.draw = function() {
332
422
const gl = this.gl;
@@ -340,8 +430,7 @@ export function UIRenderer(canvas, redrawCallback) {
340
430
gl.invalidateFramebuffer(gl.FRAMEBUFFER, [gl.COLOR]);
341
431
342
432
// Set the transform.
343
- gl.uniformMatrix4fv(this.shaderInfo.uniforms.modelViewProj, false, this.transform);
344
- gl.uniform1f(this.shaderInfo.uniforms.vpHeight, gl.canvas.height);
433
+ gl.uniform2f(this.shaderInfo.uniforms.vpSize, gl.canvas.width, gl.canvas.height);
345
434
346
435
// Bind the vertex data for the shader to use and specify how to interpret it.
347
436
// The shader works as a full size rect, new coordinates don't need to be set per frame.
@@ -413,6 +502,7 @@ export function UIRenderer(canvas, redrawCallback) {
413
502
this.textureIDs = [];
414
503
this.textureBundleIDs = [];
415
504
// Clear the state.
505
+ this.views = [];
416
506
this.stateColor = [-1, -1, -1, -1];
417
507
// Clear the style list.
418
508
this.styleDataIdx = this.styleDataStartIdx;
@@ -445,8 +535,7 @@ export function UIRenderer(canvas, redrawCallback) {
445
535
vertexPos: bind_attr(gl, shaderProgram, 'v_pos'),
446
536
},
447
537
uniforms: {
448
- modelViewProj: bind_uniform(gl, shaderProgram, 'mvp'),
449
- vpHeight: bind_uniform(gl, shaderProgram, 'viewport_height'),
538
+ vpSize: bind_uniform(gl, shaderProgram, 'viewport_size'),
450
539
numCmds: bind_uniform(gl, shaderProgram, 'num_cmds'),
451
540
cmdBufferTex: bind_uniform(gl, shaderProgram, 'cmd_data'),
452
541
samplers: [
0 commit comments