Put the OpenGL to work

This commit is contained in:
2021-01-30 12:21:42 +01:00
parent f480fffc8e
commit 17d8c9406f
16 changed files with 566 additions and 12 deletions

View File

@@ -5,12 +5,14 @@ package com.bartlomiejpluta.samplegame;
import com.bartlomiejpluta.samplegame.core.engine.GameEngine;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@Slf4j
@SpringBootApplication
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class App implements ApplicationRunner {
@@ -18,6 +20,7 @@ public class App implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) {
log.info("Starting game engine");
gameEngine.start();
}

View File

@@ -1,17 +1,23 @@
package com.bartlomiejpluta.samplegame.core.engine;
import com.bartlomiejpluta.samplegame.core.logic.GameLogic;
import com.bartlomiejpluta.samplegame.core.thread.ThreadManager;
import com.bartlomiejpluta.samplegame.core.time.ChronoMeter;
import com.bartlomiejpluta.samplegame.core.ui.Window;
import com.bartlomiejpluta.samplegame.core.ui.WindowManager;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Slf4j
@Component
public class DefaultGameEngine implements GameEngine {
private static final String THREAD_NAME = "Game Main Thread";
private final WindowManager windowManager;
private final ThreadManager threadManager;
private final GameLogic logic;
private final Thread thread;
private final Window window;
@@ -23,16 +29,17 @@ public class DefaultGameEngine implements GameEngine {
@Autowired
public DefaultGameEngine(WindowManager windowManager,
ThreadManager threadManager,
GameLogic logic,
@Value("${app.window.title}") String title,
@Value("${app.window.width}") int width,
@Value("${app.window.height}") int height,
@Value("${app.core.targetUps}") int targetUps) {
this.windowManager = windowManager;
this.threadManager = threadManager;
this.logic = logic;
this.window = windowManager.createWindow(title, width, height);
this.thread = threadManager.createThread(this::run);
this.thread = threadManager.createThread(THREAD_NAME, this::run);
this.chrono = new ChronoMeter();
this.targetUps = targetUps;
}
@@ -47,11 +54,14 @@ public class DefaultGameEngine implements GameEngine {
}
private void init() {
log.info("Initializing game engine");
window.init();
chrono.init();
logic.init(window);
}
private void loop() {
log.info("Starting game loop");
running = true;
var dt = 0.0f;
var accumulator = 0.0f;
@@ -73,19 +83,20 @@ public class DefaultGameEngine implements GameEngine {
}
private void input() {
logic.input(window);
}
private void update(float dt) {
logic.update(dt);
}
private void render() {
window.update();
logic.render(window);
}
private void cleanUp() {
logic.cleanUp();
}
@Override

View File

@@ -4,8 +4,8 @@ public class AppException extends RuntimeException {
public AppException() {
}
public AppException(String message) {
super(message);
public AppException(String message, Object... args) {
super(String.format(message, args));
}
public AppException(String message, Throwable cause) {

View File

@@ -0,0 +1,56 @@
package com.bartlomiejpluta.samplegame.core.gl.render;
import com.bartlomiejpluta.samplegame.core.ui.Window;
import com.bartlomiejpluta.samplegame.core.gl.shader.manager.ShaderManager;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.lwjgl.system.MemoryStack;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import static org.lwjgl.opengl.GL15.*;
import static org.lwjgl.opengl.GL20.glEnableVertexAttribArray;
import static org.lwjgl.opengl.GL20.glVertexAttribPointer;
import static org.lwjgl.opengl.GL30.glBindVertexArray;
import static org.lwjgl.opengl.GL30.glGenVertexArrays;
@Slf4j
@Component
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class DefaultRenderer implements Renderer {
private final ShaderManager shaderManager;
@Override
public void init() {
log.info("Initializing renderer");
shaderManager.createShader("default", "/shaders/default.vs", "/shaders/default.fs");
}
@Override
public void render(Window window) {
clear();
updateViewport(window);
shaderManager.selectShader("default").useSelectedShader();
// here the objects will be rendered with the default shader
shaderManager.detachCurrentShader();
}
private void clear() {
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}
private void updateViewport(Window window) {
if(window.isResized()) {
glViewport(0, 0, window.getWidth(), window.getHeight());
window.setResized(false);
}
}
@Override
public void cleanUp() {
shaderManager.cleanUp();
}
}

View File

@@ -0,0 +1,11 @@
package com.bartlomiejpluta.samplegame.core.gl.render;
import com.bartlomiejpluta.samplegame.core.ui.Window;
public interface Renderer {
void init();
void render(Window window);
void cleanUp();
}

View File

@@ -0,0 +1,138 @@
package com.bartlomiejpluta.samplegame.core.gl.shader.manager;
import com.bartlomiejpluta.samplegame.core.util.res.ResourcesManager;
import com.bartlomiejpluta.samplegame.core.gl.shader.program.ShaderProgram;
import com.bartlomiejpluta.samplegame.core.gl.shader.uniform.Uniform;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.joml.Matrix3f;
import org.joml.Matrix4f;
import org.joml.Vector3f;
import org.joml.Vector4f;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
@Slf4j
@Component
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class DefaultShaderManager implements ShaderManager {
private final ResourcesManager resourcesManager;
private final Map<String, ShaderProgram> shaders = new HashMap<>();
private ShaderProgram current;
@Override
public ShaderManager createShader(String programName, String vertexShaderFilename, String fragmentShaderFilename) {
log.info("Creating {} shader", programName);
var vertexShaderCode = resourcesManager.loadResourceAsString(vertexShaderFilename);
var fragmentShaderCode = resourcesManager.loadResourceAsString(fragmentShaderFilename);
var program = ShaderProgram.compile(vertexShaderCode, fragmentShaderCode);
shaders.put(programName, program);
return this;
}
@Override
public ShaderManager selectShader(String programName) {
current = shaders.get(programName);
return this;
}
@Override
public ShaderManager useSelectedShader() {
current.use();
return this;
}
@Override
public ShaderManager detachCurrentShader() {
current.detach();
return this;
}
@Override
public ShaderManager createUniform(String uniformName) {
current.createUniform(uniformName);
return this;
}
@Override
public ShaderManager createUniform(String uniformName, Uniform uniform) {
current.createUniform(uniformName, uniform);
return this;
}
@Override
public ShaderManager createUniforms(String uniformName, int size) {
current.createUniforms(uniformName, size);
return this;
}
@Override
public ShaderManager createUniforms(String uniformName, int size, Uniform uniform) {
current.createUniforms(uniformName, size, uniform);
return this;
}
@Override
public ShaderManager setUniform(String uniformName, int value) {
current.setUniform(uniformName, value);
return this;
}
@Override
public ShaderManager setUniform(String uniformName, float value) {
current.setUniform(uniformName, value);
return this;
}
@Override
public ShaderManager setUniform(String uniformName, Vector3f value) {
current.setUniform(uniformName, value);
return this;
}
@Override
public ShaderManager setUniform(String uniformName, Vector4f value) {
current.setUniform(uniformName, value);
return this;
}
@Override
public ShaderManager setUniform(String uniformName, Matrix3f value) {
current.setUniform(uniformName, value);
return this;
}
@Override
public ShaderManager setUniform(String uniformName, Matrix4f value) {
current.setUniform(uniformName, value);
return this;
}
@Override
public ShaderManager setUniform(String uniformName, Uniform uniform) {
current.setUniform(uniformName, uniform);
return this;
}
@Override
public ShaderManager setUniform(String uniformName, int index, Uniform uniform) {
current.setUniform(uniformName, index, uniform);
return this;
}
@Override
public ShaderManager setUniforms(String uniformName, Uniform[] uniforms) {
current.setUniforms(uniformName, uniforms);
return this;
}
@Override
public void cleanUp() {
shaders.forEach((name, program) -> program.cleanUp());
}
}

View File

@@ -0,0 +1,57 @@
package com.bartlomiejpluta.samplegame.core.gl.shader.manager;
import com.bartlomiejpluta.samplegame.core.gl.shader.uniform.Uniform;
import org.joml.Matrix3f;
import org.joml.Matrix4f;
import org.joml.Vector3f;
import org.joml.Vector4f;
public interface ShaderManager {
ShaderManager createShader(String programName, String vertexShaderFilename, String fragmentShaderFilename);
ShaderManager selectShader(String programName);
ShaderManager useSelectedShader();
ShaderManager detachCurrentShader();
ShaderManager createUniform(String uniformName);
ShaderManager createUniform(String uniformName, Uniform uniform);
ShaderManager createUniforms(String uniformName, int size);
ShaderManager createUniforms(String uniformName, int size, Uniform uniform);
ShaderManager setUniform(String uniformName, int value);
ShaderManager setUniform(String uniformName, float value);
ShaderManager setUniform(String uniformName, Vector3f value);
ShaderManager setUniform(String uniformName, Vector4f value);
ShaderManager setUniform(String uniformName, Matrix3f value);
ShaderManager setUniform(String uniformName, Matrix4f value);
ShaderManager setUniform(String uniformName, Uniform uniform);
ShaderManager setUniform(String uniformName, int index, Uniform uniform);
ShaderManager setUniforms(String uniformName, Uniform[] uniforms);
void cleanUp();
}

View File

@@ -0,0 +1,168 @@
package com.bartlomiejpluta.samplegame.core.gl.shader.program;
import com.bartlomiejpluta.samplegame.core.error.AppException;
import com.bartlomiejpluta.samplegame.core.gl.shader.uniform.Uniform;
import lombok.extern.slf4j.Slf4j;
import org.joml.Matrix3f;
import org.joml.Matrix4f;
import org.joml.Vector3f;
import org.joml.Vector4f;
import org.lwjgl.system.MemoryStack;
import java.util.HashMap;
import java.util.Map;
import static java.lang.String.format;
import static org.lwjgl.opengl.GL20.*;
@Slf4j
public class ShaderProgram {
private final int programId;
private final int vertexShaderId;
private final int fragmentShaderId;
private final Map<String, Integer> uniforms = new HashMap<>();
private ShaderProgram(String vertexShaderCode, String fragmentShaderCode) {
this.programId = glCreateProgram();
if(this.programId == 0) {
throw new AppException("Could not create shader program");
}
this.vertexShaderId = createShader(vertexShaderCode, GL_VERTEX_SHADER);
this.fragmentShaderId = createShader(fragmentShaderCode, GL_FRAGMENT_SHADER);
linkProgram();
}
private int createShader(String shaderCode, int shaderType) {
int shaderId = glCreateShader(shaderType);
if(shaderId == 0) {
throw new AppException("Could not create shader of type: %s", shaderType);
}
glShaderSource(shaderId, shaderCode);
glCompileShader(shaderId);
if(glGetShaderi(shaderId, GL_COMPILE_STATUS) == 0) {
throw new AppException("Could not compile shader code: %s", glGetShaderInfoLog(shaderId, 1024));
}
glAttachShader(programId, shaderId);
return shaderId;
}
private void linkProgram() {
glLinkProgram(programId);
if(glGetProgrami(programId, GL_LINK_STATUS) == 0) {
throw new AppException("Could not link shader program: %s", glGetProgramInfoLog(programId, 1024));
}
if(vertexShaderId != 0) {
glDetachShader(programId, vertexShaderId);
}
if(fragmentShaderId != 0) {
glDetachShader(programId, fragmentShaderId);
}
glValidateProgram(programId);
if(glGetProgrami(programId, GL_VALIDATE_STATUS) == 0) {
log.warn("Program validation failed: {}", glGetProgramInfoLog(programId, 1024));
}
}
public void createUniform(String uniformName) {
int location = glGetUniformLocation(programId, uniformName);
if(location < 0) {
throw new AppException("Could not find uniform: %s", uniformName);
}
uniforms.put(uniformName, location);
}
public void createUniform(String uniformName, Uniform uniform) {
uniform.createUniform(this, uniformName);
}
public void createUniforms(String uniformName, int size) {
for(int i=0; i<size; ++i) {
createUniform(format("%s[%d]", uniformName, i));
}
}
public void createUniforms(String uniformName, int size, Uniform uniform) {
for(int i=0; i<size; ++i) {
createUniform(format("%s[%d]", uniformName, i), uniform);
}
}
public void setUniform(String uniformName, int value) {
glUniform1i(uniforms.get(uniformName), value);
}
public void setUniform(String uniformName, float value) {
glUniform1f(uniforms.get(uniformName), value);
}
public void setUniform(String uniformName, Vector3f value) {
glUniform3f(uniforms.get(uniformName), value.x, value.y, value.z);
}
public void setUniform(String uniformName, Vector4f value) {
glUniform4f(uniforms.get(uniformName), value.x, value.y, value.z, value.w);
}
public void setUniform(String uniformName, Matrix3f value) {
try(var stack = MemoryStack.stackPush()) {
var buffer = stack.mallocFloat(3 * 3);
value.get(buffer);
glUniformMatrix4fv(uniforms.get(uniformName), false, buffer);
}
}
public void setUniform(String uniformName, Matrix4f value) {
try(var stack = MemoryStack.stackPush()) {
var buffer = stack.mallocFloat(4 * 4);
value.get(buffer);
glUniformMatrix4fv(uniforms.get(uniformName), false, buffer);
}
}
public void setUniform(String uniformName, Uniform uniform) {
uniform.setUniform(this, uniformName);
}
public void setUniform(String uniformName, int index, Uniform uniform) {
setUniform(format("%s[%d]", uniformName, index), uniform);
}
public void setUniforms(String uniformName, Uniform[] uniforms) {
var size = uniforms != null ? uniforms.length : 0;
for(int i=0; i<size; ++i) {
setUniform(format("%s[%d]", uniformName, i), uniforms[i]);
}
}
public void use() {
glUseProgram(programId);
}
public void detach() {
glUseProgram(0);
}
public void cleanUp() {
glUseProgram(0);
if(programId != 0) {
glDeleteProgram(programId);
}
}
public static ShaderProgram compile(String vertexShaderCode, String fragmentShaderCode) {
return new ShaderProgram(vertexShaderCode, fragmentShaderCode);
}
}

View File

@@ -0,0 +1,9 @@
package com.bartlomiejpluta.samplegame.core.gl.shader.uniform;
import com.bartlomiejpluta.samplegame.core.gl.shader.program.ShaderProgram;
public interface Uniform {
void createUniform(ShaderProgram shaderProgram, String uniformName);
void setUniform(ShaderProgram shaderProgram, String uniformName);
}

View File

@@ -0,0 +1,15 @@
package com.bartlomiejpluta.samplegame.core.logic;
import com.bartlomiejpluta.samplegame.core.ui.Window;
public interface GameLogic {
void init(Window window);
void input(Window window);
void update(float dt);
void render(Window window);
void cleanUp();
}

View File

@@ -4,7 +4,7 @@ import org.springframework.stereotype.Component;
@Component
public class ThreadManager {
public Thread createThread(Runnable runnable) {
return new Thread(runnable);
public Thread createThread(String name, Runnable runnable) {
return new Thread(runnable, name);
}
}

View File

@@ -3,6 +3,8 @@ package com.bartlomiejpluta.samplegame.core.ui;
import com.bartlomiejpluta.samplegame.core.error.AppException;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
import org.lwjgl.glfw.GLFWErrorCallback;
import org.lwjgl.glfw.GLFWVidMode;
import org.lwjgl.opengl.GL;
@@ -15,9 +17,16 @@ import static org.lwjgl.system.MemoryUtil.NULL;
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class Window {
private final String title;
private int width;
private int height;
private long windowHandle;
@Getter
private int width;
@Getter
private int height;
@Getter
@Setter
private boolean resized;
public void init() {
@@ -102,6 +111,6 @@ public class Window {
}
public static Window create(String title, int width, int height) {
return new Window(title, width, height, -1, false);
return new Window(title, -1, width, height, false);
}
}

View File

@@ -0,0 +1,19 @@
package com.bartlomiejpluta.samplegame.core.util.res;
import com.bartlomiejpluta.samplegame.core.error.AppException;
import org.springframework.stereotype.Component;
import java.io.InputStream;
import java.util.Scanner;
@Component
public class ResourcesManager {
public String loadResourceAsString(String fileName) {
try (InputStream in = ResourcesManager.class.getResourceAsStream(fileName);
Scanner scanner = new Scanner(in, java.nio.charset.StandardCharsets.UTF_8.name())) {
return scanner.useDelimiter("\\A").next();
} catch (Exception e) {
throw new AppException(e);
}
}
}

View File

@@ -0,0 +1,42 @@
package com.bartlomiejpluta.samplegame.game.logic;
import com.bartlomiejpluta.samplegame.core.gl.render.Renderer;
import com.bartlomiejpluta.samplegame.core.logic.GameLogic;
import com.bartlomiejpluta.samplegame.core.ui.Window;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Slf4j
@Component
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class DefaultGameLogic implements GameLogic {
private final Renderer renderer;
@Override
public void init(Window window) {
log.info("Initializing game logic");
renderer.init();
}
@Override
public void input(Window window) {
}
@Override
public void update(float dt) {
}
@Override
public void render(Window window) {
renderer.render(window);
}
@Override
public void cleanUp() {
renderer.cleanUp();
}
}

View File

@@ -0,0 +1,8 @@
#version 330
out vec4 fragColor;
void main()
{
fragColor = vec4(0.0, 0.5, 0.5, 1.0);
}

View File

@@ -0,0 +1,8 @@
#version 330
layout(location=0) in vec3 position;
void main()
{
gl_Position = vec4(position, 1.0);
}