Partial 7.0 merge - API preview
This commit is contained in:
@@ -3,6 +3,7 @@ package mindustry.graphics;
|
||||
import arc.*;
|
||||
import arc.graphics.*;
|
||||
import arc.graphics.g2d.*;
|
||||
import arc.graphics.gl.*;
|
||||
import arc.math.*;
|
||||
import arc.math.geom.*;
|
||||
import arc.struct.*;
|
||||
@@ -12,29 +13,93 @@ import mindustry.game.EventType.*;
|
||||
import mindustry.world.*;
|
||||
import mindustry.world.blocks.environment.*;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
public class FloorRenderer implements Disposable{
|
||||
private static final int chunksize = mobile ? 16 : 32, chunkunits = chunksize * tilesize;
|
||||
/**
|
||||
* general implementation:
|
||||
*
|
||||
* caching:
|
||||
* 1. create fixed-size float array fpr rendering into
|
||||
* 2. for each chunk, cache each layer into buffer; record layer boundary indices (alternatively, create mesh per layer for fast recache)
|
||||
* 3. create mesh for this chunk based on buffer size, copy buffer into mesh
|
||||
*
|
||||
* rendering:
|
||||
* 1. iterate through visible chunks
|
||||
* 2. activate the shader vertex attributes beforehand
|
||||
* 3. bind each mesh individually, draw it
|
||||
*
|
||||
* */
|
||||
public class FloorRenderer{
|
||||
private static final VertexAttribute[] attributes = {VertexAttribute.position, VertexAttribute.color, VertexAttribute.texCoords};
|
||||
private static final int
|
||||
chunksize = 30, //todo 32?
|
||||
chunkunits = chunksize * tilesize,
|
||||
vertexSize = 2 + 1 + 2,
|
||||
spriteSize = vertexSize * 4,
|
||||
maxSprites = chunksize * chunksize * 9;
|
||||
private static final float pad = tilesize/2f;
|
||||
//if true, chunks are rendered on-demand; this causes small lag spikes and is generally not needed for most maps
|
||||
private static final boolean dynamic = false;
|
||||
|
||||
private int[][][] cache;
|
||||
private MultiCacheBatch cbatch;
|
||||
private float[] vertices = new float[maxSprites * vertexSize * 4];
|
||||
private short[] indices = new short[maxSprites * 6];
|
||||
private int vidx;
|
||||
private FloorRenderBatch batch = new FloorRenderBatch();
|
||||
private Shader shader;
|
||||
private Texture texture;
|
||||
private TextureRegion error;
|
||||
|
||||
private Mesh[][][] cache;
|
||||
private IntSet drawnLayerSet = new IntSet();
|
||||
private IntSet recacheSet = new IntSet();
|
||||
private IntSeq drawnLayers = new IntSeq();
|
||||
private ObjectSet<CacheLayer> used = new ObjectSet<>();
|
||||
|
||||
public FloorRenderer(){
|
||||
short j = 0;
|
||||
for(int i = 0; i < indices.length; i += 6, j += 4){
|
||||
indices[i] = j;
|
||||
indices[i + 1] = (short)(j + 1);
|
||||
indices[i + 2] = (short)(j + 2);
|
||||
indices[i + 3] = (short)(j + 2);
|
||||
indices[i + 4] = (short)(j + 3);
|
||||
indices[i + 5] = j;
|
||||
}
|
||||
|
||||
shader = new Shader(
|
||||
"""
|
||||
attribute vec4 a_position;
|
||||
attribute vec4 a_color;
|
||||
attribute vec2 a_texCoord0;
|
||||
uniform mat4 u_projectionViewMatrix;
|
||||
varying vec4 v_color;
|
||||
varying vec2 v_texCoords;
|
||||
|
||||
void main(){
|
||||
v_color = a_color;
|
||||
v_color.a = v_color.a * (255.0/254.0);
|
||||
v_texCoords = a_texCoord0;
|
||||
gl_Position = u_projectionViewMatrix * a_position;
|
||||
}
|
||||
""",
|
||||
"""
|
||||
varying vec4 v_color;
|
||||
varying vec2 v_texCoords;
|
||||
uniform sampler2D u_texture;
|
||||
|
||||
void main(){
|
||||
gl_FragColor = v_color * texture2D(u_texture, v_texCoords);
|
||||
}
|
||||
""");
|
||||
|
||||
Events.on(WorldLoadEvent.class, event -> clearTiles());
|
||||
}
|
||||
|
||||
/**Queues up a cache change for a tile. Only runs in render loop. */
|
||||
/** Queues up a cache change for a tile. Only runs in render loop. */
|
||||
public void recacheTile(Tile tile){
|
||||
//currently a no-op
|
||||
//recacheSet.add(Point2.pack(tile.x / chunksize, tile.y / chunksize));
|
||||
//TODO will be faster it the position also specified the layer to be recached
|
||||
//recaching all layers may not be necessary
|
||||
recacheSet.add(Point2.pack(tile.x / chunksize, tile.y / chunksize));
|
||||
}
|
||||
|
||||
public void drawFloor(){
|
||||
@@ -44,6 +109,8 @@ public class FloorRenderer implements Disposable{
|
||||
|
||||
Camera camera = Core.camera;
|
||||
|
||||
float pad = tilesize/2f;
|
||||
|
||||
int
|
||||
minx = (int)((camera.position.x - camera.width/2f - pad) / chunkunits),
|
||||
miny = (int)((camera.position.y - camera.height/2f - pad) / chunkunits),
|
||||
@@ -61,11 +128,15 @@ public class FloorRenderer implements Disposable{
|
||||
|
||||
if(!Structs.inBounds(x, y, cache)) continue;
|
||||
|
||||
int[] chunk = cache[x][y];
|
||||
if(cache[x][y].length == 0){
|
||||
cacheChunk(x, y);
|
||||
}
|
||||
|
||||
Mesh[] chunk = cache[x][y];
|
||||
|
||||
//loop through all layers, and add layer index if it exists
|
||||
for(int i = 0; i < layers; i++){
|
||||
if(chunk[i] != -1 && i != CacheLayer.walls.ordinal()){
|
||||
if(chunk[i] != null && i != CacheLayer.walls.id){
|
||||
drawnLayerSet.add(i);
|
||||
}
|
||||
}
|
||||
@@ -92,11 +163,30 @@ public class FloorRenderer implements Disposable{
|
||||
}
|
||||
|
||||
public void beginc(){
|
||||
cbatch.beginDraw();
|
||||
shader.bind();
|
||||
shader.setUniformMatrix4("u_projectionViewMatrix", Core.camera.mat);
|
||||
shader.setUniformi("u_texture", 0);
|
||||
|
||||
//only ever use the base environment texture
|
||||
//TODO show error texture for anything else
|
||||
texture.bind(0);
|
||||
|
||||
//enable all mesh attributes
|
||||
for(VertexAttribute attribute : attributes){
|
||||
shader.enableVertexAttribute(attribute.alias);
|
||||
}
|
||||
}
|
||||
|
||||
public void endc(){
|
||||
cbatch.endDraw();
|
||||
|
||||
//disable all mesh attributes
|
||||
for(VertexAttribute attribute : attributes){
|
||||
shader.disableVertexAttribute(attribute.alias);
|
||||
}
|
||||
|
||||
//unbind last buffer
|
||||
Gl.bindBuffer(Gl.arrayBuffer, 0);
|
||||
Gl.bindBuffer(Gl.elementArrayBuffer, 0);
|
||||
}
|
||||
|
||||
public void checkChanges(){
|
||||
@@ -118,8 +208,8 @@ public class FloorRenderer implements Disposable{
|
||||
}
|
||||
|
||||
Draw.flush();
|
||||
cbatch.setProjection(Core.camera.mat);
|
||||
cbatch.beginDraw();
|
||||
|
||||
beginc();
|
||||
|
||||
Gl.enable(Gl.blend);
|
||||
}
|
||||
@@ -129,7 +219,7 @@ public class FloorRenderer implements Disposable{
|
||||
return;
|
||||
}
|
||||
|
||||
cbatch.endDraw();
|
||||
endc();
|
||||
}
|
||||
|
||||
public void drawLayer(CacheLayer layer){
|
||||
@@ -150,13 +240,37 @@ public class FloorRenderer implements Disposable{
|
||||
for(int x = minx; x <= maxx; x++){
|
||||
for(int y = miny; y <= maxy; y++){
|
||||
|
||||
if(!Structs.inBounds(x, y, cache)){
|
||||
if(!Structs.inBounds(x, y, cache) || cache[x][y].length == 0){
|
||||
continue;
|
||||
}
|
||||
|
||||
int[] chunk = cache[x][y];
|
||||
if(chunk[layer.ordinal()] == -1) continue;
|
||||
cbatch.drawCache(chunk[layer.ordinal()]);
|
||||
var mesh = cache[x][y][layer.id];
|
||||
|
||||
if(mesh != null){
|
||||
|
||||
//this *must* be a vertexbufferobject, so cast it and render it directly
|
||||
if(mesh.vertices instanceof VertexBufferObject vbo && mesh.indices instanceof IndexBufferObject ibo){
|
||||
|
||||
//bindi the buffer and update its contents, but do not unnecessarily enable all the attributes again
|
||||
vbo.bind();
|
||||
//set up vertex attribute pointers for this specific VBO
|
||||
int offset = 0;
|
||||
for(VertexAttribute attribute : attributes){
|
||||
int location = shader.getAttributeLocation(attribute.alias);
|
||||
int aoffset = offset;
|
||||
offset += attribute.size;
|
||||
if(location < 0) continue;
|
||||
|
||||
shader.setVertexAttribute(location, attribute.components, attribute.type, attribute.normalized, vertexSize * 4, aoffset);
|
||||
}
|
||||
|
||||
ibo.bind();
|
||||
|
||||
mesh.vertices.render(mesh.indices, Gl.triangles, 0, mesh.getNumIndices());
|
||||
}else{
|
||||
throw new ArcRuntimeException("Non-VBO meshes are not supported for caches.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -165,7 +279,6 @@ public class FloorRenderer implements Disposable{
|
||||
|
||||
private void cacheChunk(int cx, int cy){
|
||||
used.clear();
|
||||
int[] chunk = cache[cx][cy];
|
||||
|
||||
for(int tilex = cx * chunksize; tilex < (cx + 1) * chunksize && tilex < world.width(); tilex++){
|
||||
for(int tiley = cy * chunksize; tiley < (cy + 1) * chunksize && tiley < world.height(); tiley++){
|
||||
@@ -179,21 +292,29 @@ public class FloorRenderer implements Disposable{
|
||||
}
|
||||
}
|
||||
|
||||
if(cache[cx][cy].length == 0){
|
||||
cache[cx][cy] = new Mesh[CacheLayer.all.length];
|
||||
}
|
||||
|
||||
var meshes = cache[cx][cy];
|
||||
|
||||
for(CacheLayer layer : CacheLayer.all){
|
||||
if(meshes[layer.id] != null){
|
||||
meshes[layer.id].dispose();
|
||||
}
|
||||
meshes[layer.id] = null;
|
||||
}
|
||||
|
||||
for(CacheLayer layer : used){
|
||||
cacheChunkLayer(cx, cy, chunk, layer);
|
||||
meshes[layer.id] = cacheChunkLayer(cx, cy, layer);
|
||||
}
|
||||
}
|
||||
|
||||
private void cacheChunkLayer(int cx, int cy, int[] chunk, CacheLayer layer){
|
||||
Batch current = Core.batch;
|
||||
Core.batch = cbatch;
|
||||
private Mesh cacheChunkLayer(int cx, int cy, CacheLayer layer){
|
||||
vidx = 0;
|
||||
|
||||
//begin a new cache
|
||||
if(chunk[layer.ordinal()] == -1){
|
||||
cbatch.beginCache();
|
||||
}else{
|
||||
cbatch.beginCache(chunk[layer.ordinal()]);
|
||||
}
|
||||
Batch current = Core.batch;
|
||||
Core.batch = batch;
|
||||
|
||||
for(int tilex = cx * chunksize; tilex < (cx + 1) * chunksize; tilex++){
|
||||
for(int tiley = cy * chunksize; tiley < (cy + 1) * chunksize; tiley++){
|
||||
@@ -217,36 +338,173 @@ public class FloorRenderer implements Disposable{
|
||||
}
|
||||
|
||||
Core.batch = current;
|
||||
chunk[layer.ordinal()] = cbatch.endCache();
|
||||
|
||||
int floats = vidx;
|
||||
//every 4 vertices need 6 indices
|
||||
int vertCount = floats / vertexSize, indCount = vertCount * 6/4;
|
||||
|
||||
Mesh mesh = new Mesh(true, vertCount, indCount, attributes);
|
||||
mesh.setAutoBind(false);
|
||||
mesh.setVertices(vertices, 0, vidx);
|
||||
mesh.setIndices(indices, 0, indCount);
|
||||
|
||||
return mesh;
|
||||
}
|
||||
|
||||
public void clearTiles(){
|
||||
if(cbatch != null) cbatch.dispose();
|
||||
|
||||
recacheSet.clear();
|
||||
int chunksx = Mathf.ceil((float)(world.width()) / chunksize),
|
||||
chunksy = Mathf.ceil((float)(world.height()) / chunksize);
|
||||
cache = new int[chunksx][chunksy][CacheLayer.all.length];
|
||||
cbatch = new MultiCacheBatch(chunksize * chunksize * 9);
|
||||
|
||||
Time.mark();
|
||||
|
||||
for(int x = 0; x < chunksx; x++){
|
||||
for(int y = 0; y < chunksy; y++){
|
||||
Arrays.fill(cache[x][y], -1);
|
||||
|
||||
cacheChunk(x, y);
|
||||
//dispose all old meshes
|
||||
if(cache != null){
|
||||
for(var x : cache){
|
||||
for(var y : x){
|
||||
for(var mesh : y){
|
||||
if(mesh != null){
|
||||
mesh.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Log.debug("Time to cache: @", Time.elapsed());
|
||||
recacheSet.clear();
|
||||
int chunksx = Mathf.ceil((float)(world.width()) / chunksize), chunksy = Mathf.ceil((float)(world.height()) / chunksize);
|
||||
cache = new Mesh[chunksx][chunksy][dynamic ? 0 : CacheLayer.all.length];
|
||||
|
||||
texture = Core.atlas.find("grass1").texture;
|
||||
error = Core.atlas.find("env-error");
|
||||
//not supported due to internal access
|
||||
Mesh.useVAO = false;
|
||||
|
||||
//pre-cache chunks
|
||||
if(!dynamic){
|
||||
Time.mark();
|
||||
|
||||
for(int x = 0; x < chunksx; x++){
|
||||
for(int y = 0; y < chunksy; y++){
|
||||
cacheChunk(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
Log.debug("Time to cache: @", Time.elapsed());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose(){
|
||||
if(cbatch != null){
|
||||
cbatch.dispose();
|
||||
cbatch = null;
|
||||
class FloorRenderBatch extends Batch{
|
||||
|
||||
@Override
|
||||
protected void draw(TextureRegion region, float x, float y, float originX, float originY, float width, float height, float rotation){
|
||||
|
||||
//substitute invalid regions with error
|
||||
if(region.texture != texture && region != error){
|
||||
draw(error, x, y, originX, originY, width, height, rotation);
|
||||
return;
|
||||
}
|
||||
|
||||
float[] verts = vertices;
|
||||
int idx = vidx;
|
||||
vidx += spriteSize;
|
||||
|
||||
if(!Mathf.zero(rotation)){
|
||||
//bottom left and top right corner points relative to origin
|
||||
float worldOriginX = x + originX;
|
||||
float worldOriginY = y + originY;
|
||||
float fx = -originX;
|
||||
float fy = -originY;
|
||||
float fx2 = width - originX;
|
||||
float fy2 = height - originY;
|
||||
|
||||
// rotate
|
||||
float cos = Mathf.cosDeg(rotation);
|
||||
float sin = Mathf.sinDeg(rotation);
|
||||
|
||||
float x1 = cos * fx - sin * fy + worldOriginX;
|
||||
float y1 = sin * fx + cos * fy + worldOriginY;
|
||||
float x2 = cos * fx - sin * fy2 + worldOriginX;
|
||||
float y2 = sin * fx + cos * fy2 + worldOriginY;
|
||||
float x3 = cos * fx2 - sin * fy2 + worldOriginX;
|
||||
float y3 = sin * fx2 + cos * fy2 + worldOriginY;
|
||||
float x4 = x1 + (x3 - x2);
|
||||
float y4 = y3 - (y2 - y1);
|
||||
|
||||
float u = region.u;
|
||||
float v = region.v2;
|
||||
float u2 = region.u2;
|
||||
float v2 = region.v;
|
||||
|
||||
float color = this.colorPacked;
|
||||
|
||||
verts[idx] = x1;
|
||||
verts[idx + 1] = y1;
|
||||
verts[idx + 2] = color;
|
||||
verts[idx + 3] = u;
|
||||
verts[idx + 4] = v;
|
||||
|
||||
verts[idx + 5] = x2;
|
||||
verts[idx + 6] = y2;
|
||||
verts[idx + 7] = color;
|
||||
verts[idx + 8] = u;
|
||||
verts[idx + 9] = v2;
|
||||
|
||||
verts[idx + 10] = x3;
|
||||
verts[idx + 11] = y3;
|
||||
verts[idx + 12] = color;
|
||||
verts[idx + 13] = u2;
|
||||
verts[idx + 14] = v2;
|
||||
|
||||
verts[idx + 15] = x4;
|
||||
verts[idx + 16] = y4;
|
||||
verts[idx + 17] = color;
|
||||
verts[idx + 18] = u2;
|
||||
verts[idx + 19] = v;
|
||||
}else{
|
||||
float fx2 = x + width;
|
||||
float fy2 = y + height;
|
||||
float u = region.u;
|
||||
float v = region.v2;
|
||||
float u2 = region.u2;
|
||||
float v2 = region.v;
|
||||
|
||||
float color = this.colorPacked;
|
||||
|
||||
verts[idx] = x;
|
||||
verts[idx + 1] = y;
|
||||
verts[idx + 2] = color;
|
||||
verts[idx + 3] = u;
|
||||
verts[idx + 4] = v;
|
||||
|
||||
verts[idx + 5] = x;
|
||||
verts[idx + 6] = fy2;
|
||||
verts[idx + 7] = color;
|
||||
verts[idx + 8] = u;
|
||||
verts[idx + 9] = v2;
|
||||
|
||||
verts[idx + 10] = fx2;
|
||||
verts[idx + 11] = fy2;
|
||||
verts[idx + 12] = color;
|
||||
verts[idx + 13] = u2;
|
||||
verts[idx + 14] = v2;
|
||||
|
||||
verts[idx + 15] = fx2;
|
||||
verts[idx + 16] = y;
|
||||
verts[idx + 17] = color;
|
||||
verts[idx + 18] = u2;
|
||||
verts[idx + 19] = v;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flush(){
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setShader(Shader shader, boolean apply){
|
||||
throw new IllegalArgumentException("cache shader unsupported");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void draw(Texture texture, float[] spriteVertices, int offset, int count){
|
||||
throw new IllegalArgumentException("cache vertices unsupported");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user