mirror of
https://github.com/tobspr/shapez.io.git
synced 2025-06-09 02:54:01 +00:00
Overlay rendering performance seemed bottlenecked by drawImage calls. To reduce both the number of calls and the number of different source buffers, cache overlay buffers for squares of chunks. This adds a very small extra cost for updates (one additional drawImage) and some cost for drawing chunks outside of view, but this is more than made up for by the savings. By default, the aggregate are 4x4 squares of chunks.
244 lines
8.8 KiB
JavaScript
244 lines
8.8 KiB
JavaScript
import { globalConfig } from "../core/config";
|
|
import { DrawParameters } from "../core/draw_parameters";
|
|
import { BaseMap } from "./map";
|
|
import { freeCanvas, makeOffscreenBuffer } from "../core/buffer_utils";
|
|
import { Entity } from "./entity";
|
|
import { THEME } from "./theme";
|
|
import { MapChunkView } from "./map_chunk_view";
|
|
import { MapChunkAggregate } from "./map_chunk_aggregate";
|
|
|
|
/**
|
|
* This is the view of the map, it extends the map which is the raw model and allows
|
|
* to draw it
|
|
*/
|
|
export class MapView extends BaseMap {
|
|
constructor(root) {
|
|
super(root);
|
|
|
|
/**
|
|
* DPI of the background cache images, required in some places
|
|
*/
|
|
this.backgroundCacheDPI = 2;
|
|
|
|
/**
|
|
* The cached background sprite, containing the flat background
|
|
* @type {HTMLCanvasElement} */
|
|
this.cachedBackgroundCanvas = null;
|
|
|
|
/** @type {CanvasRenderingContext2D} */
|
|
this.cachedBackgroundContext = null;
|
|
this.internalInitializeCachedBackgroundCanvases();
|
|
this.root.signals.aboutToDestruct.add(this.cleanup, this);
|
|
|
|
this.root.signals.entityAdded.add(this.onEntityChanged, this);
|
|
this.root.signals.entityDestroyed.add(this.onEntityChanged, this);
|
|
this.root.signals.entityChanged.add(this.onEntityChanged, this);
|
|
}
|
|
|
|
cleanup() {
|
|
freeCanvas(this.cachedBackgroundCanvas);
|
|
this.cachedBackgroundCanvas = null;
|
|
}
|
|
|
|
/**
|
|
* Called when an entity was added, removed or changed
|
|
* @param {Entity} entity
|
|
*/
|
|
onEntityChanged(entity) {
|
|
const staticComp = entity.components.StaticMapEntity;
|
|
if (staticComp) {
|
|
const rect = staticComp.getTileSpaceBounds();
|
|
for (let x = rect.x; x <= rect.right(); ++x) {
|
|
for (let y = rect.y; y <= rect.bottom(); ++y) {
|
|
this.root.map.getOrCreateChunkAtTile(x, y).markDirty();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Draws all static entities like buildings etc.
|
|
* @param {DrawParameters} drawParameters
|
|
*/
|
|
drawStaticEntityDebugOverlays(drawParameters) {
|
|
if (G_IS_DEV && (globalConfig.debug.showAcceptorEjectors || globalConfig.debug.showEntityBounds)) {
|
|
const cullRange = drawParameters.visibleRect.toTileCullRectangle();
|
|
const top = cullRange.top();
|
|
const right = cullRange.right();
|
|
const bottom = cullRange.bottom();
|
|
const left = cullRange.left();
|
|
|
|
const border = 1;
|
|
|
|
const minY = top - border;
|
|
const maxY = bottom + border;
|
|
const minX = left - border;
|
|
const maxX = right + border - 1;
|
|
|
|
// Render y from top down for proper blending
|
|
for (let y = minY; y <= maxY; ++y) {
|
|
for (let x = minX; x <= maxX; ++x) {
|
|
// const content = this.tiles[x][y];
|
|
const chunk = this.getChunkAtTileOrNull(x, y);
|
|
if (!chunk) {
|
|
continue;
|
|
}
|
|
const content = chunk.getTileContentFromWorldCoords(x, y);
|
|
if (content) {
|
|
let isBorder = x <= left - 1 || x >= right + 1 || y <= top - 1 || y >= bottom + 1;
|
|
if (!isBorder) {
|
|
content.drawDebugOverlays(drawParameters);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Initializes all canvases used for background rendering
|
|
*/
|
|
internalInitializeCachedBackgroundCanvases() {
|
|
// Background canvas
|
|
const dims = globalConfig.tileSize;
|
|
const dpi = this.backgroundCacheDPI;
|
|
const [canvas, context] = makeOffscreenBuffer(dims * dpi, dims * dpi, {
|
|
smooth: false,
|
|
label: "map-cached-bg",
|
|
});
|
|
context.scale(dpi, dpi);
|
|
|
|
context.fillStyle = THEME.map.background;
|
|
context.fillRect(0, 0, dims, dims);
|
|
|
|
const borderWidth = THEME.map.gridLineWidth;
|
|
context.fillStyle = THEME.map.grid;
|
|
context.fillRect(0, 0, dims, borderWidth);
|
|
context.fillRect(0, borderWidth, borderWidth, dims);
|
|
|
|
context.fillRect(dims - borderWidth, borderWidth, borderWidth, dims - 2 * borderWidth);
|
|
context.fillRect(borderWidth, dims - borderWidth, dims, borderWidth);
|
|
|
|
this.cachedBackgroundCanvas = canvas;
|
|
this.cachedBackgroundContext = context;
|
|
}
|
|
|
|
/**
|
|
* Draws the maps foreground
|
|
* @param {DrawParameters} parameters
|
|
*/
|
|
drawForeground(parameters) {
|
|
this.drawVisibleChunks(parameters, MapChunkView.prototype.drawForegroundDynamicLayer);
|
|
this.drawVisibleChunks(parameters, MapChunkView.prototype.drawForegroundStaticLayer);
|
|
}
|
|
|
|
/**
|
|
* Calls a given method on all given chunks
|
|
* @param {DrawParameters} parameters
|
|
* @param {function} method
|
|
*/
|
|
drawVisibleChunks(parameters, method) {
|
|
const cullRange = parameters.visibleRect.allScaled(1 / globalConfig.tileSize);
|
|
const top = cullRange.top();
|
|
const right = cullRange.right();
|
|
const bottom = cullRange.bottom();
|
|
const left = cullRange.left();
|
|
|
|
const border = 0;
|
|
const minY = top - border;
|
|
const maxY = bottom + border;
|
|
const minX = left - border;
|
|
const maxX = right + border;
|
|
|
|
const chunkStartX = Math.floor(minX / globalConfig.mapChunkSize);
|
|
const chunkStartY = Math.floor(minY / globalConfig.mapChunkSize);
|
|
|
|
const chunkEndX = Math.floor(maxX / globalConfig.mapChunkSize);
|
|
const chunkEndY = Math.floor(maxY / globalConfig.mapChunkSize);
|
|
|
|
// Render y from top down for proper blending
|
|
for (let chunkX = chunkStartX; chunkX <= chunkEndX; ++chunkX) {
|
|
for (let chunkY = chunkStartY; chunkY <= chunkEndY; ++chunkY) {
|
|
const chunk = this.root.map.getChunk(chunkX, chunkY, true);
|
|
method.call(chunk, parameters);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Calls a given method on all given chunks
|
|
* @param {DrawParameters} parameters
|
|
* @param {function} method
|
|
*/
|
|
drawVisibleAggregates(parameters, method) {
|
|
const cullRange = parameters.visibleRect.allScaled(1 / globalConfig.tileSize);
|
|
const top = cullRange.top();
|
|
const right = cullRange.right();
|
|
const bottom = cullRange.bottom();
|
|
const left = cullRange.left();
|
|
|
|
const border = 0;
|
|
const minY = top - border;
|
|
const maxY = bottom + border;
|
|
const minX = left - border;
|
|
const maxX = right + border;
|
|
|
|
const aggregateTiles = globalConfig.chunkAggregateSize * globalConfig.mapChunkSize;
|
|
const aggStartX = Math.floor(minX / aggregateTiles);
|
|
const aggStartY = Math.floor(minY / aggregateTiles);
|
|
|
|
const aggEndX = Math.floor(maxX / aggregateTiles);
|
|
const aggEndY = Math.floor(maxY / aggregateTiles);
|
|
|
|
// Render y from top down for proper blending
|
|
for (let aggX = aggStartX; aggX <= aggEndX; ++aggX) {
|
|
for (let aggY = aggStartY; aggY <= aggEndY; ++aggY) {
|
|
const aggregate = this.root.map.getAggregate(aggX, aggY, true);
|
|
method.call(aggregate, parameters);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Draws the wires foreground
|
|
* @param {DrawParameters} parameters
|
|
*/
|
|
drawWiresForegroundLayer(parameters) {
|
|
this.drawVisibleChunks(parameters, MapChunkView.prototype.drawWiresForegroundLayer);
|
|
}
|
|
|
|
/**
|
|
* Draws the map overlay
|
|
* @param {DrawParameters} parameters
|
|
*/
|
|
drawOverlay(parameters) {
|
|
this.drawVisibleAggregates(parameters, MapChunkAggregate.prototype.drawOverlay);
|
|
}
|
|
|
|
/**
|
|
* Draws the map background
|
|
* @param {DrawParameters} parameters
|
|
*/
|
|
drawBackground(parameters) {
|
|
// Render tile grid
|
|
if (!this.root.app.settings.getAllSettings().disableTileGrid || !this.root.gameMode.hasResources()) {
|
|
const dpi = this.backgroundCacheDPI;
|
|
parameters.context.scale(1 / dpi, 1 / dpi);
|
|
|
|
parameters.context.fillStyle = parameters.context.createPattern(
|
|
this.cachedBackgroundCanvas,
|
|
"repeat"
|
|
);
|
|
parameters.context.fillRect(
|
|
parameters.visibleRect.x * dpi,
|
|
parameters.visibleRect.y * dpi,
|
|
parameters.visibleRect.w * dpi,
|
|
parameters.visibleRect.h * dpi
|
|
);
|
|
parameters.context.scale(dpi, dpi);
|
|
}
|
|
|
|
this.drawVisibleChunks(parameters, MapChunkView.prototype.drawBackgroundLayer);
|
|
}
|
|
}
|