mirror of
https://github.com/Anuken/Mindustry.git
synced 2026-10-01 18:50:27 +03:00
More block rendering optimization
This commit is contained in:
BIN
Binary file not shown.
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1010 B |
Binary file not shown.
|
Before Width: | Height: | Size: 244 B After Width: | Height: | Size: 1.1 KiB |
@@ -1279,7 +1279,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
}
|
||||
|
||||
public void recache(){
|
||||
if(!headless) renderer.blocks.recacheBuilding(tile);
|
||||
if(!headless) renderer.blocks.recacheBuilding(block.buildingCacheLayer, tile);
|
||||
}
|
||||
|
||||
public void payloadDraw(){
|
||||
|
||||
@@ -20,13 +20,16 @@ import mindustry.world.blocks.environment.*;
|
||||
import mindustry.world.blocks.environment.Floor.*;
|
||||
import mindustry.world.blocks.power.*;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import static arc.Core.*;
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
public class BlockRenderer{
|
||||
//TODO cracks take up far to much space, so I had to limit it to 7. this means larger blocks won't have cracks - draw tiling mirrored stuff instead?
|
||||
public static final int crackRegions = 8, maxCrackSize = 7, chunkSize = 30, maxSpritesPerCacheTile = 3;
|
||||
public static boolean drawQuadtreeDebug = false;
|
||||
public static final boolean drawQuadtreeDebug = false;
|
||||
public static final boolean blockDrawCountDebug = false;
|
||||
public static final Color shadowColor = new Color(0, 0, 0, 0.71f), blendShadowColor = Color.white.cpy().lerp(Color.black, shadowColor.a);
|
||||
|
||||
private static final int initialRequests = 32 * 32;
|
||||
@@ -62,12 +65,22 @@ public class BlockRenderer{
|
||||
|
||||
private CacheChunk[][] cacheChunks;
|
||||
private CacheBatch cbatch = new CacheBatch(null);
|
||||
private Seq<SpriteCache> caches = new Seq<>();
|
||||
private Seq<IntSeq> queuedCacheDraws = new Seq<>();
|
||||
private IntSeq queuedCacheIndices = new IntSeq();
|
||||
|
||||
private Seq<SpriteCache>[] caches = new Seq[BuildingCacheLayer.amount];
|
||||
private Seq<IntSeq>[] queuedCacheDraws = new Seq[BuildingCacheLayer.amount];
|
||||
private IntSeq[] queuedCacheIndices = new IntSeq[BuildingCacheLayer.amount];
|
||||
private IntSet dirtyChunks = new IntSet();
|
||||
|
||||
//only used when blockDrawCountDebug = true
|
||||
private ObjectIntMap<Block> blockDrawSprites = new ObjectIntMap<>();
|
||||
private ObjectIntMap<Block> blockDrawTotal = new ObjectIntMap<>();
|
||||
|
||||
public BlockRenderer(){
|
||||
for(int i = 0; i < BuildingCacheLayer.amount; i++){
|
||||
caches[i] = new Seq<>();
|
||||
queuedCacheDraws[i] = new Seq<>();
|
||||
queuedCacheIndices[i] = new IntSeq();
|
||||
}
|
||||
|
||||
Events.on(ClientLoadEvent.class, e -> {
|
||||
cracks = new TextureRegion[maxCrackSize][crackRegions];
|
||||
@@ -94,7 +107,7 @@ public class BlockRenderer{
|
||||
if(blockTree == null || floorTree == null || overlayTree == null) return;
|
||||
|
||||
if(event.tile.block().drawCached){
|
||||
recacheBuilding(event.tile);
|
||||
recacheBuilding(event.tile.block().buildingCacheLayer, event.tile);
|
||||
}
|
||||
|
||||
if(indexBlock(event.tile)){
|
||||
@@ -115,7 +128,7 @@ public class BlockRenderer{
|
||||
}
|
||||
|
||||
if(event.tile.block().drawCached){
|
||||
recacheBuilding(event.tile);
|
||||
recacheBuilding(event.tile.block().buildingCacheLayer, event.tile);
|
||||
}
|
||||
|
||||
if(visible){
|
||||
@@ -136,7 +149,7 @@ public class BlockRenderer{
|
||||
});
|
||||
}
|
||||
|
||||
public void recacheBuilding(Tile tile){
|
||||
public void recacheBuilding(BuildingCacheLayer layer, Tile tile){
|
||||
if(cacheChunks == null) return;
|
||||
|
||||
int cx = tile.x / chunkSize, cy = tile.y / chunkSize;
|
||||
@@ -147,7 +160,7 @@ public class BlockRenderer{
|
||||
|
||||
if(chunk == null) return;
|
||||
|
||||
chunk.dirty = true;
|
||||
chunk.dirty[layer.ordinal()] = true;
|
||||
int packed = Point2.pack(cx, cy);
|
||||
//don't re-cache the chunk unless it was in view
|
||||
if(chunksToDrawSet.contains(packed)){
|
||||
@@ -155,25 +168,31 @@ public class BlockRenderer{
|
||||
}
|
||||
}
|
||||
|
||||
public void cacheChunk(int cx, int cy){
|
||||
public void cacheChunk(int layer, int cx, int cy){
|
||||
int required = chunkSize * chunkSize * maxSpritesPerCacheTile;
|
||||
|
||||
CacheChunk chunk = cacheChunks[cx][cy];
|
||||
if(chunk == null){
|
||||
chunk = cacheChunks[cx][cy] = new CacheChunk();
|
||||
if(caches.isEmpty() || caches.peek().getSpritesUsed() + required > caches.peek().getSpriteCapacity()){
|
||||
//there's no sense adding leftover space, since it will never be used
|
||||
caches.add(new SpriteCache(16382 - (16382 % required), true));
|
||||
queuedCacheDraws.add(new IntSeq());
|
||||
}
|
||||
chunk.cache = caches.peek();
|
||||
chunk.spriteCacheIndex = caches.size - 1;
|
||||
chunk.cache.beginCache();
|
||||
}else{
|
||||
chunk.cache.beginCache(chunk.id);
|
||||
}
|
||||
|
||||
cbatch.cache = chunk.cache;
|
||||
SpriteCache cache = chunk.caches[layer];
|
||||
if(cache == null){
|
||||
var cacheArr = caches[layer];
|
||||
if(cacheArr.isEmpty() || cacheArr.peek().getSpritesUsed() + required > cacheArr.peek().getSpriteCapacity()){
|
||||
//there's no sense adding leftover space, since it will never be used
|
||||
cacheArr.add(new SpriteCache(16382 - (16382 % required), true));
|
||||
queuedCacheDraws[layer].add(new IntSeq());
|
||||
}
|
||||
cache = chunk.caches[layer] = cacheArr.peek();
|
||||
chunk.spriteCacheIndices[layer] = cacheArr.size - 1;
|
||||
cache.beginCache();
|
||||
}else{
|
||||
cache = chunk.caches[layer];
|
||||
cache.beginCache(chunk.cacheIds[layer]);
|
||||
}
|
||||
|
||||
cbatch.cache = cache;
|
||||
Batch lastBatch = Core.batch;
|
||||
try{
|
||||
Draw.flush();
|
||||
@@ -184,7 +203,7 @@ public class BlockRenderer{
|
||||
|
||||
blockCachedTree.intersect(cx * chunkSize * tilesize, cy * chunkSize * tilesize, chunkSize * tilesize, chunkSize * tilesize, tile -> {
|
||||
//only draw blocks strictly inside the chunk
|
||||
if(!(tile.x >= x1 && tile.x < x2 && tile.y >= y1 && tile.y < y2) || !tile.block().drawCached) return;
|
||||
if(!(tile.x >= x1 && tile.x < x2 && tile.y >= y1 && tile.y < y2) || !tile.block().drawCached || tile.block().buildingCacheLayer.ordinal() != layer) return;
|
||||
|
||||
Block block = tile.block();
|
||||
Building build = tile.build;
|
||||
@@ -208,9 +227,9 @@ public class BlockRenderer{
|
||||
batch = lastBatch;
|
||||
}
|
||||
|
||||
chunk.dirty = false;
|
||||
chunk.cache.reserve(required);
|
||||
chunk.id = chunk.cache.endCache();
|
||||
chunk.dirty[layer] = false;
|
||||
cache.reserve(required);
|
||||
chunk.cacheIds[layer] = cache.endCache();
|
||||
}
|
||||
|
||||
public void reload(){
|
||||
@@ -220,10 +239,12 @@ public class BlockRenderer{
|
||||
overlayTree = new OverlayQuadtree(new Rect(0, 0, world.unitWidth(), world.unitHeight()));
|
||||
floorTree = new FloorQuadtree(new Rect(0, 0, world.unitWidth(), world.unitHeight()));
|
||||
|
||||
for(SpriteCache cache : caches){
|
||||
cache.dispose();
|
||||
for(var arr : caches){
|
||||
for(SpriteCache cache : arr){
|
||||
cache.dispose();
|
||||
}
|
||||
arr.clear();
|
||||
}
|
||||
caches.clear();
|
||||
int chunksx = Mathf.ceil((float)(world.width()) / chunkSize), chunksy = Mathf.ceil((float)(world.height()) / chunkSize);
|
||||
cacheChunks = new CacheChunk[chunksx][chunksy];
|
||||
|
||||
@@ -577,29 +598,43 @@ public class BlockRenderer{
|
||||
}
|
||||
});
|
||||
|
||||
queuedCacheIndices.clear();
|
||||
|
||||
for(var seq : queuedCacheDraws){
|
||||
for(var seq : queuedCacheIndices){
|
||||
seq.clear();
|
||||
}
|
||||
|
||||
for(var arr : queuedCacheDraws){
|
||||
for(var seq : arr){
|
||||
seq.clear();
|
||||
}
|
||||
}
|
||||
|
||||
//begin by checking any stale/uncached chunks, and caching them if necessary
|
||||
chunksToDraw.each(xy -> {
|
||||
int cx = Point2.x(xy);
|
||||
int cy = Point2.y(xy);
|
||||
CacheChunk chunk = cacheChunks[cx][cy];
|
||||
if(chunk == null || chunk.dirty || chunk.lastSeenTeam != pteam){
|
||||
cacheChunk(cx, cy);
|
||||
if(chunk == null || chunk.lastSeenTeam != pteam){
|
||||
for(int i = 0; i < BuildingCacheLayer.amount; i++){
|
||||
cacheChunk(i, cx, cy);
|
||||
}
|
||||
}else{
|
||||
for(int i = 0; i < BuildingCacheLayer.amount; i++){
|
||||
if(chunk.dirty[i]){
|
||||
cacheChunk(i, cx, cy);
|
||||
}
|
||||
}
|
||||
}
|
||||
//in case it was null (it can never be null after caching)
|
||||
chunk = cacheChunks[cx][cy];
|
||||
|
||||
//record the sprite cache IDs that will be drawn in queuedCacheIndices, and record the actual cache IDs of the respective sprite caches in queuedCacheDraws
|
||||
IntSeq cacheIDsToDraw = queuedCacheDraws.get(chunk.spriteCacheIndex);
|
||||
if(cacheIDsToDraw.isEmpty()){
|
||||
queuedCacheIndices.add(chunk.spriteCacheIndex);
|
||||
for(int layer = 0; layer < BuildingCacheLayer.amount; layer++){
|
||||
//record the sprite cache IDs that will be drawn in queuedCacheIndices, and record the actual cache IDs of the respective sprite caches in queuedCacheDraws
|
||||
IntSeq cacheIDsToDraw = queuedCacheDraws[layer].get(chunk.spriteCacheIndices[layer]);
|
||||
if(cacheIDsToDraw.isEmpty()){
|
||||
queuedCacheIndices[layer].add(chunk.spriteCacheIndices[layer]);
|
||||
}
|
||||
cacheIDsToDraw.add(chunk.cacheIds[layer]);
|
||||
}
|
||||
cacheIDsToDraw.add(chunk.id);
|
||||
});
|
||||
|
||||
lastCamX = avgx;
|
||||
@@ -636,22 +671,43 @@ public class BlockRenderer{
|
||||
|
||||
if(chunksToDraw.size > 0){
|
||||
|
||||
Draw.draw(Layer.block, () -> {
|
||||
dirtyChunks.each(c -> cacheChunk(Point2.x(c), Point2.y(c)));
|
||||
dirtyChunks.clear();
|
||||
for(int mlayer = 0; mlayer < BuildingCacheLayer.amount; mlayer++){
|
||||
int layer = mlayer;
|
||||
float z = BuildingCacheLayer.layers[layer];
|
||||
|
||||
//minor optimization: don't transfer the matrix every time
|
||||
SpriteCache.getDefaultShader().bind();
|
||||
SpriteCache.getDefaultShader().setUniformMatrix4("u_projectionViewMatrix", camera.mat);
|
||||
Draw.draw(z, () -> {
|
||||
if(layer == 0){
|
||||
dirtyChunks.each(c -> {
|
||||
int cx = Point2.x(c), cy = Point2.y(c);
|
||||
var chunk = cacheChunks[cx][cy];
|
||||
|
||||
queuedCacheIndices.each(spriteCacheIndex -> {
|
||||
SpriteCache sprites = caches.get(spriteCacheIndex);
|
||||
IntSeq cachesToDraw = queuedCacheDraws.get(spriteCacheIndex);
|
||||
sprites.begin(false);
|
||||
cachesToDraw.each(sprites::draw);
|
||||
sprites.end();
|
||||
for(int l = 0; l < BuildingCacheLayer.amount; l++){
|
||||
if(chunk == null || chunk.dirty[l]){
|
||||
cacheChunk(l, Point2.x(c), Point2.y(c));
|
||||
}
|
||||
}
|
||||
});
|
||||
dirtyChunks.clear();
|
||||
|
||||
//minor optimization: don't transfer the matrix every time
|
||||
SpriteCache.getDefaultShader().bind();
|
||||
SpriteCache.getDefaultShader().setUniformMatrix4("u_projectionViewMatrix", camera.mat);
|
||||
}
|
||||
|
||||
queuedCacheIndices[layer].each(spriteCacheIndex -> {
|
||||
SpriteCache sprites = caches[layer].get(spriteCacheIndex);
|
||||
IntSeq cachesToDraw = queuedCacheDraws[layer].get(spriteCacheIndex);
|
||||
sprites.begin(false);
|
||||
cachesToDraw.each(sprites::draw);
|
||||
sprites.end();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if(blockDrawCountDebug){
|
||||
blockDrawTotal.clear();
|
||||
blockDrawSprites.clear();
|
||||
}
|
||||
|
||||
//draw most tile stuff
|
||||
@@ -666,6 +722,7 @@ public class BlockRenderer{
|
||||
boolean visible = (build == null || !build.inFogTo(pteam));
|
||||
|
||||
if(block != Blocks.air && (visible || build.wasVisible)){
|
||||
SpriteBatch.totalDrawCalls = 0;
|
||||
block.drawBase(tile);
|
||||
Draw.reset();
|
||||
Draw.z(Layer.block);
|
||||
@@ -700,9 +757,45 @@ public class BlockRenderer{
|
||||
}
|
||||
}
|
||||
Draw.reset();
|
||||
|
||||
if(blockDrawCountDebug){
|
||||
blockDrawTotal.increment(block, 1);
|
||||
if(SpriteBatch.totalDrawCalls > 0) blockDrawSprites.increment(block, (int)SpriteBatch.totalDrawCalls);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(blockDrawCountDebug && graphics.getFrameId() % 30 == 0){
|
||||
class Entry implements Comparable<Entry>{
|
||||
Block block;
|
||||
int amount;
|
||||
|
||||
public Entry(Block block, int amount){
|
||||
this.block = block;
|
||||
this.amount = amount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(){
|
||||
return block + ": " + amount + " (average: " + Strings.autoFixed((float)amount/blockDrawTotal.get(block), 1) + ")";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(Entry entry){
|
||||
return Integer.compare(amount, entry.amount);
|
||||
}
|
||||
}
|
||||
int total = 0;
|
||||
|
||||
Seq<Entry> entries = new Seq<>();
|
||||
for(var v : blockDrawSprites){
|
||||
entries.add(new Entry(v.key, v.value));
|
||||
total += v.value;
|
||||
}
|
||||
entries.sort().reverse();
|
||||
Log.info("Draw calls:\n" + entries.toString("\n") + "\nTOTAL: " + total + "\n");
|
||||
}
|
||||
|
||||
//draw overlay of extra cached tiles (they otherwise wouldn't draw cracks / status overlays)
|
||||
for(int i = 0; i < tileExtraCachedView.size; i++){
|
||||
Building build = tileExtraCachedView.items[i];
|
||||
@@ -716,7 +809,7 @@ public class BlockRenderer{
|
||||
build.wasVisible = true;
|
||||
updateShadow(build);
|
||||
renderer.minimap.update(build.tile);
|
||||
recacheBuilding(build.tile);
|
||||
recacheBuilding(build.block.buildingCacheLayer, build.tile);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -791,11 +884,17 @@ public class BlockRenderer{
|
||||
}
|
||||
|
||||
static class CacheChunk{
|
||||
SpriteCache cache;
|
||||
int spriteCacheIndex; //index of cache variable in list of global spritcaches
|
||||
int id;
|
||||
boolean dirty = true;
|
||||
SpriteCache[] caches = new SpriteCache[BuildingCacheLayer.amount];
|
||||
//index of cache variable in list of global sprite caches
|
||||
int[] spriteCacheIndices = new int[BuildingCacheLayer.amount];
|
||||
int[] cacheIds = new int[BuildingCacheLayer.amount];
|
||||
boolean[] dirty = new boolean[BuildingCacheLayer.amount];
|
||||
|
||||
Team lastSeenTeam;
|
||||
|
||||
{
|
||||
Arrays.fill(dirty, true);
|
||||
}
|
||||
}
|
||||
|
||||
static class BlockQuadtree extends QuadTree<Tile>{
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
package mindustry.graphics;
|
||||
|
||||
public enum BuildingCacheLayer{
|
||||
under(Layer.block - 0.2f),
|
||||
normal(Layer.block);
|
||||
|
||||
public final float layer;
|
||||
|
||||
public final static BuildingCacheLayer[] all = values();
|
||||
public final static int amount = all.length;
|
||||
public final static float[] layers = new float[all.length];
|
||||
|
||||
static{
|
||||
for(int i = 0; i < amount; i++){
|
||||
layers[i] = all[i].layer;
|
||||
}
|
||||
}
|
||||
|
||||
BuildingCacheLayer(float layer){
|
||||
this.layer = layer;
|
||||
}
|
||||
}
|
||||
@@ -235,6 +235,8 @@ public class Block extends UnlockableContent implements Senseable{
|
||||
public boolean drawDynamic = true;
|
||||
/** If enabled, drawCached() will be called on the building. */
|
||||
public boolean drawCached = false;
|
||||
/** */
|
||||
public BuildingCacheLayer buildingCacheLayer = BuildingCacheLayer.normal;
|
||||
/** Special flag; if false, floor will be drawn under this block even if it is cached. */
|
||||
public boolean fillsTile = true;
|
||||
/** If true, this block can be covered by darkness / fog even if synthetic. */
|
||||
|
||||
@@ -53,6 +53,8 @@ public class StackConveyor extends Block implements Autotiler{
|
||||
conveyorPlacement = true;
|
||||
underBullets = true;
|
||||
priority = TargetPriority.transport;
|
||||
drawCached = true;
|
||||
buildingCacheLayer = BuildingCacheLayer.under;
|
||||
|
||||
ambientSound = Sounds.loopConveyor;
|
||||
ambientSoundVolume = 0.004f;
|
||||
@@ -119,9 +121,7 @@ public class StackConveyor extends Block implements Autotiler{
|
||||
boolean proxUpdating = false;
|
||||
|
||||
@Override
|
||||
public void draw(){
|
||||
Draw.z(Layer.block - 0.2f);
|
||||
|
||||
public void drawCached(){
|
||||
Draw.rect(regions[state], x, y, rotdeg());
|
||||
|
||||
for(int i = 0; i < 4; i++){
|
||||
@@ -145,12 +145,14 @@ public class StackConveyor extends Block implements Autotiler{
|
||||
Draw.rect(sliced(regions[0], SliceMode.top), x + Geometry.d4x(rotation) * tilesize*0.75f, y + Geometry.d4y(rotation) * tilesize*0.75f, rotation * 90f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(){
|
||||
Draw.z(Layer.block - 0.1f);
|
||||
|
||||
Tile from = world.tile(link);
|
||||
|
||||
//TODO do not draw for certain configurations?
|
||||
if(glowRegion.found() && power != null && power.status > 0f){
|
||||
Draw.z(Layer.blockAdditive);
|
||||
Draw.color(glowColor, glowAlpha * power.status);
|
||||
@@ -185,7 +187,6 @@ public class StackConveyor extends Block implements Autotiler{
|
||||
|
||||
//item
|
||||
float size = itemSize * Mathf.lerp(Math.min((float)items.total() / itemCapacity, 1), 1f, 0.4f);
|
||||
Drawf.shadow(Tmp.v1.x, Tmp.v1.y, size * 1.2f);
|
||||
Draw.rect(lastItem.fullIcon, Tmp.v1.x, Tmp.v1.y, size, size, 0);
|
||||
}
|
||||
|
||||
@@ -215,6 +216,7 @@ public class StackConveyor extends Block implements Autotiler{
|
||||
@Override
|
||||
public void onProximityUpdate(){
|
||||
super.onProximityUpdate();
|
||||
recache();
|
||||
|
||||
int lastState = state;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user