373 lines
9.9 KiB
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();
|
|
}
|
|
}
|