Skip to content

Commit

Permalink
Merge pull request #36 from Chubbygummibear/fix-overlay-sort
Browse files Browse the repository at this point in the history
Fix unstable overlay sort behavior across browsers
  • Loading branch information
ToasterBiome authored Feb 18, 2024
2 parents 8cc7e74 + 37f43f9 commit 3ee9920
Show file tree
Hide file tree
Showing 11 changed files with 343 additions and 115 deletions.
4 changes: 4 additions & 0 deletions src/main/menu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,10 @@ export class MainMenu extends Menu {
this.ui.player.toggle_darkness();
this.close();
});
this.add_basic_button("Dump Textures", null, () => {
this.ui.player.dump_textures();
this.close();
});
}
}

Expand Down
76 changes: 56 additions & 20 deletions src/main/rendering/gl_holder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { CopyShader, get_copy_shader, get_icon_shader, IconShader, ShaderHolder
import { RenderingCmd } from "../../player/rendering/commands";
import { ViewportElement } from "../viewport";
import { render_maptext } from "./maptext";
import { BlendMode } from "../../misc/constants";

export class DemoPlayerGlHolder {
gl : WebGLRenderingContext;
Expand All @@ -16,6 +17,7 @@ export class DemoPlayerGlHolder {
max_texture_size : number;
copy_framebuffer : WebGLFramebuffer;
white_texture : WebGLTexture;
canvas_copy : WebGLTexture;

shader : IconShader;
shader_matrix : IconShader;
Expand All @@ -27,7 +29,7 @@ export class DemoPlayerGlHolder {
if(this.gl2) {
this.gl = this.gl2;
} else {
let gl = canvas.getContext("webgl", {desynchronized: true});
let gl = canvas.getContext("webgl", {desynchronized: true, alpha: false});
if(!gl) throw new Error("Could not initialize WebGL");
this.gl = gl;
}
Expand All @@ -49,11 +51,15 @@ export class DemoPlayerGlHolder {

this.white_texture = not_null(gl.createTexture());
gl.bindTexture(gl.TEXTURE_2D, this.white_texture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array([255,255,255,255]));
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array([0,0,0,0]));
gl.bindTexture(gl.TEXTURE_2D, null);
this.copy_framebuffer = not_null(gl.createFramebuffer());
this.max_texture_size = Math.min(gl.getParameter(gl.MAX_TEXTURE_SIZE), 32768);

this.canvas_copy = not_null(gl.createTexture());
gl.activeTexture(gl.TEXTURE1);
gl.bindTexture(gl.TEXTURE_2D, this.canvas_copy);

this.square_buffer = not_null(gl.createBuffer());
gl.bindBuffer(gl.ARRAY_BUFFER, this.square_buffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([1, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1]), gl.STATIC_DRAW);
Expand Down Expand Up @@ -169,7 +175,7 @@ export class DemoPlayerGlHolder {
}
}
gl.bindTexture(gl.TEXTURE_2D, null);
} else if(cmd.cmd == "atlestexcopywithin") {
} else if(cmd.cmd == "atlastexcopywithin") {
let tex = not_null(this.atlas_textures[cmd.index]);
let shader = this.shader_copy;
this.set_shader(shader);
Expand Down Expand Up @@ -257,6 +263,8 @@ export class DemoPlayerGlHolder {
ia.vertexAttribDivisorANGLE(shader.a_layer, 1);
ia.drawArraysInstancedANGLE(gl.TRIANGLES, 0, 6, cmd.num_elements);
gl.deleteBuffer(buf);
// gl.activeTexture(gl.TEXTURE0 + 1);
// gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, gl.canvas);
} else if(cmd.cmd == "copytoviewport") {
let this_viewport_pixel = curr_viewport_pixel;
let this_viewport = curr_viewport;
Expand Down Expand Up @@ -338,26 +346,54 @@ export class DemoPlayerGlHolder {
// this one was fun - I made test cases in BYOND and took screenshots and tried to reverse-engineer the blending equations from that.
// fun fact BYOND uses premultiplied alpha. However, when you
set_blend_mode(blend_mode : number) : void{
if(blend_mode == 0) blend_mode = 1;
//if(blend_mode == 0) blend_mode = 1;
if(blend_mode == this.curr_blend_mode) return;
this.curr_blend_mode = blend_mode;
const gl = this.gl;
if(blend_mode == 2) { // BLEND_ADD
gl.blendEquation(gl.FUNC_ADD);
gl.blendFuncSeparate(gl.ONE, gl.ONE, gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
} else if(blend_mode == 3) { // BLEND_SUBTRACT
gl.blendEquation(gl.FUNC_REVERSE_SUBTRACT);
gl.blendFuncSeparate(gl.ONE, gl.ONE, gl.ZERO, gl.ONE);
} else if(blend_mode == 4) { // BLEND_MULTIPLY
gl.blendEquation(gl.FUNC_ADD);
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.
} else if(blend_mode == 5) { // BLEND_INSET_OVERLAY
// TODO figure out if this is actually right
gl.blendEquation(gl.FUNC_ADD);
gl.blendFuncSeparate(gl.ONE, gl.ONE_MINUS_SRC_ALPHA, gl.ZERO, gl.ONE)
} else { // BLEND_OVERLAY or BLEND_DEFAULT
gl.blendEquation(gl.FUNC_ADD);
gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
switch(blend_mode){
case BlendMode.DEFAULT: {
gl.blendEquation(gl.FUNC_ADD);
gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
break;
}
case BlendMode.ADD: {
gl.blendEquation(gl.FUNC_ADD);
gl.blendFuncSeparate(gl.ONE, gl.ONE, gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
break;
}
case BlendMode.SUBTRACT: {
gl.blendEquation(gl.FUNC_REVERSE_SUBTRACT);
gl.blendFuncSeparate(gl.ONE, gl.ONE, gl.ZERO, gl.ONE);
break;
}
case BlendMode.MULTIPLY: {
gl.blendEquation(gl.FUNC_ADD);
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.
break;
}
case BlendMode.INSET_OVERLAY: {
// TODO figure out if this is actually right
gl.blendEquation(gl.FUNC_ADD);
gl.blendFuncSeparate(gl.ONE, gl.ONE_MINUS_SRC_ALPHA, gl.ZERO, gl.ONE)
break;
}
case BlendMode.ALPHA: {
gl.blendEquation(gl.FUNC_ADD);
//gl.blendFuncSeparate(gl.DST_COLOR, gl.ZERO, gl.DST_ALPHA, gl.ZERO)
gl.blendFunc(gl.DST_COLOR, gl.ONE_MINUS_SRC_ALPHA);
break;
}
case BlendMode.ALPHA_INVERTED: {
gl.blendEquation(gl.FUNC_ADD);
gl.blendFuncSeparate(gl.DST_COLOR, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE)
break;
}
//Just in case there's a weird value we'll use the default
default: {
gl.blendEquation(gl.FUNC_ADD);
gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
break;
}
}
}

Expand Down
3 changes: 3 additions & 0 deletions src/main/ui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,4 +193,7 @@ export class DemoPlayerUi {
handle_sounds(sounds : DemoSound[]) : void {
this.sound_player.handle_sounds(sounds);
}
async dump_textures() {
this.gl_holder.dump_textures();
}
}
62 changes: 51 additions & 11 deletions src/misc/appearance.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { IconStateDir } from "../player/rendering/icon";
import { Planes, RESET_ALPHA, RESET_COLOR, RESET_TRANSFORM } from "./constants";
import { BlendMode, Planes, RESET_ALPHA, RESET_COLOR, RESET_TRANSFORM } from "./constants";
import { Matrix, matrix_invert, matrix_is_identity, matrix_multiply } from "./matrix";

export enum FilterType {
Expand Down Expand Up @@ -175,7 +175,7 @@ export type TransitionalAppearance = BaseAppearance<TransitionalAppearance|Appea

export namespace Appearance {
const empty_arr : [] = [];
export function resolve_plane(plane : number, parent_plane = 0) : number {
export function resolve_plane(plane : number, parent_plane = Planes.GAME_PLANE) : number {
if(parent_plane < Planes.LOWEST_EVER_PLANE || parent_plane > Planes.HIGHEST_EVER_PLANE) parent_plane = resolve_plane(parent_plane);
if(plane < Planes.LOWEST_EVER_PLANE || plane > Planes.HIGHEST_EVER_PLANE) {
plane = ((parent_plane + plane + 32767) << 16) >> 16;
Expand All @@ -190,7 +190,10 @@ export namespace Appearance {
* @returns TRUE if the plane value falls within the range of Byond lighting planes, FALSE if the plane is anything else
*/
export function is_lighting_plane(plane : number): boolean {
return (plane >= Planes.EMISSIVE_BLOCKER_PLANE && plane <= Planes.O_LIGHTING_VISUAL_PLANE)
if(plane < Planes.LOWEST_EVER_PLANE || plane > Planes.HIGHEST_EVER_PLANE) {
plane = plane % (Planes.HIGHEST_EVER_PLANE - Planes.LOWEST_EVER_PLANE);
}
return (plane >= Planes.LIGHTING_PLANE && plane <= Planes.LIGHT_MASK_PLANE)
}

export function get_appearance_parts(appearance : Appearance) {
Expand All @@ -205,8 +208,21 @@ export namespace Appearance {
appearances.push(...get_appearance_parts(underlay));
}
}
appearances.push(appearance)
for(let overlay of [...appearance.overlays].sort((a,b) => {
appearances.push(appearance);

/**
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.
The cases required for a complete custom sort are
| compareFn(a, b) | return value | sort order
| | > 0 | sort a after b, e.g. [b, a]
| | < 0 | sort a before b, e.g. [a, b]
| | === 0 | keep original order of a and b
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.
Whereas chromium based browsers (i.e. Google Chrome, Microsoft Edge) would function as intended.
const appearances_to_sort = [...appearance.overlays].sort((a,b) => {
let a_layer = a.layer < 0 ? appearance.layer : a.layer;
let b_layer = b.layer < 0 ? appearance.layer : b.layer;
if(a_layer < b_layer)
Expand All @@ -216,7 +232,31 @@ export namespace Appearance {
if(a_float_layer < b_float_layer)
return a_float_layer - b_float_layer;
return 0;
})) {
})
*/

//To circumvent the sorting issues, we're going to split the possible layers into 2 arrays.
//1 for regular layers which have positive values and 1 for float layers which have negative values
const regular_layers = appearance.overlays.filter((overlay) => overlay.layer >= 0);
const float_layers = appearance.overlays.filter((overlay) => overlay.layer < 0);

//sort by descending order for regular layers
regular_layers.sort((a,b) => {
return a.layer - b.layer;
});

//sort by ascending order for float layers
float_layers.sort((a,b) => {
return a.layer - b.layer;
});

//now combine the arrays with regular layers first.
const appearances_to_sort = regular_layers.concat(float_layers);

//Resume monster's code

for(let overlay of appearances_to_sort) {
overlay = overlay_inherit(appearance, overlay);
if(resolve_plane(overlay.plane, appearance.plane) != resolve_plane(appearance.plane)) {
float_appearances.push(overlay);
Expand Down Expand Up @@ -267,10 +307,10 @@ export namespace Appearance {
}
overlay.color_alpha = color_alpha;
}
if(overlay.blend_mode == 0 && appearance.blend_mode > 0) {
clone();
overlay.blend_mode = appearance.blend_mode;
}
// if(appearance.blend_mode == BlendMode.DEFAULT && overlay.blend_mode != BlendMode.DEFAULT) {
// clone();

// }
if(overlay.plane < Planes.LOWEST_EVER_PLANE || overlay.plane > Planes.HIGHEST_EVER_PLANE) {
clone();
overlay.plane = resolve_plane(overlay.plane, appearance.plane);
Expand Down Expand Up @@ -405,7 +445,7 @@ export namespace ReaderAppearance {
pixel_y: 0,
pixel_z: 0,
pixel_w: 0,
blend_mode: 0,
blend_mode: BlendMode.DEFAULT,
glide_size: 8,
screen_loc: null,
transform: [1,0,0,0,1,0],
Expand Down
Loading

0 comments on commit 3ee9920

Please sign in to comment.