Skip to content

Commit 3ee9920

Browse files
authored
Merge pull request #36 from Chubbygummibear/fix-overlay-sort
Fix unstable overlay sort behavior across browsers
2 parents 8cc7e74 + 37f43f9 commit 3ee9920

File tree

11 files changed

+343
-115
lines changed

11 files changed

+343
-115
lines changed

Diff for: src/main/menu.ts

+4
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,10 @@ export class MainMenu extends Menu {
8282
this.ui.player.toggle_darkness();
8383
this.close();
8484
});
85+
this.add_basic_button("Dump Textures", null, () => {
86+
this.ui.player.dump_textures();
87+
this.close();
88+
});
8589
}
8690
}
8791

Diff for: src/main/rendering/gl_holder.ts

+56-20
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { CopyShader, get_copy_shader, get_icon_shader, IconShader, ShaderHolder
44
import { RenderingCmd } from "../../player/rendering/commands";
55
import { ViewportElement } from "../viewport";
66
import { render_maptext } from "./maptext";
7+
import { BlendMode } from "../../misc/constants";
78

89
export class DemoPlayerGlHolder {
910
gl : WebGLRenderingContext;
@@ -16,6 +17,7 @@ export class DemoPlayerGlHolder {
1617
max_texture_size : number;
1718
copy_framebuffer : WebGLFramebuffer;
1819
white_texture : WebGLTexture;
20+
canvas_copy : WebGLTexture;
1921

2022
shader : IconShader;
2123
shader_matrix : IconShader;
@@ -27,7 +29,7 @@ export class DemoPlayerGlHolder {
2729
if(this.gl2) {
2830
this.gl = this.gl2;
2931
} else {
30-
let gl = canvas.getContext("webgl", {desynchronized: true});
32+
let gl = canvas.getContext("webgl", {desynchronized: true, alpha: false});
3133
if(!gl) throw new Error("Could not initialize WebGL");
3234
this.gl = gl;
3335
}
@@ -49,11 +51,15 @@ export class DemoPlayerGlHolder {
4951

5052
this.white_texture = not_null(gl.createTexture());
5153
gl.bindTexture(gl.TEXTURE_2D, this.white_texture);
52-
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array([255,255,255,255]));
54+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array([0,0,0,0]));
5355
gl.bindTexture(gl.TEXTURE_2D, null);
5456
this.copy_framebuffer = not_null(gl.createFramebuffer());
5557
this.max_texture_size = Math.min(gl.getParameter(gl.MAX_TEXTURE_SIZE), 32768);
5658

59+
this.canvas_copy = not_null(gl.createTexture());
60+
gl.activeTexture(gl.TEXTURE1);
61+
gl.bindTexture(gl.TEXTURE_2D, this.canvas_copy);
62+
5763
this.square_buffer = not_null(gl.createBuffer());
5864
gl.bindBuffer(gl.ARRAY_BUFFER, this.square_buffer);
5965
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([1, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1]), gl.STATIC_DRAW);
@@ -169,7 +175,7 @@ export class DemoPlayerGlHolder {
169175
}
170176
}
171177
gl.bindTexture(gl.TEXTURE_2D, null);
172-
} else if(cmd.cmd == "atlestexcopywithin") {
178+
} else if(cmd.cmd == "atlastexcopywithin") {
173179
let tex = not_null(this.atlas_textures[cmd.index]);
174180
let shader = this.shader_copy;
175181
this.set_shader(shader);
@@ -257,6 +263,8 @@ export class DemoPlayerGlHolder {
257263
ia.vertexAttribDivisorANGLE(shader.a_layer, 1);
258264
ia.drawArraysInstancedANGLE(gl.TRIANGLES, 0, 6, cmd.num_elements);
259265
gl.deleteBuffer(buf);
266+
// gl.activeTexture(gl.TEXTURE0 + 1);
267+
// gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, gl.canvas);
260268
} else if(cmd.cmd == "copytoviewport") {
261269
let this_viewport_pixel = curr_viewport_pixel;
262270
let this_viewport = curr_viewport;
@@ -338,26 +346,54 @@ export class DemoPlayerGlHolder {
338346
// this one was fun - I made test cases in BYOND and took screenshots and tried to reverse-engineer the blending equations from that.
339347
// fun fact BYOND uses premultiplied alpha. However, when you
340348
set_blend_mode(blend_mode : number) : void{
341-
if(blend_mode == 0) blend_mode = 1;
349+
//if(blend_mode == 0) blend_mode = 1;
342350
if(blend_mode == this.curr_blend_mode) return;
343351
this.curr_blend_mode = blend_mode;
344352
const gl = this.gl;
345-
if(blend_mode == 2) { // BLEND_ADD
346-
gl.blendEquation(gl.FUNC_ADD);
347-
gl.blendFuncSeparate(gl.ONE, gl.ONE, gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
348-
} else if(blend_mode == 3) { // BLEND_SUBTRACT
349-
gl.blendEquation(gl.FUNC_REVERSE_SUBTRACT);
350-
gl.blendFuncSeparate(gl.ONE, gl.ONE, gl.ZERO, gl.ONE);
351-
} else if(blend_mode == 4) { // BLEND_MULTIPLY
352-
gl.blendEquation(gl.FUNC_ADD);
353-
gl.blendFunc(gl.DST_COLOR, gl.ONE_MINUS_SRC_ALPHA); // fun fact if you do the math everything cancels out so that the destination alpha doesn't change at all.
354-
} else if(blend_mode == 5) { // BLEND_INSET_OVERLAY
355-
// TODO figure out if this is actually right
356-
gl.blendEquation(gl.FUNC_ADD);
357-
gl.blendFuncSeparate(gl.ONE, gl.ONE_MINUS_SRC_ALPHA, gl.ZERO, gl.ONE)
358-
} else { // BLEND_OVERLAY or BLEND_DEFAULT
359-
gl.blendEquation(gl.FUNC_ADD);
360-
gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
353+
switch(blend_mode){
354+
case BlendMode.DEFAULT: {
355+
gl.blendEquation(gl.FUNC_ADD);
356+
gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
357+
break;
358+
}
359+
case BlendMode.ADD: {
360+
gl.blendEquation(gl.FUNC_ADD);
361+
gl.blendFuncSeparate(gl.ONE, gl.ONE, gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
362+
break;
363+
}
364+
case BlendMode.SUBTRACT: {
365+
gl.blendEquation(gl.FUNC_REVERSE_SUBTRACT);
366+
gl.blendFuncSeparate(gl.ONE, gl.ONE, gl.ZERO, gl.ONE);
367+
break;
368+
}
369+
case BlendMode.MULTIPLY: {
370+
gl.blendEquation(gl.FUNC_ADD);
371+
gl.blendFunc(gl.DST_COLOR, gl.ONE_MINUS_SRC_ALPHA); // fun fact if you do the math everything cancels out so that the destination alpha doesn't change at all.
372+
break;
373+
}
374+
case BlendMode.INSET_OVERLAY: {
375+
// TODO figure out if this is actually right
376+
gl.blendEquation(gl.FUNC_ADD);
377+
gl.blendFuncSeparate(gl.ONE, gl.ONE_MINUS_SRC_ALPHA, gl.ZERO, gl.ONE)
378+
break;
379+
}
380+
case BlendMode.ALPHA: {
381+
gl.blendEquation(gl.FUNC_ADD);
382+
//gl.blendFuncSeparate(gl.DST_COLOR, gl.ZERO, gl.DST_ALPHA, gl.ZERO)
383+
gl.blendFunc(gl.DST_COLOR, gl.ONE_MINUS_SRC_ALPHA);
384+
break;
385+
}
386+
case BlendMode.ALPHA_INVERTED: {
387+
gl.blendEquation(gl.FUNC_ADD);
388+
gl.blendFuncSeparate(gl.DST_COLOR, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE)
389+
break;
390+
}
391+
//Just in case there's a weird value we'll use the default
392+
default: {
393+
gl.blendEquation(gl.FUNC_ADD);
394+
gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
395+
break;
396+
}
361397
}
362398
}
363399

Diff for: src/main/ui.ts

+3
Original file line numberDiff line numberDiff line change
@@ -193,4 +193,7 @@ export class DemoPlayerUi {
193193
handle_sounds(sounds : DemoSound[]) : void {
194194
this.sound_player.handle_sounds(sounds);
195195
}
196+
async dump_textures() {
197+
this.gl_holder.dump_textures();
198+
}
196199
}

Diff for: src/misc/appearance.ts

+51-11
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { IconStateDir } from "../player/rendering/icon";
2-
import { Planes, RESET_ALPHA, RESET_COLOR, RESET_TRANSFORM } from "./constants";
2+
import { BlendMode, Planes, RESET_ALPHA, RESET_COLOR, RESET_TRANSFORM } from "./constants";
33
import { Matrix, matrix_invert, matrix_is_identity, matrix_multiply } from "./matrix";
44

55
export enum FilterType {
@@ -175,7 +175,7 @@ export type TransitionalAppearance = BaseAppearance<TransitionalAppearance|Appea
175175

176176
export namespace Appearance {
177177
const empty_arr : [] = [];
178-
export function resolve_plane(plane : number, parent_plane = 0) : number {
178+
export function resolve_plane(plane : number, parent_plane = Planes.GAME_PLANE) : number {
179179
if(parent_plane < Planes.LOWEST_EVER_PLANE || parent_plane > Planes.HIGHEST_EVER_PLANE) parent_plane = resolve_plane(parent_plane);
180180
if(plane < Planes.LOWEST_EVER_PLANE || plane > Planes.HIGHEST_EVER_PLANE) {
181181
plane = ((parent_plane + plane + 32767) << 16) >> 16;
@@ -190,7 +190,10 @@ export namespace Appearance {
190190
* @returns TRUE if the plane value falls within the range of Byond lighting planes, FALSE if the plane is anything else
191191
*/
192192
export function is_lighting_plane(plane : number): boolean {
193-
return (plane >= Planes.EMISSIVE_BLOCKER_PLANE && plane <= Planes.O_LIGHTING_VISUAL_PLANE)
193+
if(plane < Planes.LOWEST_EVER_PLANE || plane > Planes.HIGHEST_EVER_PLANE) {
194+
plane = plane % (Planes.HIGHEST_EVER_PLANE - Planes.LOWEST_EVER_PLANE);
195+
}
196+
return (plane >= Planes.LIGHTING_PLANE && plane <= Planes.LIGHT_MASK_PLANE)
194197
}
195198

196199
export function get_appearance_parts(appearance : Appearance) {
@@ -205,8 +208,21 @@ export namespace Appearance {
205208
appearances.push(...get_appearance_parts(underlay));
206209
}
207210
}
208-
appearances.push(appearance)
209-
for(let overlay of [...appearance.overlays].sort((a,b) => {
211+
appearances.push(appearance);
212+
213+
/**
214+
This is monster's original sort method which works on some browsers, but not all because it doesn't have all 3 cases required to uphold symmetry.
215+
The cases required for a complete custom sort are
216+
217+
| compareFn(a, b) | return value | sort order
218+
| | > 0 | sort a after b, e.g. [b, a]
219+
| | < 0 | sort a before b, e.g. [a, b]
220+
| | === 0 | keep original order of a and b
221+
222+
So the issue is that monster's only ever returned negatives or 0, so the lacking positive result would break some browser's js engine like firefox.
223+
Whereas chromium based browsers (i.e. Google Chrome, Microsoft Edge) would function as intended.
224+
225+
const appearances_to_sort = [...appearance.overlays].sort((a,b) => {
210226
let a_layer = a.layer < 0 ? appearance.layer : a.layer;
211227
let b_layer = b.layer < 0 ? appearance.layer : b.layer;
212228
if(a_layer < b_layer)
@@ -216,7 +232,31 @@ export namespace Appearance {
216232
if(a_float_layer < b_float_layer)
217233
return a_float_layer - b_float_layer;
218234
return 0;
219-
})) {
235+
})
236+
237+
*/
238+
239+
//To circumvent the sorting issues, we're going to split the possible layers into 2 arrays.
240+
//1 for regular layers which have positive values and 1 for float layers which have negative values
241+
const regular_layers = appearance.overlays.filter((overlay) => overlay.layer >= 0);
242+
const float_layers = appearance.overlays.filter((overlay) => overlay.layer < 0);
243+
244+
//sort by descending order for regular layers
245+
regular_layers.sort((a,b) => {
246+
return a.layer - b.layer;
247+
});
248+
249+
//sort by ascending order for float layers
250+
float_layers.sort((a,b) => {
251+
return a.layer - b.layer;
252+
});
253+
254+
//now combine the arrays with regular layers first.
255+
const appearances_to_sort = regular_layers.concat(float_layers);
256+
257+
//Resume monster's code
258+
259+
for(let overlay of appearances_to_sort) {
220260
overlay = overlay_inherit(appearance, overlay);
221261
if(resolve_plane(overlay.plane, appearance.plane) != resolve_plane(appearance.plane)) {
222262
float_appearances.push(overlay);
@@ -267,10 +307,10 @@ export namespace Appearance {
267307
}
268308
overlay.color_alpha = color_alpha;
269309
}
270-
if(overlay.blend_mode == 0 && appearance.blend_mode > 0) {
271-
clone();
272-
overlay.blend_mode = appearance.blend_mode;
273-
}
310+
// if(appearance.blend_mode == BlendMode.DEFAULT && overlay.blend_mode != BlendMode.DEFAULT) {
311+
// clone();
312+
313+
// }
274314
if(overlay.plane < Planes.LOWEST_EVER_PLANE || overlay.plane > Planes.HIGHEST_EVER_PLANE) {
275315
clone();
276316
overlay.plane = resolve_plane(overlay.plane, appearance.plane);
@@ -405,7 +445,7 @@ export namespace ReaderAppearance {
405445
pixel_y: 0,
406446
pixel_z: 0,
407447
pixel_w: 0,
408-
blend_mode: 0,
448+
blend_mode: BlendMode.DEFAULT,
409449
glide_size: 8,
410450
screen_loc: null,
411451
transform: [1,0,0,0,1,0],

0 commit comments

Comments
 (0)