skunkworks/com/danitheskunk/skunkworks/backends/gl/Window.java

373 lines
9.9 KiB
Java

package com.danitheskunk.skunkworks.backends.gl;
import com.danitheskunk.skunkworks.*;
import com.danitheskunk.skunkworks.gfx.IRenderContext;
import com.danitheskunk.skunkworks.gfx.ITexture;
import com.danitheskunk.skunkworks.gfx.Image;
import org.lwjgl.glfw.GLFWErrorCallback;
import org.lwjgl.opengl.GL;
import org.lwjgl.opengl.GL11;
import java.util.ArrayList;
import java.util.List;
import static org.lwjgl.glfw.GLFW.*;
import static org.lwjgl.opengl.GL46.*;
import static org.lwjgl.system.MemoryUtil.NULL;
public class Window extends BaseWindow {
private static String vertexSource = """
#version 450
layout(location = 0) in vec2 pos;
layout(location = 1) in ivec2 texCoord;
layout(location = 2) uniform vec2 windowSize;
layout(location = 1) out vec2 out_texCoord;
void main() {
gl_Position = vec4(pos / windowSize * vec2(2.0f, -2.0f) + vec2(-1.0f, 1.0f), 0.0f, 1.0f);
out_texCoord = texCoord;
}
""";
private static String fragmentSource = """
#version 450
layout(location = 1) in vec2 texCoord;
layout(binding = 0) uniform sampler2D tex;
layout(location = 3) uniform ivec2 texOffset;
layout(location = 4) uniform ivec2 texSize;
layout(location = 5) uniform vec3 tint;
out vec4 color;
void main() {
//color = vec4(vec2(texCoord).x/1000.f, 0.2f, 0.7f, 1.0f);
//color = texture(tex, texCoord);
color = texelFetch(tex, ivec2(mod(texCoord, texSize)) + texOffset, 0) * vec4(tint, 1.0f);
}
""";
private static String vertexSourceScaler = """
#version 450
layout(location = 0) in vec2 pos;
layout(location = 1) in vec2 texCoord;
layout(location = 2) uniform vec2 windowSize;
layout(location = 1) out vec2 out_texCoord;
void main() {
gl_Position = vec4(pos / windowSize * vec2(2.0f, 2.0f) + vec2(-1.0f, -1.0f), 0.0f, 1.0f);
out_texCoord = texCoord;
}
""";
private static String fragmentSourceScaler = """
#version 450
layout(location = 1) in vec2 texCoord;
layout(binding = 0) uniform sampler2D tex;
out vec4 color;
void main() {
//color = vec4(vec2(texCoord).x/1000.f, 0.2f, 0.7f, 1.0f);
color = texture(tex, texCoord);
//color = texelFetch(tex, ivec2(texCoord), 0);
//color = vec4(1.0, 1.0, 1.0, 1.0);
//color = vec4(texCoord.xy, 0.0, 1.0);
}
""";
private final Program program;
private final Program programScaler;
private final RenderContext renderContext;
private final Vec2i size;
private Vec2i windowSize;
private final TextureAtlas textureAtlas;
private final long window;
private boolean debug;
private boolean shouldClose;
private final int framebuffer;
private final int framebufferTex;
public Window(Vec2i size, String title, Engine engine) {
super(engine);
GLFWErrorCallback.createPrint(System.err).set();
if(!glfwInit()) throw new IllegalStateException(
"Unable to initialize GLFW");
glfwDefaultWindowHints();
this.debug = false;
this.size = size;
this.windowSize = size;
window = glfwCreateWindow(size.getX(), size.getY(), title, NULL, NULL);
if(window == NULL) throw new RuntimeException(
"Failed to create GLFW window");
glfwShowWindow(window);
glfwMakeContextCurrent(window);
glfwSwapInterval(1); //vsync
GL.createCapabilities();
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0.0f, size.getX(), size.getY(), 0.0f, 0.0f, 1.0f);
glEnable(GL_COLOR);
glEnable(GL_TEXTURE_2D);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glEnable(GL_BLEND);
glfwSetWindowSizeCallback(window, this::windowSizeCallback);
framebuffer = glGenFramebuffers();
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
framebufferTex = glGenTextures();
glBindTexture(GL_TEXTURE_2D, framebufferTex);
glTexImage2D(GL_TEXTURE_2D,
0,
GL_RGB,
size.getX(),
size.getY(),
0,
GL_RGB,
GL_UNSIGNED_BYTE,
0
);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glFramebufferTexture(GL_FRAMEBUFFER,
GL_COLOR_ATTACHMENT0,
framebufferTex,
0
);
glDrawBuffers(GL_COLOR_ATTACHMENT0);
textureAtlas = new TextureAtlas();
program = new Program(vertexSource, fragmentSource);
programScaler = new Program(vertexSourceScaler, fragmentSourceScaler);
renderContext = new RenderContext(size,
textureAtlas,
program.getAttribLocation("texCoord"),
program.getUniformLocation("texOffset"),
program.getUniformLocation("texSize"),
program.getUniformLocation("tint")
);
glProgramUniform2f(program.program,
program.getUniformLocation("windowSize"),
size.getX(),
size.getY()
);
shouldClose = false;
System.out.println("Skunkworks window initialised");
System.out.print("Maximum Texture Size: ");
System.out.println(GL11.glGetInteger(GL_MAX_TEXTURE_SIZE));
}
private void windowSizeCallback(long window, int width, int height) {
System.out.printf("new window size %d x %d\n", width, height);
windowSize = new Vec2i(width, height);
glViewport(0, 0, width, height);
//glLoadIdentity();
//glOrtho(0.0f, width, height, 0.0f, 0.0f, 1.0f);
}
@Override
public ITexture loadTexture(Image image) {
return textureAtlas.addTexture(image);
}
@Override
public ITexture loadTexture(String path) {
var img = engine.loadImage(path);
return textureAtlas.addTexture(img);
}
@Override
public List<ITexture> loadTextureArray(Image image, Vec2i tileSize) {
var tileCount = Vec2i.div(image.getSize(), tileSize);
var tiles = new ArrayList<ITexture>();
for(int y = 0; y < tileCount.getY(); ++y) {
for(int x = 0; x < tileCount.getX(); ++x) {
var rect = new Recti(Vec2i.mul(new Vec2i(x, y), tileSize),
tileSize
);
var img = image.getSubImage(rect);
tiles.add(loadTexture(img));
}
}
return tiles;
}
@Override
public List<ITexture> loadTextureArray(String path, Vec2i tileSize) {
var img = engine.loadImage(path);
return loadTextureArray(img, tileSize);
}
@Override
public void renderFinish(IRenderContext context) {
if(debug) {
context.drawTextureRectangle(new Recti(Vec2i.ZERO, size),
textureAtlas.getAtlasTexture(),
true
);
}
//glEnd();
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glViewport(0, 0, windowSize.getX(), windowSize.getY());
programScaler.use();
glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glBindTexture(GL_TEXTURE_2D, framebufferTex);
glProgramUniform2f(programScaler.program,
programScaler.getUniformLocation("windowSize"),
windowSize.getX(),
windowSize.getY()
);
int tlx1 = 0;
int tly1 = 0;
int tlx2 = 0;
int tly2 = 0;
switch(this.stretchMode) {
case STRETCH: {
tlx1 = 0;
tly1 = 0;
tlx2 = windowSize.getX();
tly2 = windowSize.getY();
break;
}
case ASPECT: {
float scalex = (float) windowSize.getX() / (float) size.getX();
float scaley = (float) windowSize.getY() / (float) size.getY();
float scale = Math.min(scalex, scaley);
int xoff = (int) (
(windowSize.getX() - size.getX() * scale) / 2
);
int yoff = (int) (
(windowSize.getY() - size.getY() * scale) / 2
);
tlx1 = xoff;
tly1 = yoff;
tlx2 = (int) (xoff + size.getX() * scale);
tly2 = (int) (yoff + size.getY() * scale);
break;
}
case INTEGER: {
int scalex = windowSize.getX() / size.getX();
int scaley = windowSize.getY() / size.getY();
int scale = Math.max(1, Math.min(scalex, scaley));
int xoff = (windowSize.getX() - size.getX() * scale) / 2;
int yoff = (windowSize.getY() - size.getY() * scale) / 2;
tlx1 = xoff;
tly1 = yoff;
tlx2 = xoff + size.getX() * scale;
tly2 = yoff + size.getY() * scale;
break;
}
}
var texCoordIndex = programScaler.getAttribLocation("texCoord");
glBegin(GL_TRIANGLES);
//counterclockwise triangles
glVertexAttrib2f(texCoordIndex, 0, 1);
glVertex2i(tlx1, tly2);
glVertexAttrib2f(texCoordIndex, 1, 0);
glVertex2i(tlx2, tly1);
glVertexAttrib2f(texCoordIndex, 0, 0);
glVertex2i(tlx1, tly1);
glVertexAttrib2f(texCoordIndex, 1, 0);
glVertex2i(tlx2, tly1);
glVertexAttrib2f(texCoordIndex, 0, 1);
glVertex2i(tlx1, tly2);
glVertexAttrib2f(texCoordIndex, 1, 1);
glVertex2i(tlx2, tly2);
glEnd();
glfwSwapBuffers(window);
}
@Override
public IRenderContext renderStart() {
program.use();
textureAtlas.bind();
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
glViewport(0, 0, size.getX(), size.getY());
textureAtlas.update();
glClearColor(0.f, 0.f, 0.f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
program.use();
//glBegin(GL_TRIANGLES);
return renderContext;
}
@Override
public void setDebug(boolean on) {
this.debug = on;
}
@Override
public boolean shouldClose() {
return shouldClose;
}
@Override
public Vec2i getMousePos() {
//todo: scale mouse to stretchMode
double[] x = {0.0};
double[] y = {0.0};
glfwGetCursorPos(window, x, y);
var mousePos = new Vec2i((int) x[0], (int) y[0]);
var scaledMousePos = switch(this.stretchMode) {
case STRETCH -> {
var stretch = Vec2f.div(this.size, this.windowSize);
yield Vec2i.mul(stretch, mousePos);
}
case ASPECT -> {
var scale = Vec2f.div(this.size, this.windowSize);
var scalef = Math.max(scale.getX(), scale.getY());
var off = Vec2f.div(Vec2i.sub(windowSize,
Vec2f.mul(size.toVec2f(), 1.0 / scalef).toVec2i()
).toVec2f(), 2).toVec2i();
yield Vec2i.mul(Vec2i.sub(mousePos, off), 1.0 / scalef);
}
case INTEGER -> {
var scale = Vec2i.div(this.windowSize, this.size);
var scalei = Math.max(1, Math.min(scale.getX(), scale.getY()));
var off = Vec2i.div(Vec2i.sub(windowSize,
Vec2i.mul(size, scalei)
), 2);
yield Vec2i.div(Vec2i.sub(mousePos, off), scalei);
}
};
return Vec2i.max(Vec2i.ZERO,
Vec2i.min(Vec2i.sub(size, new Vec2i(1, 1)), scaledMousePos)
);
}
@Override
public boolean isMouseClicked(int button) {
return false;
}
@Override
public void tick() {
glfwMakeContextCurrent(window);
if(glfwWindowShouldClose(window)) {
shouldClose = true;
return;
}
glfwPollEvents();
}
}