Added GUI render and support for faces without textures

- Added FlatRenderProgram to render 2D graphics
  - Can be used as-is
  - Supports rectangular coordinate-aligned nested masks
- Added AssembledFlatLayer to cache 2D graphics
  - Use RenderTarget for add elements during reassembly
- ShapeRenderProgram now allows null textures (defaults to white BG)
- Sprite now deduces size based on primitive's content size
  - unless given size explicitly
- ShapeRenderHelper.*WorldTransform methods renamed to .*Transform
This commit is contained in:
OLEGSHA 2020-08-01 16:23:31 +03:00
parent 47ea102ed7
commit 1ed8f93af4
25 changed files with 678 additions and 27 deletions

View File

@ -21,6 +21,8 @@ import ru.windcorp.optica.Proxy;
import ru.windcorp.optica.client.graphics.GUI;
import ru.windcorp.optica.client.graphics.backend.GraphicsBackend;
import ru.windcorp.optica.client.graphics.backend.RenderTaskQueue;
import ru.windcorp.optica.client.graphics.flat.FlatRenderProgram;
import ru.windcorp.optica.client.graphics.flat.LayerTestUI;
import ru.windcorp.optica.client.graphics.world.LayerWorld;
import ru.windcorp.optica.client.graphics.world.WorldRenderProgram;
@ -30,6 +32,7 @@ public class ClientProxy implements Proxy {
public void initialize() {
GraphicsBackend.initialize();
try {
RenderTaskQueue.waitAndInvoke(FlatRenderProgram::init);
RenderTaskQueue.waitAndInvoke(WorldRenderProgram::init);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
@ -37,6 +40,7 @@ public class ClientProxy implements Proxy {
}
GUI.addBottomLayer(new LayerWorld());
GUI.addTopLayer(new LayerTestUI());
}
}

View File

@ -0,0 +1,21 @@
package ru.windcorp.optica.client.graphics;
public class Colors {
public static final int
WHITE = 0xFFFFFF,
BLACK = 0x000000,
GRAY_4 = 0x444444,
GRAY = 0x888888,
GRAY_A = 0xAAAAAA,
DEBUG_RED = 0xFF0000,
DEBUG_GREEN = 0x00FF00,
DEBUG_BLUE = 0x0000FF,
DEBUG_CYAN = 0x00FFFF,
DEBUG_MAGENTA = 0xFF00FF,
DEBUG_YELLOW = 0x00FFFF;
}

View File

@ -17,6 +17,8 @@
*******************************************************************************/
package ru.windcorp.optica.client.graphics;
import ru.windcorp.optica.client.graphics.backend.GraphicsInterface;
public abstract class Layer {
private final String name;
@ -44,4 +46,12 @@ public abstract class Layer {
protected abstract void doRender();
protected float getWidth() {
return GraphicsInterface.getFramebufferWidth();
}
protected float getHeight() {
return GraphicsInterface.getFramebufferHeight();
}
}

View File

@ -0,0 +1,103 @@
package ru.windcorp.optica.client.graphics.flat;
import ru.windcorp.optica.client.graphics.Layer;
import ru.windcorp.optica.client.graphics.model.WorldRenderable;
public abstract class AssembledFlatLayer extends Layer {
private final FlatRenderHelper helper = new FlatRenderHelper();
private final RenderTarget target = new RenderTarget();
private boolean needsReassembly = true;
private Clip[] clips = null;
public AssembledFlatLayer(String name) {
super(name);
}
@Override
protected void initialize() {
// TODO Auto-generated method stub
}
public void markForReassembly() {
needsReassembly = true;
}
private void doReassemble() {
assemble(target);
clips = target.assemble();
needsReassembly = false;
}
protected abstract void assemble(RenderTarget target);
@Override
protected void doRender() {
if (needsReassembly) {
doReassemble();
}
for (Clip clip : clips) {
clip.render(helper);
}
helper.reset();
}
public static class Clip {
private final Mask mask = new Mask();
private final WorldRenderable renderable;
public Clip(
int startX, int startY,
int endX, int endY,
WorldRenderable renderable
) {
mask.set(startX, startY, endX, endY);
this.renderable = renderable;
}
public Clip(
Mask mask,
WorldRenderable renderable
) {
this(
mask.getStartX(), mask.getStartY(),
mask.getEndX(), mask.getEndY(),
renderable
);
}
public int getStartX() {
return mask.getStartX();
}
public int getStartY() {
return mask.getStartY();
}
public int getEndX() {
return mask.getEndX();
}
public int getEndY() {
return mask.getEndY();
}
public WorldRenderable getRenderable() {
return renderable;
}
public void render(FlatRenderHelper helper) {
helper.pushMask(getStartX(), getStartY(), getEndX(), getEndY());
renderable.render(helper);
helper.popTransform();
}
}
}

View File

@ -0,0 +1,7 @@
package ru.windcorp.optica.client.graphics.flat;
public class FlatGraphics {
}

View File

@ -0,0 +1,65 @@
package ru.windcorp.optica.client.graphics.flat;
import ru.windcorp.optica.client.graphics.backend.GraphicsInterface;
import ru.windcorp.optica.client.graphics.model.ShapeRenderHelper;
public class FlatRenderHelper extends ShapeRenderHelper {
private final Mask mask = new Mask();
{
setupScreenTransform();
}
public FlatRenderHelper pushMask(
int startX, int startY,
int endX, int endY
) {
mask.set(startX, startY, endX, endY);
pushTransform().translate(startX, startY, 0);
return this;
}
public FlatRenderHelper pushMask(Mask mask) {
return pushMask(
mask.getStartX(), mask.getStartY(),
mask.getEndX(), mask.getEndY()
);
}
public int getStartX() {
return mask.getStartX();
}
public int getStartY() {
return mask.getStartY();
}
public int getEndX() {
return mask.getEndX();
}
public int getEndY() {
return mask.getEndY();
}
public boolean isRenderable() {
return !mask.isEmpty();
}
@Override
public void reset() {
super.reset();
setupScreenTransform();
mask.set(0, 0, Integer.MAX_VALUE, Integer.MAX_VALUE);
}
private void setupScreenTransform() {
float width = GraphicsInterface.getFramebufferWidth();
float height = GraphicsInterface.getFramebufferHeight();
getTransform().translate(-1, +1, 0).scale(2 / width, -2 / height, 1);
}
}

View File

@ -0,0 +1,84 @@
package ru.windcorp.optica.client.graphics.flat;
import static org.lwjgl.opengl.GL11.*;
import com.google.common.collect.ObjectArrays;
import ru.windcorp.optica.client.graphics.backend.shaders.uniforms.Uniform2Int;
import ru.windcorp.optica.client.graphics.model.Face;
import ru.windcorp.optica.client.graphics.model.Shape;
import ru.windcorp.optica.client.graphics.model.ShapeRenderHelper;
import ru.windcorp.optica.client.graphics.model.ShapeRenderProgram;
public class FlatRenderProgram extends ShapeRenderProgram {
private static FlatRenderProgram def = null;
public static void init() {
def = new FlatRenderProgram(
new String[] {"FlatDefault.vertex.glsl"},
new String[] {"FlatDefault.fragment.glsl"}
);
}
public static FlatRenderProgram getDefault() {
return def;
}
private static final String FLAT_VERTEX_SHADER_RESOURCE =
"Flat.vertex.glsl";
private static final String FLAT_FRAGMENT_SHADER_RESOURCE =
"Flat.fragment.glsl";
private static final String
MASK_START_UNIFORM_NAME = "maskStart",
MASK_END_UNIFORM_NAME = "maskEnd";
private final Uniform2Int maskStartUniform;
private final Uniform2Int maskEndUniform;
public FlatRenderProgram(
String[] vertexShaderResources,
String[] fragmentShaderResources
) {
super(
attachVertexShader(vertexShaderResources),
attachFragmentShader(fragmentShaderResources)
);
this.maskStartUniform = getUniform(MASK_START_UNIFORM_NAME).as2Int();
this.maskEndUniform = getUniform(MASK_END_UNIFORM_NAME).as2Int();
}
private static String[] attachVertexShader(String[] others) {
return ObjectArrays.concat(FLAT_VERTEX_SHADER_RESOURCE, others);
}
private static String[] attachFragmentShader(String[] others) {
return ObjectArrays.concat(FLAT_FRAGMENT_SHADER_RESOURCE, others);
}
@Override
public void render(ShapeRenderHelper helper, Shape shape) {
if (((FlatRenderHelper) helper).isRenderable()) {
super.render(helper, shape);
}
}
@Override
protected void renderFace(Face face) {
glDisable(GL_CULL_FACE);
super.renderFace(face);
glEnable(GL_CULL_FACE);
}
@Override
protected void configure(ShapeRenderHelper argHelper) {
super.configure(argHelper);
FlatRenderHelper helper = ((FlatRenderHelper) argHelper);
maskStartUniform.set(helper.getStartX(), helper.getStartY());
maskEndUniform.set(helper.getEndX(), helper.getEndY());
}
}

View File

@ -0,0 +1,76 @@
package ru.windcorp.optica.client.graphics.flat;
import org.lwjgl.glfw.GLFW;
import com.google.common.eventbus.Subscribe;
import ru.windcorp.optica.client.graphics.Colors;
import ru.windcorp.optica.client.graphics.backend.GraphicsInterface;
import ru.windcorp.optica.client.graphics.input.KeyEvent;
import ru.windcorp.optica.client.graphics.texture.SimpleTexture;
import ru.windcorp.optica.client.graphics.texture.Sprite;
import ru.windcorp.optica.client.graphics.texture.Texture;
import ru.windcorp.optica.client.graphics.texture.TextureManager;
import ru.windcorp.optica.client.graphics.texture.TextureSettings;
public class LayerTestUI extends AssembledFlatLayer {
public LayerTestUI() {
super("TestUI");
GraphicsInterface.subscribeToInputEvents(this);
}
private boolean flag = false;
@Override
protected void assemble(RenderTarget target) {
final float width = 512 + 256;
final float height = 64;
final float border = 5;
final int boxColor = flag ? 0xEE8888 : 0xEEEE88;
final int borderColor = flag ? 0xAA4444 : 0xAAAA44;
final int boxShadowColor = flag ? 0x440000 : 0x444400;
float x = (getWidth() - width) / 2;
float y = getHeight() - height;
y -= 2*border;
target.fill(x + border, y + border, width, height, boxShadowColor);
target.fill(x - 1, y - 1, width + 2, height + 2, boxShadowColor);
target.fill(x, y, width, height, borderColor);
target.fill(x + border, y + border, width - 2*border, height - 2*border, boxColor);
final float texShadow = 2;
final float texSize = height - 4*border;
target.fill(x + 2*border + texShadow, y + 2*border + texShadow, texSize, texSize, Colors.BLACK);
target.drawTexture(x + 2*border, y + 2*border, texSize, texSize, qtex("compass"));
}
@Subscribe
public void onKeyEvent(KeyEvent event) {
if (event.isRepeat() || event.getKey() != GLFW.GLFW_KEY_LEFT_CONTROL) {
return;
}
flag = event.isPress();
markForReassembly();
}
/*
* TMP texture loader
*/
private static final TextureSettings TEXTURE_SETTINGS =
new TextureSettings(false);
private static Texture qtex(String name) {
return new SimpleTexture(new Sprite(
TextureManager.load(name, TEXTURE_SETTINGS)
));
}
}

View File

@ -0,0 +1,62 @@
package ru.windcorp.optica.client.graphics.flat;
public class Mask {
private int startX;
private int startY;
private int endX;
private int endY;
public Mask(int startX, int startY, int endX, int endY) {
this.startX = startX;
this.startY = startY;
this.endX = endX;
this.endY = endY;
}
public Mask() {}
public int getStartX() {
return startX;
}
public void setStartX(int startX) {
this.startX = startX;
}
public int getStartY() {
return startY;
}
public void setStartY(int startY) {
this.startY = startY;
}
public int getEndX() {
return endX;
}
public void setEndX(int endX) {
this.endX = endX;
}
public int getEndY() {
return endY;
}
public void setEndY(int endY) {
this.endY = endY;
}
public void set(int startX, int startY, int endX, int endY) {
this.startX = startX;
this.startY = startY;
this.endX = endX;
this.endY = endY;
}
public boolean isEmpty() {
return startX >= endX || startY >= endY;
}
}

View File

@ -0,0 +1,166 @@
package ru.windcorp.optica.client.graphics.flat;
import java.util.ArrayList;
import java.util.Deque;
import java.util.LinkedList;
import java.util.List;
import glm.vec._3.Vec3;
import ru.windcorp.optica.client.graphics.Colors;
import ru.windcorp.optica.client.graphics.backend.Usage;
import ru.windcorp.optica.client.graphics.model.Face;
import ru.windcorp.optica.client.graphics.model.Faces;
import ru.windcorp.optica.client.graphics.model.Shape;
import ru.windcorp.optica.client.graphics.model.WorldRenderable;
import ru.windcorp.optica.client.graphics.texture.Texture;
public class RenderTarget {
private static final int MAX_DEPTH = 1 << 16;
private final List<AssembledFlatLayer.Clip> assembled = new ArrayList<>();
private final Deque<Mask> maskStack = new LinkedList<>();
private final List<Face> currentClipFaces = new ArrayList<>();
private int depth = 0;
public RenderTarget() {
reset();
}
public void pushMaskStartEnd(int startX, int startY, int endX, int endY) {
assembleCurrentClipFromFaces();
maskStack.push(intersect(getMask(), startX, startY, endX, endY));
}
private Mask intersect(
Mask current,
int startX, int startY, int endX, int endY
) {
return new Mask(
Math.max(startX, current.getStartX()),
Math.max(startY, current.getStartY()),
Math.min(endX, current.getEndX()),
Math.min(endY, current.getEndY())
);
}
public void pushMask(Mask mask) {
pushMaskStartEnd(
mask.getStartX(), mask.getStartY(),
mask.getEndX(), mask.getEndY()
);
}
public void pushMaskStartSize(int x, int y, int width, int height) {
pushMaskStartEnd(x, y, x + width, y + height);
}
public void popMask() {
assembleCurrentClipFromFaces();
maskStack.pop();
}
public Mask getMask() {
return maskStack.getFirst();
}
protected void assembleCurrentClipFromFaces() {
if (!currentClipFaces.isEmpty()) {
Mask mask = getMask();
if (mask.isEmpty()) {
currentClipFaces.clear();
return;
}
Face[] faces = currentClipFaces.toArray(
new Face[currentClipFaces.size()]
);
currentClipFaces.clear();
Shape shape = new Shape(
Usage.STATIC, FlatRenderProgram.getDefault(), faces
);
assembled.add(new AssembledFlatLayer.Clip(mask, shape));
}
}
public AssembledFlatLayer.Clip[] assemble() {
assembleCurrentClipFromFaces();
AssembledFlatLayer.Clip[] result = assembled.toArray(
new AssembledFlatLayer.Clip[assembled.size()]
);
reset();
return result;
}
private void reset() {
maskStack.clear();
currentClipFaces.clear();
assembled.clear();
maskStack.add(new Mask(0, 0, Integer.MAX_VALUE, Integer.MAX_VALUE));
depth = 0;
}
public void addCustomRenderer(WorldRenderable renderable) {
assembleCurrentClipFromFaces();
Mask mask = getMask();
if (mask.isEmpty()) {
return;
}
assembled.add(new AssembledFlatLayer.Clip(mask, renderable));
}
protected void addFaceToCurrentClip(Face face) {
currentClipFaces.add(face);
}
public void drawTexture(
float x, float y, float width, float height,
int color, Texture texture
) {
float depth = this.depth-- / (float) MAX_DEPTH;
addFaceToCurrentClip(Faces.createRectangle(
FlatRenderProgram.getDefault(),
texture,
createVectorFromRGBInt(color),
new Vec3(x, y + height, depth), // Flip
new Vec3(width, 0, depth),
new Vec3(0, -height, depth)
));
}
public void drawTexture(
float x, float y, float width, float height,
Texture texture
) {
drawTexture(x, y, width, height, Colors.WHITE, texture);
}
public void fill(
float x, float y, float width, float height,
int color
) {
drawTexture(x, y, width, height, color, null);
}
private static Vec3 createVectorFromRGBInt(int rgb) {
int r = (rgb & 0xFF0000) >> 16;
int g = (rgb & 0x00FF00) >> 8;
int b = (rgb & 0x0000FF);
return new Vec3(r / 256f, g / 256f, b / 256f);
}
}

View File

@ -237,7 +237,7 @@ public class Face {
}
public void setTexture(Texture texture) {
this.texture = Objects.requireNonNull(texture, "texture");
this.texture = texture;
}
}

View File

@ -36,10 +36,10 @@ public abstract class Model implements WorldRenderable {
Mat4 transform = getTransform(i);
try {
helper.pushWorldTransform().mul(transform);
helper.pushTransform().mul(transform);
part.render(helper);
} finally {
helper.popWorldTransform();
helper.popTransform();
}
}
}

View File

@ -32,21 +32,21 @@ public class ShapeRenderHelper {
transformStack.push().identity();
}
public Mat4 pushWorldTransform() {
public Mat4 pushTransform() {
Mat4 previous = transformStack.getHead();
return transformStack.push().set(previous);
}
public void popWorldTransform() {
public void popTransform() {
transformStack.removeHead();
}
public Mat4 getWorldTransform() {
public Mat4 getTransform() {
return transformStack.getHead();
}
public Mat4 getFinalTransform() {
return getWorldTransform();
return getTransform();
}
public void reset() {

View File

@ -35,6 +35,7 @@ import ru.windcorp.optica.client.graphics.backend.shaders.Program;
import ru.windcorp.optica.client.graphics.backend.shaders.attributes.*;
import ru.windcorp.optica.client.graphics.backend.shaders.uniforms.*;
import ru.windcorp.optica.client.graphics.texture.Sprite;
import ru.windcorp.optica.client.graphics.texture.Texture;
public class ShapeRenderProgram extends Program {
@ -53,6 +54,7 @@ public class ShapeRenderProgram extends Program {
POSITIONS_ATTRIBUTE_NAME = "inputPositions",
COLOR_MULTIPLER_ATTRIBUTE_NAME = "inputColorMultiplier",
TEXTURE_COORDS_ATTRIBUTE_NAME = "inputTextureCoords",
USE_TEXTURE_UNIFORM_NAME = "useTexture",
TEXTURE_SLOT_UNIFORM_NAME = "textureSlot",
TEXTURE_START_UNIFORM_NAME = "textureStart",
TEXTURE_SIZE_UNIFORM_NAME = "textureSize";
@ -61,6 +63,7 @@ public class ShapeRenderProgram extends Program {
private final AttributeVertexArray positionsAttribute;
private final AttributeVertexArray colorsAttribute;
private final AttributeVertexArray textureCoordsAttribute;
private final Uniform1Int useTextureUniform;
private final Uniform1Int textureSlotUniform;
private final Uniform2Float textureStartUniform;
private final Uniform2Float textureSizeUniform;
@ -90,6 +93,9 @@ public class ShapeRenderProgram extends Program {
this.textureCoordsAttribute =
getAttribute(TEXTURE_COORDS_ATTRIBUTE_NAME).asVertexArray();
this.useTextureUniform = getUniform(USE_TEXTURE_UNIFORM_NAME)
.as1Int();
this.textureSlotUniform = getUniform(TEXTURE_SLOT_UNIFORM_NAME)
.as1Int();
@ -174,13 +180,20 @@ public class ShapeRenderProgram extends Program {
}
protected void renderFace(Face face) {
Sprite sprite = face.getTexture().getSprite();
Texture texture = face.getTexture();
if (texture != null) {
Sprite sprite = texture.getSprite();
sprite.getPrimitive().bind(0);
textureSlotUniform.set(0);
useTextureUniform.set(1);
textureStartUniform.set(sprite.getStart());
textureSizeUniform.set(sprite.getSize());
} else {
useTextureUniform.set(0);
}
GL11.glDrawElements(
GL11.GL_TRIANGLES,

View File

@ -24,7 +24,6 @@ import glm.vec._2.Vec2;
public class Sprite {
private static final Vec2 ORIGIN = new Vec2(0, 0);
private static final Vec2 FULL_PRIMITIVE = new Vec2(1, 1);
private final TexturePrimitive primitive;
@ -38,7 +37,10 @@ public class Sprite {
}
public Sprite(TexturePrimitive primitive) {
this(primitive, ORIGIN, FULL_PRIMITIVE);
this(primitive, ORIGIN, new Vec2(
primitive.getWidth() / (float) primitive.getBufferWidth(),
primitive.getHeight() / (float) primitive.getBufferHeight()
));
}
public TexturePrimitive getPrimitive() {

View File

@ -48,7 +48,7 @@ public class WorldRenderHelper extends ShapeRenderHelper {
@Override
public Mat4 getFinalTransform() {
return finalTransform.set(getViewTransform()).mul(getWorldTransform());
return finalTransform.set(getViewTransform()).mul(getTransform());
}
@Override

View File

@ -96,7 +96,7 @@ public class WorldRenderProgram extends ShapeRenderProgram {
@Override
protected void configure(ShapeRenderHelper helper) {
super.configure(helper);
worldTransformUniform.set(helper.getWorldTransform());
worldTransformUniform.set(helper.getTransform());
}
@Override

View File

@ -75,7 +75,7 @@ public class ChunkRender {
buildModel();
}
renderer.pushWorldTransform().translate(
renderer.pushTransform().translate(
data.getX() * ChunkData.BLOCKS_PER_CHUNK,
data.getY() * ChunkData.BLOCKS_PER_CHUNK,
data.getZ() * ChunkData.BLOCKS_PER_CHUNK
@ -83,7 +83,7 @@ public class ChunkRender {
model.render(renderer);
renderer.popWorldTransform();
renderer.popTransform();
}
private void buildModel() {

View File

@ -56,7 +56,7 @@ public class WorldRender {
}
public void render(ShapeRenderHelper renderer) {
renderer.pushWorldTransform().rotateX(-Math.PI / 2);
renderer.pushTransform().rotateX(-Math.PI / 2);
for (ChunkRender chunk : getChunks()) {
chunk.render(renderer);

View File

@ -0,0 +1,13 @@
#version 120
uniform ivec2 maskStart;
uniform ivec2 maskEnd;
void applyMask() {
if (
gl_FragCoord.x < maskStart.x || gl_FragCoord.x >= maskEnd.x ||
gl_FragCoord.y < maskStart.y || gl_FragCoord.y >= maskEnd.y
) {
discard;
}
}

View File

@ -0,0 +1,5 @@
#version 120
void flatTransferToFragment() {
shapeTransferToFragment();
}

View File

@ -0,0 +1,8 @@
#version 120
void main(void) {
applyTexture();
applyColorMultiplier();
applyAlpha();
applyMask();
}

View File

@ -0,0 +1,6 @@
#version 120
void main(void) {
gl_Position = applyFinalTransform(vec4(inputPositions, 1.0));
flatTransferToFragment();
}

View File

@ -7,7 +7,12 @@ uniform sampler2D textureSlot;
uniform vec2 textureStart;
uniform vec2 textureSize;
uniform bool useTexture;
void applyTexture() {
if (!useTexture) {
gl_FragColor = vec4(1, 1, 1, 1);
} else {
gl_FragColor = texture2D(
textureSlot,
vec2(
@ -15,6 +20,7 @@ void applyTexture() {
varyingTextureCoords[1] * textureSize[1] + textureStart[1]
)
);
}
}
void multiply(inout vec4 vector, float scalar) {

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB