mirror of
https://github.com/tobspr/shapez.io.git
synced 2024-10-27 20:34:29 +00:00
Improve buffer backlog performance, should speed up whole game
This commit is contained in:
parent
14350721eb
commit
7bc45d8959
@ -13,7 +13,7 @@ import { round1Digit } from "./utils";
|
|||||||
|
|
||||||
const logger = createLogger("buffers");
|
const logger = createLogger("buffers");
|
||||||
|
|
||||||
const bufferGcDurationSeconds = 5;
|
const bufferGcDurationSeconds = 0.5;
|
||||||
|
|
||||||
export class BufferMaintainer {
|
export class BufferMaintainer {
|
||||||
/**
|
/**
|
||||||
|
@ -25,17 +25,43 @@ export function disableImageSmoothing(context) {
|
|||||||
context.webkitImageSmoothingEnabled = false;
|
context.webkitImageSmoothingEnabled = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const registeredCanvas = [];
|
/**
|
||||||
const freeCanvasList = [];
|
* @typedef {{
|
||||||
|
* canvas: HTMLCanvasElement,
|
||||||
|
* context: CanvasRenderingContext2D
|
||||||
|
* }} CanvasCacheEntry
|
||||||
|
*/
|
||||||
|
|
||||||
let vramUsage = 0;
|
/**
|
||||||
let bufferCount = 0;
|
* @type {Array<CanvasCacheEntry>}
|
||||||
|
*/
|
||||||
|
const registeredCanvas = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Buckets for each width * height combination
|
||||||
|
* @type {Map<number, Array<CanvasCacheEntry>>}
|
||||||
|
*/
|
||||||
|
const freeCanvasBuckets = new Map();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Track statistics
|
||||||
|
*/
|
||||||
|
const stats = {
|
||||||
|
vramUsage: 0,
|
||||||
|
backlogVramUsage: 0,
|
||||||
|
bufferCount: 0,
|
||||||
|
numReused: 0,
|
||||||
|
numCreated: 0,
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {HTMLCanvasElement} canvas
|
* @param {HTMLCanvasElement} canvas
|
||||||
*/
|
*/
|
||||||
export function getBufferVramUsageBytes(canvas) {
|
export function getBufferVramUsageBytes(canvas) {
|
||||||
|
assert(canvas, "no canvas given");
|
||||||
|
assert(Number.isFinite(canvas.width), "bad canvas width: " + canvas.width);
|
||||||
|
assert(Number.isFinite(canvas.height), "bad canvas height" + canvas.height);
|
||||||
return canvas.width * canvas.height * 4;
|
return canvas.width * canvas.height * 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -43,17 +69,31 @@ export function getBufferVramUsageBytes(canvas) {
|
|||||||
* Returns stats on the allocated buffers
|
* Returns stats on the allocated buffers
|
||||||
*/
|
*/
|
||||||
export function getBufferStats() {
|
export function getBufferStats() {
|
||||||
|
let numBuffersFree = 0;
|
||||||
|
freeCanvasBuckets.forEach(bucket => {
|
||||||
|
numBuffersFree += bucket.length;
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
vramUsage,
|
...stats,
|
||||||
bufferCount,
|
backlogKeys: freeCanvasBuckets.size,
|
||||||
backlog: freeCanvasList.length,
|
backlogSize: numBuffersFree,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears the backlog buffers if they grew too much
|
||||||
|
*/
|
||||||
export function clearBufferBacklog() {
|
export function clearBufferBacklog() {
|
||||||
while (freeCanvasList.length > 50) {
|
freeCanvasBuckets.forEach(bucket => {
|
||||||
freeCanvasList.pop();
|
while (bucket.length > 500) {
|
||||||
|
const entry = bucket[bucket.length - 1];
|
||||||
|
stats.backlogVramUsage -= getBufferVramUsageBytes(entry.canvas);
|
||||||
|
delete entry.canvas;
|
||||||
|
delete entry.context;
|
||||||
|
bucket.pop();
|
||||||
}
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -84,46 +124,16 @@ export function makeOffscreenBuffer(w, h, { smooth = true, reusable = true, labe
|
|||||||
let canvas = null;
|
let canvas = null;
|
||||||
let context = null;
|
let context = null;
|
||||||
|
|
||||||
let bestMatchingOne = null;
|
|
||||||
let bestMatchingPixelsDiff = 1e50;
|
|
||||||
|
|
||||||
const currentPixels = w * h;
|
|
||||||
|
|
||||||
// Ok, search in cache first
|
// Ok, search in cache first
|
||||||
for (let i = 0; i < freeCanvasList.length; ++i) {
|
const bucket = freeCanvasBuckets.get(w * h) || [];
|
||||||
const { canvas: useableCanvas, context: useableContext } = freeCanvasList[i];
|
|
||||||
|
for (let i = 0; i < bucket.length; ++i) {
|
||||||
|
const { canvas: useableCanvas, context: useableContext } = bucket[i];
|
||||||
if (useableCanvas.width === w && useableCanvas.height === h) {
|
if (useableCanvas.width === w && useableCanvas.height === h) {
|
||||||
// Ok we found one
|
// Ok we found one
|
||||||
canvas = useableCanvas;
|
canvas = useableCanvas;
|
||||||
context = useableContext;
|
context = useableContext;
|
||||||
|
|
||||||
fastArrayDelete(freeCanvasList, i);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
const otherPixels = useableCanvas.width * useableCanvas.height;
|
|
||||||
const diff = Math.abs(otherPixels - currentPixels);
|
|
||||||
if (diff < bestMatchingPixelsDiff) {
|
|
||||||
bestMatchingPixelsDiff = diff;
|
|
||||||
bestMatchingOne = {
|
|
||||||
canvas: useableCanvas,
|
|
||||||
context: useableContext,
|
|
||||||
index: i,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ok none matching, reuse one though
|
|
||||||
if (!canvas && bestMatchingOne) {
|
|
||||||
canvas = bestMatchingOne.canvas;
|
|
||||||
context = bestMatchingOne.context;
|
|
||||||
canvas.width = w;
|
|
||||||
canvas.height = h;
|
|
||||||
fastArrayDelete(freeCanvasList, bestMatchingOne.index);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset context
|
|
||||||
if (context) {
|
|
||||||
// Restore past state
|
// Restore past state
|
||||||
context.restore();
|
context.restore();
|
||||||
context.save();
|
context.save();
|
||||||
@ -131,6 +141,12 @@ export function makeOffscreenBuffer(w, h, { smooth = true, reusable = true, labe
|
|||||||
|
|
||||||
delete canvas.style.width;
|
delete canvas.style.width;
|
||||||
delete canvas.style.height;
|
delete canvas.style.height;
|
||||||
|
|
||||||
|
stats.numReused++;
|
||||||
|
stats.backlogVramUsage -= getBufferVramUsageBytes(canvas);
|
||||||
|
fastArrayDelete(bucket, i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// None found , create new one
|
// None found , create new one
|
||||||
@ -138,6 +154,8 @@ export function makeOffscreenBuffer(w, h, { smooth = true, reusable = true, labe
|
|||||||
canvas = document.createElement("canvas");
|
canvas = document.createElement("canvas");
|
||||||
context = canvas.getContext("2d" /*, { alpha } */);
|
context = canvas.getContext("2d" /*, { alpha } */);
|
||||||
|
|
||||||
|
stats.numCreated++;
|
||||||
|
|
||||||
canvas.width = w;
|
canvas.width = w;
|
||||||
canvas.height = h;
|
canvas.height = h;
|
||||||
|
|
||||||
@ -145,6 +163,7 @@ export function makeOffscreenBuffer(w, h, { smooth = true, reusable = true, labe
|
|||||||
context.save();
|
context.save();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
canvas.label = label;
|
canvas.label = label;
|
||||||
|
|
||||||
if (smooth) {
|
if (smooth) {
|
||||||
@ -167,8 +186,9 @@ export function makeOffscreenBuffer(w, h, { smooth = true, reusable = true, labe
|
|||||||
export function registerCanvas(canvas, context) {
|
export function registerCanvas(canvas, context) {
|
||||||
registeredCanvas.push({ canvas, context });
|
registeredCanvas.push({ canvas, context });
|
||||||
|
|
||||||
bufferCount += 1;
|
stats.bufferCount += 1;
|
||||||
vramUsage += getBufferVramUsageBytes(canvas);
|
const bytesUsed = getBufferVramUsageBytes(canvas);
|
||||||
|
stats.vramUsage += bytesUsed;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -180,6 +200,7 @@ export function freeCanvas(canvas) {
|
|||||||
|
|
||||||
let index = -1;
|
let index = -1;
|
||||||
let data = null;
|
let data = null;
|
||||||
|
|
||||||
for (let i = 0; i < registeredCanvas.length; ++i) {
|
for (let i = 0; i < registeredCanvas.length; ++i) {
|
||||||
if (registeredCanvas[i].canvas === canvas) {
|
if (registeredCanvas[i].canvas === canvas) {
|
||||||
index = i;
|
index = i;
|
||||||
@ -193,8 +214,18 @@ export function freeCanvas(canvas) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
fastArrayDelete(registeredCanvas, index);
|
fastArrayDelete(registeredCanvas, index);
|
||||||
freeCanvasList.push(data);
|
|
||||||
|
|
||||||
bufferCount -= 1;
|
const key = canvas.width * canvas.height;
|
||||||
vramUsage -= getBufferVramUsageBytes(canvas);
|
const bucket = freeCanvasBuckets.get(key);
|
||||||
|
if (bucket) {
|
||||||
|
bucket.push(data);
|
||||||
|
} else {
|
||||||
|
freeCanvasBuckets.set(key, [data]);
|
||||||
|
}
|
||||||
|
|
||||||
|
stats.bufferCount -= 1;
|
||||||
|
|
||||||
|
const bytesUsed = getBufferVramUsageBytes(canvas);
|
||||||
|
stats.vramUsage -= bytesUsed;
|
||||||
|
stats.backlogVramUsage += bytesUsed;
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,12 @@
|
|||||||
import { Application } from "../application";
|
import { Application } from "../application";
|
||||||
/* typehints:end */
|
/* typehints:end */
|
||||||
import { BufferMaintainer } from "../core/buffer_maintainer";
|
import { BufferMaintainer } from "../core/buffer_maintainer";
|
||||||
import { disableImageSmoothing, enableImageSmoothing, registerCanvas } from "../core/buffer_utils";
|
import {
|
||||||
|
disableImageSmoothing,
|
||||||
|
enableImageSmoothing,
|
||||||
|
getBufferStats,
|
||||||
|
registerCanvas,
|
||||||
|
} from "../core/buffer_utils";
|
||||||
import { globalConfig } from "../core/config";
|
import { globalConfig } from "../core/config";
|
||||||
import { getDeviceDPI, resizeHighDPICanvas } from "../core/dpi_manager";
|
import { getDeviceDPI, resizeHighDPICanvas } from "../core/dpi_manager";
|
||||||
import { DrawParameters } from "../core/draw_parameters";
|
import { DrawParameters } from "../core/draw_parameters";
|
||||||
@ -219,9 +224,6 @@ export class GameCore {
|
|||||||
lastContext.clearRect(0, 0, lastCanvas.width, lastCanvas.height);
|
lastContext.clearRect(0, 0, lastCanvas.width, lastCanvas.height);
|
||||||
}
|
}
|
||||||
|
|
||||||
// globalConfig.smoothing.smoothMainCanvas = getDeviceDPI() < 1.5;
|
|
||||||
// globalConfig.smoothing.smoothMainCanvas = true;
|
|
||||||
|
|
||||||
canvas.classList.toggle("smoothed", globalConfig.smoothing.smoothMainCanvas);
|
canvas.classList.toggle("smoothed", globalConfig.smoothing.smoothMainCanvas);
|
||||||
|
|
||||||
// Oof, use :not() instead
|
// Oof, use :not() instead
|
||||||
@ -374,9 +376,9 @@ export class GameCore {
|
|||||||
(zoomLevel / globalConfig.assetsDpi) * getDeviceDPI() * globalConfig.assetsSharpness;
|
(zoomLevel / globalConfig.assetsDpi) * getDeviceDPI() * globalConfig.assetsSharpness;
|
||||||
|
|
||||||
let desiredAtlasScale = "0.25";
|
let desiredAtlasScale = "0.25";
|
||||||
if (effectiveZoomLevel > 0.8 && !lowQuality) {
|
if (effectiveZoomLevel > 0.5 && !lowQuality) {
|
||||||
desiredAtlasScale = ORIGINAL_SPRITE_SCALE;
|
desiredAtlasScale = ORIGINAL_SPRITE_SCALE;
|
||||||
} else if (effectiveZoomLevel > 0.4 && !lowQuality) {
|
} else if (effectiveZoomLevel > 0.35 && !lowQuality) {
|
||||||
desiredAtlasScale = "0.5";
|
desiredAtlasScale = "0.5";
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -500,18 +502,37 @@ export class GameCore {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const stats = this.root.buffers.getStats();
|
const stats = this.root.buffers.getStats();
|
||||||
|
|
||||||
context.fillText(
|
context.fillText(
|
||||||
"Buffers: " +
|
"Maintained Buffers: " +
|
||||||
stats.rootKeys +
|
stats.rootKeys +
|
||||||
" root keys, " +
|
" root keys / " +
|
||||||
stats.subKeys +
|
stats.subKeys +
|
||||||
" sub keys / buffers / VRAM: " +
|
" buffers / VRAM: " +
|
||||||
round2Digits(stats.vramBytes / (1024 * 1024)) +
|
round2Digits(stats.vramBytes / (1024 * 1024)) +
|
||||||
" MB",
|
" MB",
|
||||||
|
|
||||||
20,
|
20,
|
||||||
620
|
620
|
||||||
);
|
);
|
||||||
|
const internalStats = getBufferStats();
|
||||||
|
context.fillText(
|
||||||
|
"Total Buffers: " +
|
||||||
|
internalStats.bufferCount +
|
||||||
|
" buffers / " +
|
||||||
|
internalStats.backlogSize +
|
||||||
|
" backlog / " +
|
||||||
|
internalStats.backlogKeys +
|
||||||
|
" keys in backlog / VRAM " +
|
||||||
|
round2Digits(internalStats.vramUsage / (1024 * 1024)) +
|
||||||
|
" MB / Backlog " +
|
||||||
|
round2Digits(internalStats.backlogVramUsage / (1024 * 1024)) +
|
||||||
|
" MB / Created " +
|
||||||
|
internalStats.numCreated +
|
||||||
|
" / Reused " +
|
||||||
|
internalStats.numReused,
|
||||||
|
20,
|
||||||
|
640
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (G_IS_DEV && globalConfig.debug.testClipping) {
|
if (G_IS_DEV && globalConfig.debug.testClipping) {
|
||||||
|
Loading…
Reference in New Issue
Block a user