Compare commits
2 commits
c07be8ad62
...
e19ee6d867
Author | SHA1 | Date | |
---|---|---|---|
e19ee6d867 | |||
2891fd73ed |
157 changed files with 2224 additions and 22271 deletions
56
.gitignore
vendored
56
.gitignore
vendored
|
@ -1,56 +0,0 @@
|
|||
.gradle
|
||||
build/
|
||||
!gradle/wrapper/gradle-wrapper.jar
|
||||
!**/src/main/**/build/
|
||||
!**/src/test/**/build/
|
||||
|
||||
### IntelliJ IDEA ###
|
||||
/.idea/workspace.xml
|
||||
/.idea/usage.statistics.xml
|
||||
/.idea/uiDesigner.xml
|
||||
*.iws
|
||||
*.iml
|
||||
*.ipr
|
||||
out/
|
||||
!**/src/main/**/out/
|
||||
!**/src/test/**/out/
|
||||
|
||||
### Eclipse ###
|
||||
.apt_generated
|
||||
.classpath
|
||||
.factorypath
|
||||
.project
|
||||
.settings
|
||||
.springBeans
|
||||
.sts4-cache
|
||||
bin/
|
||||
!**/src/main/**/bin/
|
||||
!**/src/test/**/bin/
|
||||
|
||||
### NetBeans ###
|
||||
/nbproject/private/
|
||||
/nbbuild/
|
||||
/dist/
|
||||
/nbdist/
|
||||
/.nb-gradle/
|
||||
|
||||
### VS Code ###
|
||||
.vscode/
|
||||
|
||||
### OTHER ###
|
||||
/wiki
|
||||
.DS_Store
|
||||
*.bat
|
||||
*.conf
|
||||
*.dll
|
||||
*.dylib
|
||||
*.jar
|
||||
*.jpg
|
||||
*.mhr
|
||||
*.ogg
|
||||
*.sh
|
||||
*.so
|
||||
*.ttf
|
||||
*.wav
|
||||
*.zip
|
||||
touch.txt
|
|
@ -1,6 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="CompilerConfiguration">
|
||||
<bytecodeTargetLevel target="16" />
|
||||
</component>
|
||||
</project>
|
|
@ -1,15 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="GradleSettings">
|
||||
<option name="linkedExternalProjectsSettings">
|
||||
<GradleProjectSettings>
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
<option name="modules">
|
||||
<set>
|
||||
<option value="$PROJECT_DIR$" />
|
||||
</set>
|
||||
</option>
|
||||
</GradleProjectSettings>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
|
@ -1,20 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="RemoteRepositoriesConfiguration">
|
||||
<remote-repository>
|
||||
<option name="id" value="central" />
|
||||
<option name="name" value="Maven Central repository" />
|
||||
<option name="url" value="https://repo1.maven.org/maven2" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="jboss.community" />
|
||||
<option name="name" value="JBoss Community repository" />
|
||||
<option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="MavenRepo" />
|
||||
<option name="name" value="MavenRepo" />
|
||||
<option name="url" value="https://repo.maven.apache.org/maven2/" />
|
||||
</remote-repository>
|
||||
</component>
|
||||
</project>
|
|
@ -1,8 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||
<component name="FrameworkDetectionExcludesConfiguration">
|
||||
<file type="web" url="file://$PROJECT_DIR$" />
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_16" project-jdk-name="corretto-16" project-jdk-type="JavaSDK" />
|
||||
</project>
|
|
@ -1,6 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
0
.gitattributes → Engine/.gitattributes
vendored
0
.gitattributes → Engine/.gitattributes
vendored
7
Engine/.gitignore
vendored
Normal file
7
Engine/.gitignore
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
.gradle/
|
||||
gradle/
|
||||
gradlew
|
||||
gradlew.bat
|
||||
build/
|
||||
|
||||
.idea/
|
53
Engine/build.gradle.kts
Normal file
53
Engine/build.gradle.kts
Normal file
|
@ -0,0 +1,53 @@
|
|||
plugins {
|
||||
java
|
||||
id("com.gradleup.shadow") version "8.3.0"
|
||||
}
|
||||
|
||||
subprojects {
|
||||
apply(plugin = "java")
|
||||
apply(plugin = "com.gradleup.shadow")
|
||||
java {
|
||||
toolchain {
|
||||
languageVersion = JavaLanguageVersion.of(22)
|
||||
}
|
||||
withSourcesJar()
|
||||
withJavadocJar()
|
||||
}
|
||||
}
|
||||
|
||||
val lwjglVersion = "3.3.4"
|
||||
val jomlVersion = "1.10.7"
|
||||
val jomlPrimitivesVersion = "1.10.0"
|
||||
|
||||
val lwjglNatives = Pair(
|
||||
System.getProperty("os.name")!!,
|
||||
System.getProperty("os.arch")!!
|
||||
).let { (name, arch) ->
|
||||
when {
|
||||
arrayOf("Linux", "SunOS", "Unit").any { name.startsWith(it) } ->
|
||||
if (arrayOf("arm", "aarch64").any { arch.startsWith(it) })
|
||||
"natives-linux${if (arch.contains("64") || arch.startsWith("armv8")) "-arm64" else "-arm32"}"
|
||||
else if (arch.startsWith("ppc"))
|
||||
"natives-linux-ppc64le"
|
||||
else if (arch.startsWith("riscv"))
|
||||
"natives-linux-riscv64"
|
||||
else
|
||||
"natives-linux"
|
||||
|
||||
arrayOf("Mac OS X", "Darwin").any { name.startsWith(it) } ->
|
||||
"natives-macos${if (arch.startsWith("aarch64")) "-arm64" else ""}"
|
||||
|
||||
arrayOf("Windows").any { name.startsWith(it) } ->
|
||||
if (arch.contains("64"))
|
||||
"natives-windows${if (arch.startsWith("aarch64")) "-arm64" else ""}"
|
||||
else
|
||||
"natives-windows-x86"
|
||||
|
||||
else ->
|
||||
throw Error("Unrecognized or unsupported platform. Please set \"lwjglNatives\" manually")
|
||||
}
|
||||
}
|
||||
extra["lwjglVersion"] = lwjglVersion
|
||||
extra["jomlVersion"] = jomlVersion
|
||||
extra["jomlPrimitivesVersion"] = jomlPrimitivesVersion
|
||||
extra["lwjglNatives"] = lwjglNatives
|
7
Engine/settings.gradle.kts
Normal file
7
Engine/settings.gradle.kts
Normal file
|
@ -0,0 +1,7 @@
|
|||
rootProject.name = "Engine"
|
||||
include(":core")
|
||||
project(":core").projectDir = file("src/core")
|
||||
include(":opengl")
|
||||
project(":opengl").projectDir = file("src/opengl")
|
||||
include(":rendering")
|
||||
project(":rendering").projectDir = file("src/rendering")
|
59
Engine/src/core/build.gradle.kts
Normal file
59
Engine/src/core/build.gradle.kts
Normal file
|
@ -0,0 +1,59 @@
|
|||
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
plugins {
|
||||
java
|
||||
id("com.gradleup.shadow") version "8.3.0"
|
||||
}
|
||||
|
||||
val lwjglVersion: String by rootProject.extra
|
||||
val jomlVersion: String by rootProject.extra
|
||||
val jomlPrimitivesVersion: String by rootProject.extra
|
||||
val lwjglNatives: String by rootProject.extra
|
||||
|
||||
sourceSets {
|
||||
val main by getting {
|
||||
java.srcDir("java")
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
annotationProcessor("org.projectlombok:lombok:1.18.34")
|
||||
implementation("org.projectlombok:lombok:1.18.34")
|
||||
annotationProcessor("javax.annotation:javax.annotation-api:1.3.2")
|
||||
implementation("javax.annotation:javax.annotation-api:1.3.2")
|
||||
|
||||
implementation(platform("org.lwjgl:lwjgl-bom:$lwjglVersion"))
|
||||
implementation("org.lwjgl", "lwjgl")
|
||||
runtimeOnly("org.lwjgl", "lwjgl", classifier = lwjglNatives)
|
||||
|
||||
implementation("org.lwjgl", "lwjgl-glfw")
|
||||
runtimeOnly("org.lwjgl", "lwjgl-glfw", classifier = lwjglNatives)
|
||||
|
||||
implementation("org.lwjgl", "lwjgl-stb")
|
||||
runtimeOnly("org.lwjgl", "lwjgl-stb", classifier = lwjglNatives)
|
||||
|
||||
implementation("org.joml", "joml", jomlVersion)
|
||||
implementation("org.joml", "joml-primitives", jomlPrimitivesVersion)
|
||||
|
||||
shadow(localGroovy())
|
||||
shadow(gradleApi())
|
||||
|
||||
//implementation("org.lwjgl", "lwjgl-nuklear")
|
||||
//implementation("org.lwjgl", "lwjgl-openal")
|
||||
//implementation("org.lwjgl", "lwjgl-remotery")
|
||||
//runtimeOnly("org.lwjgl", "lwjgl-nuklear", classifier = lwjglNatives)
|
||||
//runtimeOnly("org.lwjgl", "lwjgl-openal", classifier = lwjglNatives)
|
||||
//runtimeOnly("org.lwjgl", "lwjgl-remotery", classifier = lwjglNatives)
|
||||
}
|
||||
|
||||
tasks.named<ShadowJar>("shadowJar") {
|
||||
archiveFileName.set("dev.euph.engine.core.jar")
|
||||
}
|
||||
|
||||
configurations {
|
||||
shadow
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
package dev.euph.engine.core.data;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
@Getter
|
||||
@EqualsAndHashCode
|
||||
public class Archetype<T> {
|
||||
private final Set<Class<? extends T>> componentClasses;
|
||||
|
||||
@SafeVarargs
|
||||
public Archetype(Class<? extends T>... componentClasses) {
|
||||
this.componentClasses = new HashSet<>(Arrays.asList(componentClasses));
|
||||
}
|
||||
|
||||
public Archetype(Set<Class<? extends T>> componentClasses) {
|
||||
this.componentClasses = new HashSet<>(componentClasses);
|
||||
}
|
||||
|
||||
public Archetype(List<Class<? extends T>> componentClasses) {
|
||||
this.componentClasses = new HashSet<>(componentClasses);
|
||||
}
|
||||
|
||||
public boolean contains(Class<? extends T> componentClass) {
|
||||
for (Class<? extends T> _componentClass : componentClasses) {
|
||||
if (componentClass.isAssignableFrom(_componentClass)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
package dev.euph.engine.core.data;
|
||||
|
||||
import org.lwjgl.BufferUtils;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
|
||||
import static org.lwjgl.stb.STBImage.*;
|
||||
|
||||
|
||||
public class ImageLoader {
|
||||
public record Image(ByteBuffer data, int width, int height) {
|
||||
}
|
||||
|
||||
public static Image loadImage(String path) {
|
||||
IntBuffer width = BufferUtils.createIntBuffer(1);
|
||||
IntBuffer height = BufferUtils.createIntBuffer(1);
|
||||
IntBuffer channels = BufferUtils.createIntBuffer(1);
|
||||
ByteBuffer imageBuffer = stbi_load(path, width, height, channels, 0);
|
||||
if(imageBuffer == null) {
|
||||
throw new RuntimeException("Failed to load image: " + stbi_failure_reason());
|
||||
}
|
||||
Image image = new Image(imageBuffer, width.get(0), height.get(0));
|
||||
stbi_image_free(imageBuffer);
|
||||
return image;
|
||||
}
|
||||
}
|
|
@ -1,16 +1,14 @@
|
|||
package dev.euph.engine.datastructs.octree;
|
||||
package dev.euph.engine.core.data.octree;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.joml.Vector3f;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@AllArgsConstructor
|
||||
public class Octree<T> {
|
||||
private final OctreeNode<T> root;
|
||||
|
||||
public Octree(int maxCapacity, Vector3f center, float halfSize) {
|
||||
root = new OctreeNode<T>(maxCapacity, center, halfSize);
|
||||
}
|
||||
|
||||
public void insert(Vector3f position, T data) {
|
||||
root.insert(position, data);
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
package dev.euph.engine.datastructs.octree;
|
||||
package dev.euph.engine.core.data.octree;
|
||||
|
||||
import lombok.Getter;
|
||||
import org.joml.Vector3f;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
@ -7,15 +8,13 @@ import java.util.HashMap;
|
|||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static java.lang.Math.max;
|
||||
import static java.lang.String.join;
|
||||
|
||||
public class OctreeNode<T> {
|
||||
private final int maxCapacity;
|
||||
private final Vector3f center;
|
||||
private final float halfSize;
|
||||
private final List<Vector3f> positions;
|
||||
private final Map<Vector3f, T> dataMap;
|
||||
@Getter
|
||||
private OctreeNode<T>[] children;
|
||||
|
||||
public OctreeNode(int maxCapacity, Vector3f center, float halfSize) {
|
||||
|
@ -28,27 +27,23 @@ public class OctreeNode<T> {
|
|||
}
|
||||
|
||||
public void insert(Vector3f position, T data) {
|
||||
// Check if the item fits within this node's region
|
||||
if (isOutOfBounds(position)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If the node is not subdivided, add the item to its data list and map
|
||||
if (children == null) {
|
||||
positions.add(position);
|
||||
dataMap.put(position, data);
|
||||
|
||||
// Subdivide if the capacity is exceeded
|
||||
if (positions.size() > maxCapacity) {
|
||||
subdivide();
|
||||
}
|
||||
} else {
|
||||
// Otherwise, insert the item into the appropriate child node
|
||||
return;
|
||||
}
|
||||
|
||||
for (OctreeNode<T> child : children) {
|
||||
child.insert(position, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public List<T> query(Vector3f position, float radius) {
|
||||
List<T> result = new ArrayList<>();
|
||||
|
@ -57,21 +52,20 @@ public class OctreeNode<T> {
|
|||
return result;
|
||||
}
|
||||
|
||||
// Add data items from this node if their positions are within the query radius
|
||||
for (Vector3f itemPosition : positions) {
|
||||
if (itemPosition.distance(position) <= radius) {
|
||||
result.add(dataMap.get(itemPosition));
|
||||
}
|
||||
}
|
||||
|
||||
// Recursively query child nodes
|
||||
if (children != null) {
|
||||
if (children == null) {
|
||||
return result;
|
||||
}
|
||||
for (OctreeNode<T> child : children) {
|
||||
result.addAll(child.query(position, radius));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
private boolean isOutOfBounds(Vector3f position) {
|
||||
|
@ -98,7 +92,6 @@ public class OctreeNode<T> {
|
|||
|
||||
float quarterSize = halfSize / 2.0f;
|
||||
|
||||
// Calculate centers for each child node
|
||||
Vector3f[] childCenters = new Vector3f[]{
|
||||
new Vector3f(center.x - quarterSize, center.y - quarterSize, center.z - quarterSize),
|
||||
new Vector3f(center.x + quarterSize, center.y - quarterSize, center.z - quarterSize),
|
||||
|
@ -110,18 +103,14 @@ public class OctreeNode<T> {
|
|||
new Vector3f(center.x + quarterSize, center.y + quarterSize, center.z + quarterSize)
|
||||
};
|
||||
|
||||
// Create and initialize child nodes
|
||||
for (int i = 0; i < 8; i++) {
|
||||
childNodes[i] = new OctreeNode<>(maxCapacity, childCenters[i], quarterSize);
|
||||
}
|
||||
children = childNodes; // Assign the initialized children array to the class field
|
||||
children = childNodes;
|
||||
|
||||
// Redistribute positions and data from this node to children
|
||||
for (int i = positions.size() - 1; i >= 0; i--) {
|
||||
Vector3f position = positions.get(i);
|
||||
T data = dataMap.remove(position); // Remove data from the current node
|
||||
|
||||
// Find the child that contains the position and add data to it
|
||||
T data = dataMap.remove(position);
|
||||
for (OctreeNode<T> child : children) {
|
||||
if (!child.isOutOfBounds(position)) {
|
||||
child.insert(position, data);
|
||||
|
@ -129,7 +118,6 @@ public class OctreeNode<T> {
|
|||
}
|
||||
}
|
||||
|
||||
// Remove the position from the current node
|
||||
positions.remove(i);
|
||||
}
|
||||
}
|
||||
|
@ -146,8 +134,4 @@ public class OctreeNode<T> {
|
|||
return allPositions;
|
||||
}
|
||||
|
||||
public OctreeNode<T>[] getChildren() {
|
||||
return children;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package dev.euph.engine.datastructs.pipeline;
|
||||
package dev.euph.engine.core.data.pipeline;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
|
@ -10,12 +10,15 @@ public class Pipeline<Input, Output> {
|
|||
public Pipeline() {
|
||||
this.pipelineStages = new ArrayList<>();
|
||||
}
|
||||
|
||||
public Pipeline(PipelineStage<Input, Output> pipelineStage) {
|
||||
pipelineStages = Collections.singletonList(pipelineStage);
|
||||
}
|
||||
|
||||
private Pipeline(Collection<PipelineStage<?, ?>> pipelineStages) {
|
||||
this.pipelineStages = new ArrayList<>(pipelineStages);
|
||||
}
|
||||
|
||||
public <NewOutput> Pipeline<Input, NewOutput> addStage(PipelineStage<Output, NewOutput> pipelineStage) {
|
||||
final ArrayList<PipelineStage<?, ?>> newPipelineStages = new ArrayList<>(pipelineStages);
|
||||
newPipelineStages.add(pipelineStage);
|
|
@ -1,15 +1,18 @@
|
|||
package dev.euph.engine.datastructs.pipeline;
|
||||
package dev.euph.engine.core.data.pipeline;
|
||||
|
||||
public class PipelineRuntimeException extends RuntimeException {
|
||||
public PipelineRuntimeException(String message) {
|
||||
super("Pipeline Runtime Exception: " + message);
|
||||
}
|
||||
|
||||
public PipelineRuntimeException(String message, Throwable cause) {
|
||||
super("Pipeline Runtime Exception: " + message, cause);
|
||||
}
|
||||
|
||||
public PipelineRuntimeException(Throwable cause) {
|
||||
super("Pipeline Runtime Exception: " + cause);
|
||||
}
|
||||
|
||||
protected PipelineRuntimeException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
|
||||
super("Pipeline Runtime Exception: " + message, cause, enableSuppression, writableStackTrace);
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package dev.euph.engine.datastructs.pipeline;
|
||||
package dev.euph.engine.core.data.pipeline;
|
||||
|
||||
public interface PipelineStage<Input, Output> {
|
||||
Output execute(Input input);
|
28
Engine/src/core/java/dev/euph/engine/core/ecs/Component.java
Normal file
28
Engine/src/core/java/dev/euph/engine/core/ecs/Component.java
Normal file
|
@ -0,0 +1,28 @@
|
|||
package dev.euph.engine.core.ecs;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public abstract class Component {
|
||||
|
||||
@Getter
|
||||
protected Entity entity = null;
|
||||
protected List<Class<? extends Component>> requiredComponents = new ArrayList<>();
|
||||
|
||||
public void OnReady() {
|
||||
}
|
||||
|
||||
public void OnUpdate(float deltaTime) {
|
||||
}
|
||||
|
||||
public void OnDestroy() {
|
||||
}
|
||||
|
||||
protected boolean hasRequiredComponents(Entity entity) {
|
||||
return requiredComponents
|
||||
.stream()
|
||||
.allMatch(requiredClass -> entity.getComponent(requiredClass) != null);
|
||||
}
|
||||
}
|
87
Engine/src/core/java/dev/euph/engine/core/ecs/Entity.java
Normal file
87
Engine/src/core/java/dev/euph/engine/core/ecs/Entity.java
Normal file
|
@ -0,0 +1,87 @@
|
|||
package dev.euph.engine.core.ecs;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class Entity {
|
||||
@Getter
|
||||
private final String name;
|
||||
@Getter
|
||||
private final List<Component> components;
|
||||
private Scene scene;
|
||||
|
||||
public Entity(String name) {
|
||||
this.name = name;
|
||||
components = new ArrayList<>();
|
||||
}
|
||||
|
||||
public <T extends Component> Entity addComponent(T component) {
|
||||
|
||||
if (!component.hasRequiredComponents(this)) {
|
||||
throw new IllegalArgumentException("Cannot add Component. Missing required Components.");
|
||||
}
|
||||
|
||||
this.components.add(component);
|
||||
if (this.scene != null) {
|
||||
this.scene.updateEntityComponents(this);
|
||||
}
|
||||
|
||||
if (component.entity != null) {
|
||||
component.entity.removeComponent(component.getClass());
|
||||
}
|
||||
component.entity = this;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public <T extends Component> T getComponent(Class<T> componentClass) {
|
||||
for (Component c : components) {
|
||||
if (componentClass.isAssignableFrom(c.getClass())) {
|
||||
try {
|
||||
return componentClass.cast(c);
|
||||
} catch (ClassCastException e) {
|
||||
e.printStackTrace();
|
||||
assert false : "Error: Casting component.";
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public <T extends Component> void removeComponent(Class<T> componentClass) {
|
||||
for (int i = 0; i < components.size(); i++) {
|
||||
if (componentClass.isAssignableFrom(components.get(i).getClass())) {
|
||||
components.remove(i);
|
||||
if (this.scene != null) {
|
||||
this.scene.updateEntityComponents(this);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ready() {
|
||||
for (Component component : components) {
|
||||
component.OnReady();
|
||||
}
|
||||
}
|
||||
|
||||
public void update(float deltaTime) {
|
||||
for (Component component : components) {
|
||||
component.OnUpdate(deltaTime);
|
||||
}
|
||||
}
|
||||
|
||||
public void destroy() {
|
||||
scene.removeEntity(this);
|
||||
components.forEach(Component::OnDestroy);
|
||||
}
|
||||
|
||||
public Entity addToScene(Scene scene) {
|
||||
scene.addEntity(this);
|
||||
this.scene = scene;
|
||||
return this;
|
||||
}
|
||||
}
|
12
Engine/src/core/java/dev/euph/engine/core/ecs/Requires.java
Normal file
12
Engine/src/core/java/dev/euph/engine/core/ecs/Requires.java
Normal file
|
@ -0,0 +1,12 @@
|
|||
package dev.euph.engine.core.ecs;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.TYPE)
|
||||
public @interface Requires {
|
||||
Class<? extends Component>[] value();
|
||||
}
|
101
Engine/src/core/java/dev/euph/engine/core/ecs/Scene.java
Normal file
101
Engine/src/core/java/dev/euph/engine/core/ecs/Scene.java
Normal file
|
@ -0,0 +1,101 @@
|
|||
package dev.euph.engine.core.ecs;
|
||||
|
||||
|
||||
import dev.euph.engine.core.data.Archetype;
|
||||
import dev.euph.engine.core.ecs.storages.ComponentClassesBySuperclassStorage;
|
||||
import dev.euph.engine.core.ecs.storages.ComponentsByTypeStorage;
|
||||
import dev.euph.engine.core.ecs.storages.EntitiesByArchetypeStorage;
|
||||
import dev.euph.engine.core.ecs.storages.EntitiesByNameStorage;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class Scene {
|
||||
@Getter
|
||||
private final Set<Entity> entities = new HashSet<>();
|
||||
private final EntitiesByNameStorage entitiesByName = new EntitiesByNameStorage();
|
||||
private final EntitiesByArchetypeStorage entitiesByArchetype = new EntitiesByArchetypeStorage();
|
||||
private final ComponentClassesBySuperclassStorage componentClassesBySuperclass = new ComponentClassesBySuperclassStorage();
|
||||
private final ComponentsByTypeStorage componentsByType = new ComponentsByTypeStorage();
|
||||
@Getter
|
||||
private boolean isRunning = false;
|
||||
|
||||
public void start() {
|
||||
if (isRunning) return;
|
||||
isRunning = true;
|
||||
|
||||
for (Entity entity : entities) {
|
||||
entity.ready();
|
||||
}
|
||||
}
|
||||
|
||||
public void update(float deltaTime) {
|
||||
entities.forEach(entity -> entity.update(deltaTime));
|
||||
}
|
||||
|
||||
protected void removeEntity(Entity entity) {
|
||||
entities.remove(entity);
|
||||
entitiesByName.remove(entity);
|
||||
entitiesByArchetype.remove(entity);
|
||||
componentClassesBySuperclass.remove(entity);
|
||||
componentsByType.remove(entity);
|
||||
}
|
||||
|
||||
protected void updateEntityComponents(Entity entity) {
|
||||
Set<Class<? extends Component>> components = entity.getComponents()
|
||||
.stream()
|
||||
.map(Component::getClass)
|
||||
.collect(Collectors.toSet());
|
||||
entitiesByName.update(entity, components);
|
||||
entitiesByArchetype.update(entity, components);
|
||||
componentClassesBySuperclass.update(entity, components);
|
||||
componentsByType.update(entity, components);
|
||||
}
|
||||
|
||||
protected void addEntity(Entity entity) {
|
||||
if (entities.contains(entity)) {
|
||||
return;
|
||||
}
|
||||
entities.add(entity);
|
||||
|
||||
Set<Class<? extends Component>> components = entity.getComponents()
|
||||
.stream()
|
||||
.map(Component::getClass)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
entitiesByName.add(entity, components);
|
||||
entitiesByArchetype.add(entity, components);
|
||||
componentClassesBySuperclass.add(entity, components);
|
||||
componentsByType.add(entity, components);
|
||||
|
||||
if (!isRunning) {
|
||||
return;
|
||||
}
|
||||
entity.ready();
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
public final Set<Entity> getByComponents(boolean exactMatch, Class<? extends Component>... componentClasses) {
|
||||
Set<Entity> filteredEntities = new HashSet<>();
|
||||
|
||||
if (exactMatch) {
|
||||
filteredEntities.addAll(entitiesByArchetype.get(new Archetype<>(componentClasses)));
|
||||
} else {
|
||||
Set<Class<? extends Component>> componentSet = componentClassesBySuperclass.get(Set.of(componentClasses));
|
||||
Set<Component> componentInstances = componentsByType.get(componentSet);
|
||||
if (componentInstances != null) {
|
||||
filteredEntities.addAll(componentInstances.stream()
|
||||
.map(Component::getEntity)
|
||||
.toList());
|
||||
}
|
||||
}
|
||||
return filteredEntities;
|
||||
}
|
||||
|
||||
public final Set<Entity> getByName(String name) {
|
||||
return entitiesByName.get(name);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
package dev.euph.engine.core.ecs;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
public interface SceneStorage<T, U> {
|
||||
void add(Entity entity, Set<Class<? extends Component>> componentClasses);
|
||||
|
||||
void remove(Entity entity);
|
||||
|
||||
void remove(Set<Entity> entities);
|
||||
|
||||
void update(Entity entity, Set<Class<? extends Component>> newComponentClasses);
|
||||
|
||||
Set<U> get(T identifier);
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
package dev.euph.engine.core.ecs.storages;
|
||||
|
||||
import dev.euph.engine.core.ecs.Component;
|
||||
import dev.euph.engine.core.ecs.Entity;
|
||||
import dev.euph.engine.core.ecs.SceneStorage;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public class ComponentClassesBySuperclassStorage implements SceneStorage<Set<Class<? extends Component>>, Class<? extends Component>> {
|
||||
private final Map<Class<? extends Component>, Set<Class<? extends Component>>> subclassesByComponent;
|
||||
|
||||
public ComponentClassesBySuperclassStorage() {
|
||||
this.subclassesByComponent = new HashMap<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(Entity entity, Set<Class<? extends Component>> componentClasses) {
|
||||
for (Class<? extends Component> componentClass : componentClasses) {
|
||||
Set<Class<? extends Component>> subclasses = getSubclasses(componentClass);
|
||||
subclassesByComponent.put(componentClass, subclasses);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(Entity entity) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(Set<Entity> entities) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void update(Entity entity, Set<Class<? extends Component>> newComponentClasses) {
|
||||
remove(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Class<? extends Component>> get(Set<Class<? extends Component>> identifier) {
|
||||
Set<Class<? extends Component>> componentSet = new HashSet<>();
|
||||
for (Class<? extends Component> componentClass : identifier) {
|
||||
componentSet.add(componentClass);
|
||||
componentSet.addAll(subclassesByComponent.getOrDefault(componentClass, Collections.emptySet()));
|
||||
}
|
||||
return componentSet;
|
||||
}
|
||||
|
||||
private Set<Class<? extends Component>> getSubclasses(Class<? extends Component> componentClass) {
|
||||
Set<Class<? extends Component>> subclasses = new HashSet<>();
|
||||
for (Class<? extends Component> clazz : subclassesByComponent.keySet()) {
|
||||
if (componentClass.isAssignableFrom(clazz)) {
|
||||
subclasses.add(clazz);
|
||||
}
|
||||
}
|
||||
return subclasses;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
package dev.euph.engine.core.ecs.storages;
|
||||
|
||||
import dev.euph.engine.core.ecs.Component;
|
||||
import dev.euph.engine.core.ecs.Entity;
|
||||
import dev.euph.engine.core.ecs.SceneStorage;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public class ComponentsByTypeStorage implements SceneStorage<Set<Class<? extends Component>>, Component> {
|
||||
|
||||
private final Map<Class<? extends Component>, Set<Component>> componentInstancesByType;
|
||||
|
||||
public ComponentsByTypeStorage() {
|
||||
this.componentInstancesByType = new HashMap<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(Entity entity, Set<Class<? extends Component>> componentClasses) {
|
||||
for (Class<? extends Component> componentClass : componentClasses) {
|
||||
Set<Component> componentInstances = componentInstancesByType.computeIfAbsent(componentClass, key -> new HashSet<>());
|
||||
componentInstances.add(entity.getComponent(componentClass)); // added
|
||||
|
||||
Class<?> superClass = componentClass.getSuperclass();
|
||||
while (Component.class.isAssignableFrom(superClass)) {
|
||||
if (Component.class.isAssignableFrom(superClass)) {
|
||||
Set<Component> superClassInstances = componentInstancesByType.computeIfAbsent(superClass.asSubclass(Component.class), key -> new HashSet<>());
|
||||
superClassInstances.add(entity.getComponent(componentClass));
|
||||
}
|
||||
superClass = superClass.getSuperclass();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(Entity entity) {
|
||||
for (Component component : entity.getComponents()) {
|
||||
Class<? extends Component> componentClass = component.getClass();
|
||||
Set<Component> componentInstances = componentInstancesByType.get(componentClass);
|
||||
if (componentInstances != null) {
|
||||
componentInstances.remove(component);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(Set<Entity> entities) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(Entity entity, Set<Class<? extends Component>> newComponentClasses) {
|
||||
for (Component component : entity.getComponents()) {
|
||||
Class<? extends Component> componentClass = component.getClass();
|
||||
Set<Component> componentInstances = componentInstancesByType.computeIfAbsent(componentClass, key -> new HashSet<>());
|
||||
componentInstances.add(component);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Component> get(Set<Class<? extends Component>> identifier) {
|
||||
Set<Component> filteredEntities = new HashSet<>();
|
||||
for (Class<? extends Component> componentClass : identifier) {
|
||||
filteredEntities.addAll(componentInstancesByType.get(componentClass));
|
||||
}
|
||||
return filteredEntities;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
package dev.euph.engine.core.ecs.storages;
|
||||
|
||||
import dev.euph.engine.core.data.Archetype;
|
||||
import dev.euph.engine.core.ecs.Component;
|
||||
import dev.euph.engine.core.ecs.Entity;
|
||||
import dev.euph.engine.core.ecs.SceneStorage;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public class EntitiesByArchetypeStorage implements SceneStorage<Archetype<Component>, Entity> {
|
||||
private final Map<Archetype<Component>, Set<Entity>> entitiesByArchetype;
|
||||
|
||||
public EntitiesByArchetypeStorage() {
|
||||
this.entitiesByArchetype = new HashMap<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(Entity entity, Set<Class<? extends Component>> componentClasses) {
|
||||
Archetype<Component> type = new Archetype<>(componentClasses);
|
||||
Set<Entity> entitySet = entitiesByArchetype.computeIfAbsent(type, _ -> new HashSet<>());
|
||||
entitySet.add(entity);
|
||||
this.entitiesByArchetype.put(type, entitySet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(Entity entity) {
|
||||
for (Map.Entry<Archetype<Component>, Set<Entity>> entry : entitiesByArchetype.entrySet()) {
|
||||
Archetype<Component> archetype = entry.getKey();
|
||||
Set<Entity> archetypeEntities = entry.getValue();
|
||||
if (!archetypeEntities.remove(entity)) {
|
||||
return;
|
||||
}
|
||||
if (archetypeEntities.isEmpty()) {
|
||||
entitiesByArchetype.remove(archetype);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(Set<Entity> entities) {
|
||||
entities.forEach(this::remove);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(Entity entity, Set<Class<? extends Component>> newComponentClasses) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Entity> get(Archetype<Component> identifier) {
|
||||
return this.entitiesByArchetype.get(identifier);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
package dev.euph.engine.core.ecs.storages;
|
||||
|
||||
import dev.euph.engine.core.ecs.Component;
|
||||
import dev.euph.engine.core.ecs.Entity;
|
||||
import dev.euph.engine.core.ecs.SceneStorage;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public class EntitiesByNameStorage implements SceneStorage<String, Entity> {
|
||||
private final Map<String, Set<Entity>> entitiesByName;
|
||||
|
||||
public EntitiesByNameStorage() {
|
||||
this.entitiesByName = new HashMap<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(Entity entity, Set<Class<? extends Component>> componentClasses) {
|
||||
Set<Entity> entities = this.entitiesByName.computeIfAbsent(entity.getName(), _ -> new HashSet<>());
|
||||
if (entities == null) {
|
||||
entities = new HashSet<>();
|
||||
}
|
||||
entities.add(entity);
|
||||
this.entitiesByName.put(entity.getName(), entities);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(Entity entity) {
|
||||
Set<Entity> entities = this.entitiesByName.get(entity.getName());
|
||||
if (entities == null) {
|
||||
return;
|
||||
}
|
||||
if (entities.size() == 1 && entities.contains(entity)) {
|
||||
this.entitiesByName.remove(entity.getName());
|
||||
return;
|
||||
}
|
||||
entities.remove(entity);
|
||||
this.entitiesByName.put(entity.getName(), entities);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(Set<Entity> entities) {
|
||||
entities.forEach(this::remove);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(Entity entity, Set<Class<? extends Component>> newComponentClasses) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Entity> get(String identifier) {
|
||||
return entitiesByName.get(identifier);
|
||||
}
|
||||
}
|
|
@ -1,73 +1,84 @@
|
|||
package dev.euph.engine.ecs.components;
|
||||
package dev.euph.engine.core.transform;
|
||||
|
||||
import dev.euph.engine.ecs.Component;
|
||||
import dev.euph.engine.math.TransformationMatrix;
|
||||
import org.joml.Matrix4f;
|
||||
import dev.euph.engine.core.ecs.Component;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.joml.Quaternionf;
|
||||
import org.joml.Vector3f;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
|
||||
import static java.lang.Math.toRadians;
|
||||
|
||||
@Getter
|
||||
@NoArgsConstructor
|
||||
public class Transform extends Component {
|
||||
//region Fields
|
||||
private Vector3f position;
|
||||
private Vector3f rotation;
|
||||
private Vector3f scale;
|
||||
private TransformationMatrix transformationMatrix;
|
||||
//endregion
|
||||
|
||||
//region Constructor
|
||||
public Transform() {
|
||||
this(new Vector3f(), new Vector3f(), new Vector3f(1));
|
||||
}
|
||||
private Vector3f position = new Vector3f();
|
||||
private Vector3f rotation = new Vector3f();
|
||||
private Vector3f scale = new Vector3f(1);
|
||||
private TransformationMatrix transformationMatrix;
|
||||
|
||||
|
||||
public Transform(Vector3f position, Vector3f rotation, Vector3f scale) {
|
||||
this.position = position;
|
||||
this.rotation = rotation;
|
||||
this.scale = scale;
|
||||
calculateTransformationMatrix();
|
||||
}
|
||||
|
||||
public Transform(Transform transform) {
|
||||
this.position = transform.position;
|
||||
this.rotation = transform.rotation;
|
||||
this.scale = transform.scale;
|
||||
calculateTransformationMatrix();
|
||||
}
|
||||
//endregion
|
||||
|
||||
//region Utility
|
||||
public static Transform FromPosition(float x, float y, float z) {
|
||||
return Transform.FromPosition(new Vector3f(x, y, z));
|
||||
}
|
||||
|
||||
public static Transform FromPosition(Vector3f position) {
|
||||
return new Transform(position, new Vector3f(), new Vector3f(1));
|
||||
}
|
||||
|
||||
public static Transform FromRotation(float x, float y, float z) {
|
||||
return Transform.FromRotation(new Vector3f(x, y, z));
|
||||
}
|
||||
|
||||
public static Transform FromRotation(Vector3f rotation) {
|
||||
return new Transform(new Vector3f(), rotation, new Vector3f(1));
|
||||
}
|
||||
|
||||
public static Transform FromScale(float x, float y, float z) {
|
||||
return Transform.FromScale(new Vector3f(x, y, z));
|
||||
}
|
||||
|
||||
public static Transform FromScale(Vector3f scale) {
|
||||
return new Transform(new Vector3f(), new Vector3f(), scale);
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
private void calculateTransformationMatrix() {
|
||||
transformationMatrix = new TransformationMatrix(this);
|
||||
}
|
||||
//endregion
|
||||
|
||||
//region Getter/Setter
|
||||
public Vector3f getPosition() {
|
||||
return new Vector3f(position);
|
||||
}
|
||||
|
||||
public void setPosition(Vector3f position) {
|
||||
this.position = position;
|
||||
calculateTransformationMatrix();
|
||||
}
|
||||
|
||||
public Vector3f getRotation() {
|
||||
return new Vector3f(rotation);
|
||||
}
|
||||
|
||||
public void setRotation(Vector3f rotation) {
|
||||
this.rotation = rotation;
|
||||
calculateTransformationMatrix();
|
||||
}
|
||||
public Vector3f getScale() {
|
||||
return new Vector3f(scale);
|
||||
|
||||
public Transform setScale(float scale) {
|
||||
return setScale(new Vector3f(scale));
|
||||
}
|
||||
|
||||
public void setScale(Vector3f scale) {
|
||||
public Transform setScale(Vector3f scale) {
|
||||
this.scale = scale;
|
||||
calculateTransformationMatrix();
|
||||
}
|
||||
public TransformationMatrix getTransformationMatrix() {
|
||||
return transformationMatrix;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Vector3f getUp() {
|
||||
|
@ -104,5 +115,4 @@ public class Transform extends Component {
|
|||
);
|
||||
return q.positiveZ(new Vector3f());
|
||||
}
|
||||
//endregion
|
||||
}
|
|
@ -1,6 +1,5 @@
|
|||
package dev.euph.engine.math;
|
||||
package dev.euph.engine.core.transform;
|
||||
|
||||
import dev.euph.engine.ecs.components.Transform;
|
||||
import org.joml.Matrix4f;
|
||||
import org.joml.Vector3f;
|
||||
|
||||
|
@ -15,6 +14,7 @@ public class TransformationMatrix extends Matrix4f {
|
|||
super.rotate((float) Math.toRadians(rotation.z % 360), new Vector3f(0, 0, 1));
|
||||
super.scale(scale);
|
||||
}
|
||||
|
||||
public TransformationMatrix(Transform transform) {
|
||||
super();
|
||||
super.identity();
|
||||
|
@ -25,6 +25,7 @@ public class TransformationMatrix extends Matrix4f {
|
|||
super.rotate((float) Math.toRadians(rotation.z % 360), new Vector3f(0, 0, 1));
|
||||
super.scale(transform.getScale());
|
||||
}
|
||||
|
||||
public TransformationMatrix() {
|
||||
super();
|
||||
super.identity();
|
24
Engine/src/core/java/dev/euph/engine/core/util/Time.java
Normal file
24
Engine/src/core/java/dev/euph/engine/core/util/Time.java
Normal file
|
@ -0,0 +1,24 @@
|
|||
package dev.euph.engine.core.util;
|
||||
|
||||
import lombok.Getter;
|
||||
import org.lwjgl.glfw.GLFW;
|
||||
|
||||
public class Time {
|
||||
private long lastFrameTime;
|
||||
@Getter
|
||||
private float deltaTime;
|
||||
|
||||
public void startDeltaTime() {
|
||||
lastFrameTime = getCurrentTime();
|
||||
}
|
||||
|
||||
public void updateDeltaTime() {
|
||||
long currentFrameTime = getCurrentTime();
|
||||
deltaTime = (currentFrameTime - lastFrameTime) / 1000f;
|
||||
lastFrameTime = currentFrameTime;
|
||||
}
|
||||
|
||||
private long getCurrentTime() {
|
||||
return (long) (GLFW.glfwGetTime() * 1000L);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
package dev.euph.engine.core.window;
|
||||
|
||||
public interface CloseCallback {
|
||||
void onClose();
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
package dev.euph.engine.core.window;
|
||||
|
||||
public interface ResizeCallback {
|
||||
void onResize(int width, int height);
|
||||
}
|
127
Engine/src/core/java/dev/euph/engine/core/window/Window.java
Normal file
127
Engine/src/core/java/dev/euph/engine/core/window/Window.java
Normal file
|
@ -0,0 +1,127 @@
|
|||
package dev.euph.engine.core.window;
|
||||
|
||||
import dev.euph.engine.core.util.Time;
|
||||
import lombok.Getter;
|
||||
import org.lwjgl.glfw.GLFWErrorCallback;
|
||||
import org.lwjgl.glfw.GLFWWindowCloseCallback;
|
||||
import org.lwjgl.glfw.GLFWWindowSizeCallback;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.lwjgl.glfw.Callbacks.glfwFreeCallbacks;
|
||||
import static org.lwjgl.glfw.GLFW.*;
|
||||
|
||||
@Getter
|
||||
public abstract class Window {
|
||||
|
||||
//@Getter(AccessLevel.NONE)
|
||||
protected final long id;
|
||||
protected final Time time;
|
||||
|
||||
protected final int initialWidth, initialHeight;
|
||||
private final Set<CloseCallback> closeCallbacks = new HashSet<>();
|
||||
private final Set<ResizeCallback> resizeCallbacks = new HashSet<>();
|
||||
protected int width, height;
|
||||
|
||||
public Window(int width, int height, String title) {
|
||||
this(width, height, title, 0, new Time());
|
||||
}
|
||||
|
||||
public Window(int width, int height, String title, int vsyncInterval) {
|
||||
this(width, height, title, vsyncInterval, new Time());
|
||||
}
|
||||
|
||||
private Window(int width, int height, String title, int vsyncInterval, Time time) {
|
||||
this.time = time;
|
||||
this.initialWidth = width;
|
||||
this.width = width;
|
||||
this.initialHeight = height;
|
||||
this.height = height;
|
||||
|
||||
GLFWErrorCallback.createPrint(System.err).set();
|
||||
if (!glfwInit()) {
|
||||
throw new IllegalStateException("Unable to initialize GLFW");
|
||||
}
|
||||
glfwDefaultWindowHints();
|
||||
glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE);
|
||||
glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE);
|
||||
|
||||
this.id = glfwCreateWindow(width, height, title, 0, 0);
|
||||
if (id == 0) {
|
||||
throw new RuntimeException("Failed to create the GLFW window");
|
||||
}
|
||||
setupCallbacks();
|
||||
glfwMakeContextCurrent(id);
|
||||
glfwSwapInterval(vsyncInterval);
|
||||
glfwShowWindow(id);
|
||||
time.startDeltaTime();
|
||||
}
|
||||
|
||||
private void setupCallbacks() {
|
||||
GLFWWindowCloseCallback closeCallback = new GLFWWindowCloseCallback() {
|
||||
@Override
|
||||
public void invoke(long window) {
|
||||
closeCallbacks.forEach(CloseCallback::onClose);
|
||||
}
|
||||
};
|
||||
glfwSetWindowCloseCallback(id, closeCallback);
|
||||
GLFWWindowSizeCallback windowSizeCallback = new GLFWWindowSizeCallback() {
|
||||
@Override
|
||||
public void invoke(long window, int newWidth, int newHeight) {
|
||||
Window.this.width = newWidth;
|
||||
Window.this.height = newHeight;
|
||||
resizeCallbacks.forEach(callback -> callback.onResize(newWidth, newHeight));
|
||||
}
|
||||
};
|
||||
glfwSetWindowSizeCallback(id, windowSizeCallback);
|
||||
}
|
||||
|
||||
public void update() {
|
||||
glfwSwapBuffers(id);
|
||||
glfwPollEvents();
|
||||
time.updateDeltaTime();
|
||||
onUpdate();
|
||||
}
|
||||
|
||||
public void destroy() {
|
||||
glfwFreeCallbacks(id);
|
||||
glfwDestroyWindow(id);
|
||||
glfwTerminate();
|
||||
Objects.requireNonNull(glfwSetErrorCallback(null)).free();
|
||||
onDestroy();
|
||||
}
|
||||
|
||||
public void resize(int width, int height) {
|
||||
glfwSetWindowSize(id, width, height);
|
||||
resizeCallbacks.forEach(callback -> callback.onResize(width, height));
|
||||
}
|
||||
|
||||
public void setVsync(int interval) {
|
||||
glfwSwapInterval(interval);
|
||||
}
|
||||
|
||||
public void addResizeCallback(ResizeCallback resizeCallback) {
|
||||
resizeCallbacks.add(resizeCallback);
|
||||
}
|
||||
|
||||
public void removeResizeCallback(ResizeCallback resizeCallback) {
|
||||
resizeCallbacks.remove(resizeCallback);
|
||||
}
|
||||
|
||||
public void addCloseCallback(CloseCallback closeCallback) {
|
||||
closeCallbacks.add(closeCallback);
|
||||
}
|
||||
|
||||
public void removeCloseCallback(CloseCallback closeCallback) {
|
||||
closeCallbacks.remove(closeCallback);
|
||||
}
|
||||
|
||||
|
||||
protected abstract void onUpdate();
|
||||
|
||||
protected abstract void onDestroy();
|
||||
|
||||
|
||||
}
|
49
Engine/src/opengl/build.gradle.kts
Normal file
49
Engine/src/opengl/build.gradle.kts
Normal file
|
@ -0,0 +1,49 @@
|
|||
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
plugins {
|
||||
java
|
||||
id("com.gradleup.shadow") version "8.3.0"
|
||||
}
|
||||
|
||||
val lwjglVersion: String by rootProject.extra
|
||||
val jomlVersion: String by rootProject.extra
|
||||
val jomlPrimitivesVersion: String by rootProject.extra
|
||||
val lwjglNatives: String by rootProject.extra
|
||||
|
||||
sourceSets {
|
||||
val main by getting {
|
||||
java.srcDir("java")
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
annotationProcessor("org.projectlombok:lombok:1.18.34")
|
||||
implementation("org.projectlombok:lombok:1.18.34")
|
||||
annotationProcessor("javax.annotation:javax.annotation-api:1.3.2")
|
||||
implementation("javax.annotation:javax.annotation-api:1.3.2")
|
||||
|
||||
compileOnly(project(":core", configuration = "shadow"))
|
||||
compileOnly(project(":rendering", configuration = "shadow"))
|
||||
|
||||
implementation("org.lwjgl", "lwjgl-assimp")
|
||||
runtimeOnly("org.lwjgl", "lwjgl-assimp", classifier = lwjglNatives)
|
||||
|
||||
implementation(platform("org.lwjgl:lwjgl-bom:$lwjglVersion"))
|
||||
implementation("org.lwjgl", "lwjgl-opengl")
|
||||
runtimeOnly("org.lwjgl", "lwjgl-opengl", classifier = lwjglNatives)
|
||||
|
||||
shadow(localGroovy())
|
||||
shadow(gradleApi())
|
||||
}
|
||||
|
||||
tasks.named<ShadowJar>("shadowJar") {
|
||||
archiveFileName.set("dev.euph.engine.opengl.jar")
|
||||
}
|
||||
|
||||
configurations {
|
||||
shadow
|
||||
}
|
44
Engine/src/opengl/java/dev/euph/engine/opengl/GL.java
Normal file
44
Engine/src/opengl/java/dev/euph/engine/opengl/GL.java
Normal file
|
@ -0,0 +1,44 @@
|
|||
package dev.euph.engine.opengl;
|
||||
|
||||
import org.lwjgl.opengl.GL31;
|
||||
|
||||
public class GL extends GL31 {
|
||||
|
||||
protected static int getTextureIndex(int index) {
|
||||
return switch (index) {
|
||||
case 0 -> GL_TEXTURE0;
|
||||
case 1 -> GL_TEXTURE1;
|
||||
case 2 -> GL_TEXTURE2;
|
||||
case 3 -> GL_TEXTURE3;
|
||||
case 4 -> GL_TEXTURE4;
|
||||
case 5 -> GL_TEXTURE5;
|
||||
case 6 -> GL_TEXTURE6;
|
||||
case 7 -> GL_TEXTURE7;
|
||||
case 8 -> GL_TEXTURE8;
|
||||
case 9 -> GL_TEXTURE9;
|
||||
case 10 -> GL_TEXTURE10;
|
||||
case 11 -> GL_TEXTURE11;
|
||||
case 12 -> GL_TEXTURE12;
|
||||
case 13 -> GL_TEXTURE13;
|
||||
case 14 -> GL_TEXTURE14;
|
||||
case 15 -> GL_TEXTURE15;
|
||||
case 16 -> GL_TEXTURE16;
|
||||
case 17 -> GL_TEXTURE17;
|
||||
case 18 -> GL_TEXTURE18;
|
||||
case 19 -> GL_TEXTURE19;
|
||||
case 20 -> GL_TEXTURE20;
|
||||
case 21 -> GL_TEXTURE21;
|
||||
case 22 -> GL_TEXTURE22;
|
||||
case 23 -> GL_TEXTURE23;
|
||||
case 24 -> GL_TEXTURE24;
|
||||
case 25 -> GL_TEXTURE25;
|
||||
case 26 -> GL_TEXTURE26;
|
||||
case 27 -> GL_TEXTURE27;
|
||||
case 28 -> GL_TEXTURE28;
|
||||
case 29 -> GL_TEXTURE29;
|
||||
case 30 -> GL_TEXTURE30;
|
||||
case 31 -> GL_TEXTURE31;
|
||||
default -> throw new IndexOutOfBoundsException("There are Only 32 GL Textures");
|
||||
};
|
||||
}
|
||||
}
|
34
Engine/src/opengl/java/dev/euph/engine/opengl/GLMesh.java
Normal file
34
Engine/src/opengl/java/dev/euph/engine/opengl/GLMesh.java
Normal file
|
@ -0,0 +1,34 @@
|
|||
package dev.euph.engine.opengl;
|
||||
|
||||
import dev.euph.engine.rendering.resources.Mesh;
|
||||
|
||||
import static org.lwjgl.opengl.GL30.glBindVertexArray;
|
||||
|
||||
public class GLMesh implements Mesh {
|
||||
private final int vaoId;
|
||||
private final int vertexCount;
|
||||
|
||||
public GLMesh(float[] vertices, int[] indices) {
|
||||
this.vertexCount = indices.length;
|
||||
vaoId = GLMeshHelper.getVao();
|
||||
GLMeshHelper.prepareLoad(vaoId);
|
||||
GLMeshHelper.loadVertices(vertices);
|
||||
GLMeshHelper.loadIndices(indices);
|
||||
GLMeshHelper.cleanupLoad();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bind() {
|
||||
glBindVertexArray(vaoId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unbind() {
|
||||
glBindVertexArray(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getVertexCount() {
|
||||
return vertexCount;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
package dev.euph.engine.opengl;
|
||||
|
||||
import org.lwjgl.BufferUtils;
|
||||
|
||||
import java.nio.FloatBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
|
||||
import static org.lwjgl.opengl.GL11.GL_FLOAT;
|
||||
import static org.lwjgl.opengl.GL15.*;
|
||||
import static org.lwjgl.opengl.GL20.glVertexAttribPointer;
|
||||
import static org.lwjgl.opengl.GL30.glBindVertexArray;
|
||||
import static org.lwjgl.opengl.GL30.glGenVertexArrays;
|
||||
|
||||
public class GLMeshHelper {
|
||||
private final static int VERTICE_INDEX = 0;
|
||||
private final static int TEXTURE_COORDINATE_INDEX = 1;
|
||||
|
||||
protected static int getVao() {
|
||||
return glGenVertexArrays();
|
||||
}
|
||||
|
||||
protected static void prepareLoad(int vaoId) {
|
||||
glBindVertexArray(vaoId);
|
||||
}
|
||||
|
||||
protected static void cleanupLoad() {
|
||||
glBindVertexArray(0);
|
||||
}
|
||||
|
||||
protected static void loadVertices(float[] vertices) {
|
||||
int vboId = glGenBuffers();
|
||||
glBindBuffer(GL_ARRAY_BUFFER, vboId);
|
||||
|
||||
FloatBuffer buffer = BufferUtils.createFloatBuffer(vertices.length);
|
||||
buffer.put(vertices).flip();
|
||||
glBufferData(GL_ARRAY_BUFFER, buffer, GL_STATIC_DRAW);
|
||||
|
||||
glVertexAttribPointer(VERTICE_INDEX, 3, GL_FLOAT, false, 0, 0);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
}
|
||||
|
||||
protected static void loadIndices(int[] indices) {
|
||||
int eboId = glGenBuffers();
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, eboId);
|
||||
|
||||
IntBuffer buffer = BufferUtils.createIntBuffer(indices.length);
|
||||
buffer.put(indices).flip();
|
||||
glBufferData(GL_ELEMENT_ARRAY_BUFFER, buffer, GL_STATIC_DRAW);
|
||||
}
|
||||
|
||||
protected static void loadTextureCoordinates(float[] textureCoordinates) {
|
||||
int vboTextureCoords = glGenBuffers();
|
||||
glBindBuffer(GL_ARRAY_BUFFER, vboTextureCoords);
|
||||
|
||||
FloatBuffer buffer = BufferUtils.createFloatBuffer(textureCoordinates.length);
|
||||
buffer.put(textureCoordinates).flip();
|
||||
glBufferData(GL_ARRAY_BUFFER, buffer, GL_STATIC_DRAW);
|
||||
|
||||
glVertexAttribPointer(TEXTURE_COORDINATE_INDEX, 2, GL_FLOAT, false, 0, 0);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
|
||||
}
|
||||
}
|
108
Engine/src/opengl/java/dev/euph/engine/opengl/GLMeshLoader.java
Normal file
108
Engine/src/opengl/java/dev/euph/engine/opengl/GLMeshLoader.java
Normal file
|
@ -0,0 +1,108 @@
|
|||
package dev.euph.engine.opengl;
|
||||
|
||||
import dev.euph.engine.rendering.resources.Mesh;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.lwjgl.assimp.*;
|
||||
|
||||
import java.net.URL;
|
||||
import java.nio.IntBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
public class GLMeshLoader{
|
||||
|
||||
public static Mesh loadMesh(URL modelResource) {
|
||||
try {
|
||||
AIMesh mesh = loadMeshIntoAssimp(modelResource);
|
||||
|
||||
List<Float> verticesList = new ArrayList<>();
|
||||
for (int i = 0; i < mesh.mNumVertices(); i++) {
|
||||
loadVertex(mesh, i, verticesList);
|
||||
}
|
||||
float[] vertices = listToFloatArray(verticesList);
|
||||
int[] indices = listToIntArray(loadIndices(mesh));
|
||||
|
||||
return new GLMesh(vertices, indices);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
throw new RuntimeException("Error loading mesh: " + modelResource.getPath());
|
||||
}
|
||||
}
|
||||
|
||||
public static Mesh loadTexturedMesh(URL modelResource) {
|
||||
try {
|
||||
AIMesh mesh = loadMeshIntoAssimp(modelResource);
|
||||
|
||||
List<Float> verticesList = new ArrayList<>();
|
||||
List<Float> textureCoordinatesList = new ArrayList<>();
|
||||
|
||||
for (int i = 0; i < mesh.mNumVertices(); i++) {
|
||||
loadVertex(mesh, i, verticesList);
|
||||
loadTextureCoordinates(mesh, i, textureCoordinatesList);
|
||||
}
|
||||
float[] vertices = listToFloatArray(verticesList);
|
||||
int[] indices = listToIntArray(loadIndices(mesh));
|
||||
float[] texCoords = listToFloatArray(textureCoordinatesList);
|
||||
|
||||
return new GLTexturedMesh(vertices, indices, texCoords);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
throw new RuntimeException("Error loading mesh: " + modelResource.getPath());
|
||||
}
|
||||
}
|
||||
|
||||
private static void loadTextureCoordinates(AIMesh mesh, int i, List<Float> texCoordsList) {
|
||||
AIVector3D textureCoordinate = mesh.mTextureCoords(0).get(i);
|
||||
texCoordsList.add(textureCoordinate.x());
|
||||
texCoordsList.add(textureCoordinate.y());
|
||||
}
|
||||
|
||||
private static void loadVertex(AIMesh mesh, int i, List<Float> verticesList) {
|
||||
AIVector3D vertex = mesh.mVertices().get(i);
|
||||
verticesList.add(vertex.x());
|
||||
verticesList.add(vertex.y());
|
||||
verticesList.add(vertex.z());
|
||||
}
|
||||
|
||||
private static List<Integer> loadIndices(AIMesh mesh) {
|
||||
List<Integer> indicesList = new ArrayList<>();
|
||||
AIFace.Buffer faceBuffer = mesh.mFaces();
|
||||
while (faceBuffer.remaining() > 0) {
|
||||
AIFace face = faceBuffer.get();
|
||||
IntBuffer buffer = face.mIndices();
|
||||
while (buffer.remaining() > 0) {
|
||||
indicesList.add(buffer.get());
|
||||
}
|
||||
}
|
||||
return indicesList;
|
||||
}
|
||||
|
||||
private static @NotNull AIMesh loadMeshIntoAssimp(URL modelResource) {
|
||||
AIScene scene = Assimp.aiImportFile(modelResource.getPath(), Assimp.aiProcess_Triangulate | Assimp.aiProcess_FlipUVs);
|
||||
|
||||
if (scene == null || scene.mNumMeshes() == 0) {
|
||||
throw new RuntimeException("Failed to load mesh: " + modelResource.getPath());
|
||||
}
|
||||
|
||||
AIMesh mesh = AIMesh.create(Objects.requireNonNull(scene.mMeshes()).get(0));
|
||||
scene.close();
|
||||
return mesh;
|
||||
}
|
||||
|
||||
private static float[] listToFloatArray(List<Float> list) {
|
||||
float[] array = new float[list.size()];
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
array[i] = list.get(i);
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
private static int[] listToIntArray(List<Integer> list) {
|
||||
int[] array = new int[list.size()];
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
array[i] = list.get(i);
|
||||
}
|
||||
return array;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
package dev.euph.engine.opengl;
|
||||
|
||||
import dev.euph.engine.core.transform.Transform;
|
||||
import dev.euph.engine.core.ecs.Entity;
|
||||
import dev.euph.engine.core.ecs.Scene;
|
||||
import dev.euph.engine.core.transform.TransformationMatrix;
|
||||
import dev.euph.engine.rendering.Material;
|
||||
import dev.euph.engine.rendering.RenderPipeline;
|
||||
import dev.euph.engine.rendering.Shader;
|
||||
import dev.euph.engine.rendering.ShaderManager;
|
||||
import dev.euph.engine.rendering.components.Camera;
|
||||
import dev.euph.engine.rendering.components.MeshRenderer;
|
||||
import dev.euph.engine.rendering.resources.Mesh;
|
||||
import dev.euph.engine.rendering.utils.ProjectionMatrix;
|
||||
import dev.euph.engine.rendering.utils.ViewMatrix;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import static dev.euph.engine.opengl.GL.*;
|
||||
|
||||
public class GLRenderer extends RenderPipeline {
|
||||
private final ShaderManager shaderManager;
|
||||
public Camera activeCamera;
|
||||
@Getter
|
||||
@Setter
|
||||
private Scene scene;
|
||||
|
||||
public GLRenderer(Scene scene, ShaderManager shaderManager) {
|
||||
this.scene = scene;
|
||||
this.shaderManager = shaderManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init() {
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render() {
|
||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||
if (activeCamera == null) {
|
||||
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
return;
|
||||
}
|
||||
|
||||
glClearColor(
|
||||
activeCamera.backgroundColor.getRed(),
|
||||
activeCamera.backgroundColor.getGreen(),
|
||||
activeCamera.backgroundColor.getBlue(),
|
||||
activeCamera.backgroundColor.getAlpha()
|
||||
);
|
||||
|
||||
Shader shader = shaderManager.getShader("default");
|
||||
if (shader == null) return;
|
||||
|
||||
startShader(shader);
|
||||
ViewMatrix viewMatrix = new ViewMatrix(activeCamera);
|
||||
loadMatrix4(shader, getUniformLocation(shader, "viewMatrix"), viewMatrix);
|
||||
ProjectionMatrix projectionMatrix = new ProjectionMatrix(activeCamera);
|
||||
loadMatrix4(shader, getUniformLocation(shader, "projectionMatrix"), projectionMatrix);
|
||||
stopShader(shader);
|
||||
|
||||
for (Entity entity : scene.getByComponents(false, MeshRenderer.class)) {
|
||||
renderEntity(entity, entity.getComponent(MeshRenderer.class));
|
||||
}
|
||||
}
|
||||
|
||||
private void renderEntity(Entity entity, MeshRenderer meshRenderer) {
|
||||
Mesh mesh = meshRenderer.getMesh();
|
||||
Material material = meshRenderer.getMaterial();
|
||||
Shader shader = material.getShader();
|
||||
|
||||
if (shader == null || mesh == null) return;
|
||||
|
||||
startShader(shader);
|
||||
bindShaderAttributes(shader);
|
||||
|
||||
TransformationMatrix transformationMatrix = entity.getComponent(Transform.class).getTransformationMatrix();
|
||||
loadMatrix4(shader, getUniformLocation(shader, "transformationMatrix"), transformationMatrix);
|
||||
|
||||
material.useMaterial();
|
||||
mesh.bind();
|
||||
enableShaderAttributes(shader);
|
||||
glDrawElements(GL_TRIANGLES, mesh.getVertexCount(), GL_UNSIGNED_INT, 0);
|
||||
disableShaderAttributes(shader);
|
||||
mesh.unbind();
|
||||
stopShader(shader);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,5 +1,7 @@
|
|||
package dev.euph.engine.render;
|
||||
package dev.euph.engine.opengl;
|
||||
|
||||
import dev.euph.engine.rendering.Shader;
|
||||
import dev.euph.engine.rendering.resources.Texture;
|
||||
import org.joml.Matrix4f;
|
||||
import org.joml.Vector2f;
|
||||
import org.joml.Vector3f;
|
||||
|
@ -7,138 +9,45 @@ import org.joml.Vector4f;
|
|||
import org.lwjgl.BufferUtils;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.URL;
|
||||
import java.nio.FloatBuffer;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.lwjgl.opengl.GL40.*;
|
||||
import static dev.euph.engine.opengl.GL.*;
|
||||
|
||||
public class Shader implements AutoCloseable {
|
||||
//region Fields
|
||||
private final String name;
|
||||
private final int programId;
|
||||
private final int vertexShaderId;
|
||||
private final int fragmentShaderId;
|
||||
public class GLShader extends Shader {
|
||||
|
||||
private static final FloatBuffer matrixBuffer = BufferUtils.createFloatBuffer(16);
|
||||
private final int programId;
|
||||
private final Map<String, Integer> uniformLocations = new HashMap<>();
|
||||
//endregion
|
||||
|
||||
//region Constructors
|
||||
public Shader(String name, String vertexFile, String fragmentFile) throws IOException {
|
||||
this.name = name;
|
||||
vertexShaderId = loadShader(vertexFile, GL_VERTEX_SHADER);
|
||||
fragmentShaderId = loadShader(fragmentFile, GL_FRAGMENT_SHADER);
|
||||
public GLShader(String name, URL vertexShader, URL fragmentShader) throws IOException {
|
||||
super(
|
||||
name,
|
||||
loadShader(vertexShader.openStream(), GL_VERTEX_SHADER),
|
||||
loadShader(fragmentShader.openStream(), GL_FRAGMENT_SHADER)
|
||||
);
|
||||
programId = glCreateProgram();
|
||||
glAttachShader(programId, vertexShaderId);
|
||||
glAttachShader(programId, fragmentShaderId);
|
||||
bindAttributes();
|
||||
glLinkProgram(programId);
|
||||
glValidateProgram(programId);
|
||||
getAllUniformLocations();
|
||||
}
|
||||
//endregion
|
||||
|
||||
//region Public Methods
|
||||
public void start() {
|
||||
glUseProgram(programId);
|
||||
setupProgram();
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
glUseProgram(0);
|
||||
public GLShader(String name, InputStream vertexShaderStream, InputStream fragmentShaderStream) throws IOException {
|
||||
super(
|
||||
name,
|
||||
loadShader(vertexShaderStream, GL_VERTEX_SHADER),
|
||||
loadShader(fragmentShaderStream, GL_FRAGMENT_SHADER)
|
||||
);
|
||||
programId = glCreateProgram();
|
||||
setupProgram();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
stop();
|
||||
glDetachShader(programId, vertexShaderId);
|
||||
glDetachShader(programId, fragmentShaderId);
|
||||
glDeleteShader(vertexShaderId);
|
||||
glDeleteShader(fragmentShaderId);
|
||||
glDeleteProgram(programId);
|
||||
}
|
||||
//endregion
|
||||
|
||||
//region Uniform Locations
|
||||
protected void getAllUniformLocations() {
|
||||
int numUniforms = glGetProgrami(programId, GL_ACTIVE_UNIFORMS);
|
||||
|
||||
for (int i = 0; i < numUniforms; i++) {
|
||||
String name = glGetActiveUniformName(programId, i);
|
||||
int location = glGetUniformLocation(programId, name);
|
||||
|
||||
uniformLocations.put(name, location);
|
||||
}
|
||||
}
|
||||
|
||||
public int getUniformLocation(String uniformName) {
|
||||
if (uniformLocations.containsKey(uniformName)) {
|
||||
return uniformLocations.get(uniformName);
|
||||
}
|
||||
|
||||
int location = glGetUniformLocation(programId, uniformName);
|
||||
uniformLocations.put(uniformName, location);
|
||||
return location;
|
||||
}
|
||||
//endregion
|
||||
|
||||
//region Attributes
|
||||
public void enableAttributes(){
|
||||
glEnableVertexAttribArray(0);
|
||||
glEnableVertexAttribArray(1);
|
||||
glEnableVertexAttribArray(2);
|
||||
}
|
||||
public void disableAttributes(){
|
||||
glDisableVertexAttribArray(0);
|
||||
glDisableVertexAttribArray(1);
|
||||
glDisableVertexAttribArray(2);
|
||||
}
|
||||
public void bindAttributes() {
|
||||
bindAttribute(0, "vertexPositions");
|
||||
bindAttribute(1, "textureCoords");
|
||||
bindAttribute(2, "normals");
|
||||
}
|
||||
protected void bindAttribute(int attribute, String variableName) {
|
||||
glBindAttribLocation(programId, attribute, variableName);
|
||||
}
|
||||
//endregion
|
||||
|
||||
//region Uniform Loaders
|
||||
public void loadBoolean(int location, boolean value) {
|
||||
glUniform1f(location, value ? 1f : 0f);
|
||||
}
|
||||
|
||||
public void loadInt(int location, int value) {
|
||||
glUniform1i(location, value);
|
||||
}
|
||||
|
||||
public void loadFloat(int location, float value) {
|
||||
glUniform1f(location, value);
|
||||
}
|
||||
|
||||
public void loadVector2(int location, Vector2f value) {
|
||||
glUniform2f(location, value.x, value.y);
|
||||
}
|
||||
|
||||
public void loadVector3(int location, Vector3f value) {
|
||||
glUniform3f(location, value.x, value.y, value.z);
|
||||
}
|
||||
public void loadVector4(int location, Vector4f value) {
|
||||
glUniform4f(location, value.x, value.y, value.z, value.w);
|
||||
}
|
||||
|
||||
public void loadMatrix4(int location, Matrix4f value) {
|
||||
value.get(matrixBuffer);
|
||||
glUniformMatrix4fv(location, false, matrixBuffer);
|
||||
}
|
||||
//endregion
|
||||
|
||||
//region Helper Methods
|
||||
private static int loadShader(String file, int type) throws IOException{
|
||||
private static int loadShader(InputStream shaderStream, int type) throws IOException {
|
||||
StringBuilder shaderSource = new StringBuilder();
|
||||
try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
|
||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(shaderStream))) {
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
shaderSource.append(line).append("\n");
|
||||
|
@ -158,10 +67,134 @@ public class Shader implements AutoCloseable {
|
|||
}
|
||||
return shaderId;
|
||||
}
|
||||
//endregion
|
||||
|
||||
private void setupProgram() {
|
||||
glAttachShader(programId, vertexShaderId);
|
||||
glAttachShader(programId, fragmentShaderId);
|
||||
bindAttributes();
|
||||
glLinkProgram(programId);
|
||||
glValidateProgram(programId);
|
||||
getAllUniformLocations();
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
@Override
|
||||
public void start() {
|
||||
glUseProgram(programId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
glUseProgram(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
stop();
|
||||
glDetachShader(programId, vertexShaderId);
|
||||
glDetachShader(programId, fragmentShaderId);
|
||||
glDeleteShader(vertexShaderId);
|
||||
glDeleteShader(fragmentShaderId);
|
||||
glDeleteProgram(programId);
|
||||
}
|
||||
|
||||
private void getAllUniformLocations() {
|
||||
int numUniforms = glGetProgrami(programId, GL_ACTIVE_UNIFORMS);
|
||||
|
||||
for (int i = 0; i < numUniforms; i++) {
|
||||
String name = glGetActiveUniformName(programId, i);
|
||||
int location = glGetUniformLocation(programId, name);
|
||||
|
||||
uniformLocations.put(name, location);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getUniformLocation(String uniformName) {
|
||||
if (uniformLocations.containsKey(uniformName)) {
|
||||
return uniformLocations.get(uniformName);
|
||||
}
|
||||
|
||||
int location = glGetUniformLocation(programId, uniformName);
|
||||
uniformLocations.put(uniformName, location);
|
||||
return location;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void bindAttributes() {
|
||||
glBindAttribLocation(programId, 0, "vertexPositions");
|
||||
glBindAttribLocation(programId, 1, "textureCoords");
|
||||
glBindAttribLocation(programId, 2, "normals");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void enableAttributes() {
|
||||
glEnableVertexAttribArray(0);
|
||||
glEnableVertexAttribArray(1);
|
||||
glEnableVertexAttribArray(2);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void disableAttributes() {
|
||||
glDisableVertexAttribArray(0);
|
||||
glDisableVertexAttribArray(1);
|
||||
glDisableVertexAttribArray(2);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void loadBoolean(int location, boolean value) {
|
||||
glUniform1f(location, value ? 1f : 0f);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void loadInt(int location, int value) {
|
||||
glUniform1i(location, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void loadFloat(int location, float value) {
|
||||
glUniform1f(location, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void loadVector2(int location, Vector2f value) {
|
||||
glUniform2f(location, value.x, value.y);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void loadVector3(int location, Vector3f value) {
|
||||
glUniform3f(location, value.x, value.y, value.z);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void loadVector4(int location, Vector4f value) {
|
||||
glUniform4f(location, value.x, value.y, value.z, value.w);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void loadMatrix4(int location, Matrix4f value) {
|
||||
value.get(matrixBuffer);
|
||||
glUniformMatrix4fv(location, false, matrixBuffer);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void deleteTexture(Texture texture) {
|
||||
if(texture == null) {
|
||||
return;
|
||||
}
|
||||
glDeleteTextures(texture.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void bindTexture2D(int location, Texture texture, int textureIndex) {
|
||||
loadInt(location, textureIndex);
|
||||
glActiveTexture(getTextureIndex(textureIndex));
|
||||
glBindTexture(GL_TEXTURE_2D, texture.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void bindTexture3D(int location, Texture texture, int textureIndex) {
|
||||
loadInt(location, textureIndex);
|
||||
glActiveTexture(getTextureIndex(textureIndex));
|
||||
glBindTexture(GL_TEXTURE_3D, texture.getId());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
package dev.euph.engine.opengl;
|
||||
|
||||
import dev.euph.engine.core.data.ImageLoader;
|
||||
import dev.euph.engine.rendering.resources.Texture;
|
||||
import dev.euph.engine.rendering.resources.loader.TextureLoader;
|
||||
|
||||
import java.net.URL;
|
||||
|
||||
import static dev.euph.engine.opengl.GL.*;
|
||||
|
||||
public class GLTextureLoader {
|
||||
public static Texture loadTexture(URL imageResource) {
|
||||
int textureID = GL.glGenTextures();
|
||||
glBindTexture(GL_TEXTURE_2D, textureID);
|
||||
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
|
||||
ImageLoader.Image image = ImageLoader.loadImage(imageResource.getPath());
|
||||
glTexImage2D(
|
||||
GL_TEXTURE_2D,
|
||||
0,
|
||||
GL_RGBA,
|
||||
image.width(), image.height(),
|
||||
0,
|
||||
GL_RGBA,
|
||||
GL_UNSIGNED_BYTE,
|
||||
image.data()
|
||||
);
|
||||
glGenerateMipmap(GL_TEXTURE_2D);
|
||||
return new Texture(textureID, image.width(), image.height());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
package dev.euph.engine.opengl;
|
||||
|
||||
import dev.euph.engine.rendering.resources.Mesh;
|
||||
|
||||
import static org.lwjgl.opengl.GL30.glBindVertexArray;
|
||||
|
||||
public class GLTexturedMesh implements Mesh {
|
||||
private final int vaoId;
|
||||
private final int vertexCount;
|
||||
|
||||
public GLTexturedMesh(float[] vertices, int[] indices, float[] textureCoords) {
|
||||
this.vertexCount = indices.length;
|
||||
vaoId = GLMeshHelper.getVao();
|
||||
GLMeshHelper.prepareLoad(vaoId);
|
||||
GLMeshHelper.loadVertices(vertices);
|
||||
GLMeshHelper.loadIndices(indices);
|
||||
GLMeshHelper.loadTextureCoordinates(textureCoords);
|
||||
GLMeshHelper.cleanupLoad();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bind() {
|
||||
glBindVertexArray(vaoId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unbind() {
|
||||
glBindVertexArray(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getVertexCount() {
|
||||
return vertexCount;
|
||||
}
|
||||
}
|
51
Engine/src/opengl/java/dev/euph/engine/opengl/GLWindow.java
Normal file
51
Engine/src/opengl/java/dev/euph/engine/opengl/GLWindow.java
Normal file
|
@ -0,0 +1,51 @@
|
|||
package dev.euph.engine.opengl;
|
||||
|
||||
import dev.euph.engine.core.window.Window;
|
||||
import org.lwjgl.glfw.GLFWVidMode;
|
||||
import org.lwjgl.opengl.GL;
|
||||
|
||||
import static org.lwjgl.glfw.GLFW.*;
|
||||
import static org.lwjgl.opengl.GL11.glViewport;
|
||||
|
||||
public class GLWindow extends Window {
|
||||
|
||||
public GLWindow(int width, int height, String title) {
|
||||
super(width, height, title);
|
||||
|
||||
glfwSetKeyCallback(id, (window, key, scancode, action, mods) -> {
|
||||
if (key == GLFW_KEY_ESCAPE && action == GLFW_RELEASE) {
|
||||
glfwSetWindowShouldClose(window, true);
|
||||
}
|
||||
});
|
||||
|
||||
if (glfwGetPlatform() != GLFW_PLATFORM_WAYLAND) {
|
||||
GLFWVidMode vidmode = glfwGetVideoMode(glfwGetPrimaryMonitor());
|
||||
assert vidmode != null;
|
||||
glfwSetWindowPos(
|
||||
id,
|
||||
(vidmode.width() - width) / 2,
|
||||
(vidmode.height() - height) / 2
|
||||
);
|
||||
}
|
||||
|
||||
addResizeCallback((int newWidth, int newHeight) -> {
|
||||
GLWindow.this.width = newWidth;
|
||||
GLWindow.this.height = newHeight;
|
||||
glViewport(0, 0, newWidth, newHeight);
|
||||
});
|
||||
|
||||
GL.createCapabilities();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onUpdate() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
42
Engine/src/rendering/build.gradle.kts
Normal file
42
Engine/src/rendering/build.gradle.kts
Normal file
|
@ -0,0 +1,42 @@
|
|||
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
plugins {
|
||||
java
|
||||
id("com.gradleup.shadow") version "8.3.0"
|
||||
}
|
||||
|
||||
val lwjglVersion: String by rootProject.extra
|
||||
val jomlVersion: String by rootProject.extra
|
||||
val jomlPrimitivesVersion: String by rootProject.extra
|
||||
val lwjglNatives: String by rootProject.extra
|
||||
|
||||
sourceSets {
|
||||
val main by getting {
|
||||
java.srcDir("java")
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
annotationProcessor("org.projectlombok:lombok:1.18.34")
|
||||
implementation("org.projectlombok:lombok:1.18.34")
|
||||
annotationProcessor("javax.annotation:javax.annotation-api:1.3.2")
|
||||
implementation("javax.annotation:javax.annotation-api:1.3.2")
|
||||
|
||||
compileOnly(project(":core", configuration = "shadow"))
|
||||
implementation(platform("org.lwjgl:lwjgl-bom:$lwjglVersion"))
|
||||
|
||||
shadow(localGroovy())
|
||||
shadow(gradleApi())
|
||||
}
|
||||
|
||||
tasks.named<ShadowJar>("shadowJar") {
|
||||
archiveFileName.set("dev.euph.engine.rendering.jar")
|
||||
}
|
||||
|
||||
configurations {
|
||||
shadow
|
||||
}
|
|
@ -1,7 +1,6 @@
|
|||
package dev.euph.engine.util;
|
||||
package dev.euph.engine.rendering;
|
||||
|
||||
public class Config {
|
||||
public static final int MAX_LIGHTS = 6;
|
||||
public static final int MAX_LIGHT_DISTANCE = 100;
|
||||
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
package dev.euph.engine.rendering;
|
||||
|
||||
import dev.euph.engine.rendering.resources.Texture;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.awt.*;
|
||||
|
||||
@Setter
|
||||
@Getter
|
||||
@Accessors(chain = true)
|
||||
public class Material {
|
||||
|
||||
@Setter(AccessLevel.NONE)
|
||||
protected final Shader shader;
|
||||
private Texture albedoTexture;
|
||||
private Color color;
|
||||
|
||||
public Material(Shader shader) {
|
||||
this.shader = shader;
|
||||
this.albedoTexture = null;
|
||||
this.color = Color.WHITE;
|
||||
}
|
||||
|
||||
public void useMaterial() {
|
||||
shader.start();
|
||||
prepareColor();
|
||||
prepareAlbedoTexture();
|
||||
}
|
||||
|
||||
private void prepareColor() {
|
||||
int colorLocation = shader.getUniformLocation("color");
|
||||
shader.loadVector4(colorLocation, new org.joml.Vector4f(
|
||||
color.getRed() / 255f,
|
||||
color.getGreen() / 255f,
|
||||
color.getBlue() / 255f,
|
||||
1f)
|
||||
);
|
||||
}
|
||||
|
||||
private void prepareAlbedoTexture() {
|
||||
int useAlbedoTextureLocation = shader.getUniformLocation("useAlbedoTexture");
|
||||
if (albedoTexture != null) {
|
||||
int albedoTextureLocation = shader.getUniformLocation("albedoTexture");
|
||||
shader.bindTexture2D(albedoTextureLocation, albedoTexture, Texture.ALBEDO_INDEX);
|
||||
shader.loadBoolean(useAlbedoTextureLocation, true);
|
||||
} else {
|
||||
shader.loadBoolean(useAlbedoTextureLocation, false);
|
||||
}
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public void cleanup() {
|
||||
shader.deleteTexture(albedoTexture);
|
||||
shader.close();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
package dev.euph.engine.rendering;
|
||||
|
||||
import dev.euph.engine.rendering.resources.Texture;
|
||||
import lombok.Setter;
|
||||
|
||||
@Setter
|
||||
public class PBRMaterial extends Material {
|
||||
private Texture normalTexture;
|
||||
private Texture metallicTexture;
|
||||
private Texture roughnessTexture;
|
||||
private Texture aoTexture;
|
||||
|
||||
public PBRMaterial(Shader shader) {
|
||||
super(shader);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void useMaterial() {
|
||||
super.useMaterial();
|
||||
|
||||
if (normalTexture != null) {
|
||||
int normalTextureLocation = shader.getUniformLocation("normalTexture");
|
||||
shader.bindTexture2D(normalTextureLocation, normalTexture, Texture.NORMAL_INDEX);
|
||||
}
|
||||
|
||||
if (metallicTexture != null) {
|
||||
int metallicTextureLocation = shader.getUniformLocation("metallicTexture");
|
||||
shader.bindTexture2D(metallicTextureLocation, metallicTexture, Texture.METALLIC_INDEX);
|
||||
}
|
||||
|
||||
if (roughnessTexture != null) {
|
||||
int roughnessTextureLocation = shader.getUniformLocation("roughnessTexture");
|
||||
shader.bindTexture2D(roughnessTextureLocation, roughnessTexture, Texture.ROUGHNESS_INDEX);
|
||||
}
|
||||
|
||||
if (aoTexture != null) {
|
||||
int aoTextureLocation = shader.getUniformLocation("aoTexture");
|
||||
shader.bindTexture2D(aoTextureLocation, aoTexture, Texture.AMBIENT_OCCLUSION_INDEX);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cleanup() {
|
||||
super.cleanup();
|
||||
shader.deleteTexture(normalTexture);
|
||||
shader.deleteTexture(metallicTexture);
|
||||
shader.deleteTexture(roughnessTexture);
|
||||
shader.deleteTexture(aoTexture);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
package dev.euph.engine.rendering;
|
||||
|
||||
import org.joml.Matrix4f;
|
||||
import org.joml.Vector2f;
|
||||
import org.joml.Vector3f;
|
||||
import org.joml.Vector4f;
|
||||
|
||||
public abstract class RenderPipeline {
|
||||
protected static void startShader(Shader shader) {
|
||||
shader.start();
|
||||
}
|
||||
|
||||
protected static void stopShader(Shader shader) {
|
||||
shader.stop();
|
||||
}
|
||||
|
||||
protected static void bindShaderAttributes(Shader shader) {
|
||||
shader.bindAttributes();
|
||||
}
|
||||
|
||||
protected static void enableShaderAttributes(Shader shader) {
|
||||
shader.enableAttributes();
|
||||
}
|
||||
|
||||
protected static void disableShaderAttributes(Shader shader) {
|
||||
shader.disableAttributes();
|
||||
}
|
||||
|
||||
protected static void loadBoolean(Shader shader, int location, boolean value) {
|
||||
shader.loadBoolean(location, value);
|
||||
}
|
||||
|
||||
protected static void loadInt(Shader shader, int location, int value) {
|
||||
shader.loadInt(location, value);
|
||||
}
|
||||
|
||||
protected static void loadFloat(Shader shader, int location, float value) {
|
||||
shader.loadFloat(location, value);
|
||||
}
|
||||
|
||||
protected static void loadVector2(Shader shader, int location, Vector2f value) {
|
||||
shader.loadVector2(location, value);
|
||||
}
|
||||
|
||||
protected static void loadVector3(Shader shader, int location, Vector3f value) {
|
||||
shader.loadVector3(location, value);
|
||||
}
|
||||
|
||||
protected static void loadVector4(Shader shader, int location, Vector4f value) {
|
||||
shader.loadVector4(location, value);
|
||||
}
|
||||
|
||||
protected static void loadMatrix4(Shader shader, int location, Matrix4f value) {
|
||||
shader.loadMatrix4(location, value);
|
||||
}
|
||||
|
||||
protected static int getUniformLocation(Shader shader, String name) {
|
||||
return shader.getUniformLocation(name);
|
||||
}
|
||||
|
||||
public abstract void render();
|
||||
|
||||
public abstract void init();
|
||||
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
package dev.euph.engine.rendering;
|
||||
|
||||
import dev.euph.engine.rendering.resources.Texture;
|
||||
import lombok.Getter;
|
||||
import org.joml.Vector2f;
|
||||
import org.joml.Vector3f;
|
||||
import org.joml.Vector4f;
|
||||
import org.joml.Matrix4f;
|
||||
|
||||
public abstract class Shader implements AutoCloseable {
|
||||
protected final int vertexShaderId;
|
||||
protected final int fragmentShaderId;
|
||||
@Getter
|
||||
private final String name;
|
||||
|
||||
protected Shader(String name, int vertexShaderId, int fragmentShaderId) {
|
||||
this.name = name;
|
||||
this.vertexShaderId = vertexShaderId;
|
||||
this.fragmentShaderId = fragmentShaderId;
|
||||
}
|
||||
|
||||
protected abstract void start();
|
||||
|
||||
protected abstract void stop();
|
||||
|
||||
protected abstract void bindAttributes();
|
||||
|
||||
protected abstract void enableAttributes();
|
||||
|
||||
protected abstract void disableAttributes();
|
||||
|
||||
protected abstract int getUniformLocation(String name);
|
||||
|
||||
protected abstract void loadBoolean(int location, boolean value);
|
||||
|
||||
protected abstract void loadInt(int location, int value);
|
||||
|
||||
protected abstract void loadFloat(int location, float value);
|
||||
|
||||
protected abstract void loadVector2(int location, Vector2f value);
|
||||
|
||||
protected abstract void loadVector3(int location, Vector3f value);
|
||||
|
||||
protected abstract void loadVector4(int location, Vector4f value);
|
||||
|
||||
protected abstract void loadMatrix4(int location, Matrix4f value);
|
||||
|
||||
protected abstract void deleteTexture(Texture texture);
|
||||
|
||||
protected abstract void bindTexture2D(int location, Texture texture, int textureIndex);
|
||||
|
||||
protected abstract void bindTexture3D(int location, Texture texture, int textureIndex);
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
package dev.euph.engine.rendering;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@AllArgsConstructor
|
||||
public class ShaderManager {
|
||||
|
||||
private final Map<String, Shader> shaders = new HashMap<>();
|
||||
|
||||
public void addShader(Shader shader) {
|
||||
shaders.put(shader.getName(), shader);
|
||||
}
|
||||
|
||||
public Shader getShader(String shaderName) {
|
||||
return shaders.get(shaderName);
|
||||
}
|
||||
|
||||
public void cleanup() {
|
||||
shaders.values().forEach(shader -> {
|
||||
try {
|
||||
shader.close();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
shaders.clear();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
package dev.euph.engine.rendering.components;
|
||||
|
||||
import dev.euph.engine.core.transform.Transform;
|
||||
import dev.euph.engine.core.ecs.Component;
|
||||
import dev.euph.engine.core.ecs.Requires;
|
||||
import dev.euph.engine.core.window.Window;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.awt.*;
|
||||
|
||||
@Requires(Transform.class)
|
||||
public class Camera extends Component {
|
||||
public float fov = 90f;
|
||||
public float nearPlane = 0.1f;
|
||||
public float farPlane = 500f;
|
||||
public Color backgroundColor = new Color(0, 200, 255);
|
||||
@Getter
|
||||
private Window window;
|
||||
|
||||
public Camera(Window window) {
|
||||
this.window = window;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
package dev.euph.engine.rendering.components;
|
||||
|
||||
import dev.euph.engine.core.transform.Transform;
|
||||
import dev.euph.engine.core.ecs.Component;
|
||||
import dev.euph.engine.core.ecs.Requires;
|
||||
import dev.euph.engine.rendering.Material;
|
||||
import dev.euph.engine.rendering.resources.Mesh;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
@Requires(Transform.class)
|
||||
public class MeshRenderer extends Component {
|
||||
private final Mesh mesh;
|
||||
private final Material material;
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
package dev.euph.engine.rendering.components.lights;
|
||||
|
||||
import java.awt.*;
|
||||
|
||||
public class DirectionalLight extends LightSource {
|
||||
public DirectionalLight(Color color) {
|
||||
super(color, LightType.Directional);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
package dev.euph.engine.rendering.components.lights;
|
||||
|
||||
import dev.euph.engine.core.ecs.Component;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.awt.*;
|
||||
|
||||
@Getter
|
||||
public abstract class LightSource extends Component {
|
||||
|
||||
private final int type;
|
||||
@Setter
|
||||
private Color color;
|
||||
public LightSource(Color color, int type) {
|
||||
this.color = color;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public static class LightType {
|
||||
public static final int Directional = 0;
|
||||
public static final int Point = 1;
|
||||
public static final int Spot = 2;
|
||||
public static final int Area = 3;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package dev.euph.engine.rendering.components.lights;
|
||||
|
||||
import dev.euph.engine.core.transform.Transform;
|
||||
import dev.euph.engine.core.ecs.Requires;
|
||||
import org.joml.Vector3f;
|
||||
|
||||
import java.awt.*;
|
||||
|
||||
@Requires(Transform.class)
|
||||
public class PointLight extends LightSource {
|
||||
public Vector3f attenuation;
|
||||
|
||||
public PointLight(Color color, Vector3f attenuation) {
|
||||
super(color, LightType.Point);
|
||||
this.attenuation = attenuation;
|
||||
}
|
||||
}
|
|
@ -1,18 +1,18 @@
|
|||
package dev.euph.engine.ecs.components.lights;
|
||||
package dev.euph.engine.rendering.components.lights;
|
||||
|
||||
import dev.euph.engine.core.transform.Transform;
|
||||
import dev.euph.engine.core.ecs.Requires;
|
||||
import org.joml.Vector3f;
|
||||
|
||||
import java.awt.*;
|
||||
|
||||
@Requires(Transform.class)
|
||||
public class SpotLight extends LightSource {
|
||||
//region Fields
|
||||
public float coneAngle;
|
||||
public float hardCutoff;
|
||||
public float softCutoff;
|
||||
private Vector3f attenuation;
|
||||
//endregion
|
||||
public Vector3f attenuation;
|
||||
|
||||
//region Constructor
|
||||
public SpotLight(Color color, Vector3f attenuation, float coneAngle, float hardCutoff, float softCutoff) {
|
||||
super(color, LightType.Spot);
|
||||
this.attenuation = attenuation;
|
||||
|
@ -20,5 +20,4 @@ public class SpotLight extends LightSource{
|
|||
this.hardCutoff = hardCutoff;
|
||||
this.softCutoff = softCutoff;
|
||||
}
|
||||
//endregion
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
package dev.euph.engine.rendering.resources;
|
||||
|
||||
public interface Mesh {
|
||||
void bind();
|
||||
void unbind();
|
||||
int getVertexCount();
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
package dev.euph.engine.rendering.resources;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
public class Texture {
|
||||
|
||||
public final static int ALBEDO_INDEX = 0;
|
||||
public final static int NORMAL_INDEX = 1;
|
||||
public final static int METALLIC_INDEX = 2;
|
||||
public final static int ROUGHNESS_INDEX = 3;
|
||||
public final static int AMBIENT_OCCLUSION_INDEX = 4;
|
||||
|
||||
private final int id;
|
||||
private final int height;
|
||||
private final int width;
|
||||
|
||||
public Texture(int id, int width, int height) {
|
||||
this.id = id;
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
}
|
||||
}
|
|
@ -1,23 +1,18 @@
|
|||
package dev.euph.engine.math;
|
||||
package dev.euph.engine.rendering.utils;
|
||||
|
||||
import dev.euph.engine.ecs.components.Camera;
|
||||
import dev.euph.engine.managers.Window;
|
||||
import dev.euph.engine.core.window.Window;
|
||||
import dev.euph.engine.rendering.components.Camera;
|
||||
import org.joml.Matrix4f;
|
||||
import org.lwjgl.BufferUtils;
|
||||
|
||||
import java.nio.IntBuffer;
|
||||
|
||||
import static org.lwjgl.glfw.GLFW.glfwGetWindowSize;
|
||||
|
||||
public class ProjectionMatrix extends Matrix4f {
|
||||
|
||||
public ProjectionMatrix(Camera camera) {
|
||||
super();
|
||||
calcualteProjection(camera.getWindow(), camera.fov, camera.nearPlane, camera.farPlane);
|
||||
calculateProjection(camera.getWindow(), camera.fov, camera.nearPlane, camera.farPlane);
|
||||
}
|
||||
|
||||
private void calcualteProjection(Window window, float fov, float nearPlane, float farPlane) {
|
||||
float aspectRatio = (float) window.getInitalWidth() / (float) window.getInitalHeight();
|
||||
private void calculateProjection(Window window, float fov, float nearPlane, float farPlane) {
|
||||
float aspectRatio = (float) window.getWidth() / (float) window.getHeight();
|
||||
|
||||
float y_scale = (float) (1f / Math.tan(Math.toRadians(fov / 2f))) * aspectRatio;
|
||||
float x_scale = y_scale / aspectRatio;
|
|
@ -1,7 +1,7 @@
|
|||
package dev.euph.engine.math;
|
||||
package dev.euph.engine.rendering.utils;
|
||||
|
||||
import dev.euph.engine.ecs.components.Camera;
|
||||
import dev.euph.engine.ecs.components.Transform;
|
||||
import dev.euph.engine.core.transform.Transform;
|
||||
import dev.euph.engine.rendering.components.Camera;
|
||||
import org.joml.Math;
|
||||
import org.joml.Matrix4f;
|
||||
import org.joml.Vector3f;
|
46
Engine/src/resources/build.gradle.kts
Normal file
46
Engine/src/resources/build.gradle.kts
Normal file
|
@ -0,0 +1,46 @@
|
|||
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
plugins {
|
||||
java
|
||||
id("com.gradleup.shadow") version "8.3.0"
|
||||
}
|
||||
|
||||
val lwjglVersion: String by rootProject.extra
|
||||
val jomlVersion: String by rootProject.extra
|
||||
val jomlPrimitivesVersion: String by rootProject.extra
|
||||
val lwjglNatives: String by rootProject.extra
|
||||
|
||||
sourceSets {
|
||||
val main by getting {
|
||||
java.srcDir("java")
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
annotationProcessor("org.projectlombok:lombok:1.18.34")
|
||||
implementation("org.projectlombok:lombok:1.18.34")
|
||||
annotationProcessor("javax.annotation:javax.annotation-api:1.3.2")
|
||||
implementation("javax.annotation:javax.annotation-api:1.3.2")
|
||||
|
||||
compileOnly(project(":core", configuration = "shadow"))
|
||||
implementation(platform("org.lwjgl:lwjgl-bom:$lwjglVersion"))
|
||||
|
||||
implementation("org.lwjgl", "lwjgl-assimp")
|
||||
runtimeOnly("org.lwjgl", "lwjgl-assimp", classifier = lwjglNatives)
|
||||
|
||||
|
||||
shadow(localGroovy())
|
||||
shadow(gradleApi())
|
||||
}
|
||||
|
||||
tasks.named<ShadowJar>("shadowJar") {
|
||||
archiveFileName.set("dev.euph.engine.resources.jar")
|
||||
}
|
||||
|
||||
configurations {
|
||||
shadow
|
||||
}
|
7
TestGame/.gitignore
vendored
Normal file
7
TestGame/.gitignore
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
.gradle/
|
||||
gradle/
|
||||
gradlew
|
||||
gradlew.bat
|
||||
build/
|
||||
|
||||
.idea/
|
73
TestGame/build.gradle.kts
Normal file
73
TestGame/build.gradle.kts
Normal file
|
@ -0,0 +1,73 @@
|
|||
plugins {
|
||||
application
|
||||
java
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
java {
|
||||
toolchain {
|
||||
languageVersion.set(JavaLanguageVersion.of(22))
|
||||
}
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
java {
|
||||
srcDir("src")
|
||||
|
||||
}
|
||||
resources {
|
||||
srcDir("res")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
application {
|
||||
mainClass.set("dev.euph.game.Main")
|
||||
}
|
||||
|
||||
val lwjglVersion = "3.3.4"
|
||||
val jomlVersion = "1.10.7"
|
||||
val jomlPrimitivesVersion = "1.10.0"
|
||||
|
||||
val lwjglNatives = Pair(
|
||||
System.getProperty("os.name")!!,
|
||||
System.getProperty("os.arch")!!
|
||||
).let { (name, arch) ->
|
||||
when {
|
||||
arrayOf("Linux", "SunOS", "Unit").any { name.startsWith(it) } ->
|
||||
if (arrayOf("arm", "aarch64").any { arch.startsWith(it) })
|
||||
"natives-linux${if (arch.contains("64") || arch.startsWith("armv8")) "-arm64" else "-arm32"}"
|
||||
else if (arch.startsWith("ppc"))
|
||||
"natives-linux-ppc64le"
|
||||
else if (arch.startsWith("riscv"))
|
||||
"natives-linux-riscv64"
|
||||
else
|
||||
"natives-linux"
|
||||
|
||||
arrayOf("Mac OS X", "Darwin").any { name.startsWith(it) } ->
|
||||
"natives-macos${if (arch.startsWith("aarch64")) "-arm64" else ""}"
|
||||
|
||||
arrayOf("Windows").any { name.startsWith(it) } ->
|
||||
if (arch.contains("64"))
|
||||
"natives-windows${if (arch.startsWith("aarch64")) "-arm64" else ""}"
|
||||
else
|
||||
"natives-windows-x86"
|
||||
|
||||
else ->
|
||||
throw Error("Unrecognized or unsupported platform. Please set \"lwjglNatives\" manually")
|
||||
}
|
||||
}
|
||||
extra["lwjglVersion"] = lwjglVersion
|
||||
extra["jomlVersion"] = jomlVersion
|
||||
extra["jomlPrimitivesVersion"] = jomlPrimitivesVersion
|
||||
extra["lwjglNatives"] = lwjglNatives
|
||||
|
||||
dependencies {
|
||||
implementation(project(":core", configuration = "shadow"))
|
||||
implementation(project(":rendering", configuration = "shadow"))
|
||||
implementation(project(":opengl", configuration = "shadow"))
|
||||
}
|
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 39 KiB |
8
TestGame/settings.gradle.kts
Normal file
8
TestGame/settings.gradle.kts
Normal file
|
@ -0,0 +1,8 @@
|
|||
rootProject.name = "Game"
|
||||
|
||||
include(":core")
|
||||
project(":core").projectDir = file("../Engine/src/core")
|
||||
include(":rendering")
|
||||
project(":rendering").projectDir = file("../Engine/src/rendering")
|
||||
include(":opengl")
|
||||
project(":opengl").projectDir = file("../Engine/src/opengl")
|
|
@ -1,16 +1,19 @@
|
|||
package dev.euph.game;
|
||||
|
||||
import dev.euph.engine.ecs.Component;
|
||||
import dev.euph.engine.ecs.components.Camera;
|
||||
import dev.euph.engine.ecs.components.Transform;
|
||||
import dev.euph.engine.core.transform.Transform;
|
||||
import dev.euph.engine.core.ecs.Component;
|
||||
import dev.euph.engine.core.ecs.Requires;
|
||||
import dev.euph.engine.rendering.components.Camera;
|
||||
import org.joml.Vector2f;
|
||||
import org.joml.Vector3f;
|
||||
import org.lwjgl.glfw.GLFW;
|
||||
import org.lwjgl.glfw.GLFWKeyCallback;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import static java.lang.Math.max;
|
||||
import static java.lang.Math.min;
|
||||
|
||||
@NoArgsConstructor
|
||||
@Requires({Transform.class, Camera.class})
|
||||
public class CameraController extends Component {
|
||||
|
||||
Camera camera;
|
||||
|
@ -24,11 +27,6 @@ public class CameraController extends Component {
|
|||
private Vector2f prevMousePos;
|
||||
private Vector2f inputVectorWASD = new Vector2f();
|
||||
private boolean justCapturedMouse = true;
|
||||
public CameraController() {
|
||||
super();
|
||||
requireComponent(Transform.class);
|
||||
requireComponent(Camera.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void OnReady() {
|
100
TestGame/src/dev/euph/game/Main.java
Normal file
100
TestGame/src/dev/euph/game/Main.java
Normal file
|
@ -0,0 +1,100 @@
|
|||
package dev.euph.game;
|
||||
|
||||
import dev.euph.engine.core.ecs.Entity;
|
||||
import dev.euph.engine.core.ecs.Scene;
|
||||
import dev.euph.engine.core.transform.Transform;
|
||||
import dev.euph.engine.core.window.Window;
|
||||
import dev.euph.engine.opengl.*;
|
||||
import dev.euph.engine.rendering.Material;
|
||||
import dev.euph.engine.rendering.ShaderManager;
|
||||
import dev.euph.engine.rendering.components.Camera;
|
||||
import dev.euph.engine.rendering.components.MeshRenderer;
|
||||
import dev.euph.engine.rendering.components.lights.DirectionalLight;
|
||||
import dev.euph.engine.rendering.components.lights.PointLight;
|
||||
import dev.euph.engine.rendering.resources.Mesh;
|
||||
import dev.euph.engine.rendering.resources.Texture;
|
||||
import org.joml.Vector3f;
|
||||
|
||||
import java.awt.*;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
public class Main {
|
||||
public static void main(String[] args) throws IOException {
|
||||
Window window = new GLWindow(1200, 720, "Test Window");
|
||||
window.setVsync(1);
|
||||
|
||||
ShaderManager shaderManager = loadShaders();
|
||||
Scene scene = loadScene(window, shaderManager);
|
||||
Entity camera = scene.getByName("Camera").stream().findFirst().orElse(null);
|
||||
Entity Model = scene.getByName("Model").stream().findFirst().orElse(null);
|
||||
|
||||
GLRenderer forwardRenderer = new GLRenderer(scene, shaderManager);
|
||||
forwardRenderer.activeCamera = camera.getComponent(Camera.class);
|
||||
forwardRenderer.init();
|
||||
|
||||
float rotationAngle = 0;
|
||||
float rotationSpeed = 20f;
|
||||
scene.start();
|
||||
AtomicBoolean running = new AtomicBoolean(true);
|
||||
window.addCloseCallback(() -> {
|
||||
running.set(false);
|
||||
});
|
||||
while (running.get()) {
|
||||
scene.update(window.getTime().getDeltaTime());
|
||||
|
||||
rotationAngle += rotationSpeed * window.getTime().getDeltaTime();
|
||||
rotationAngle %= 360;
|
||||
Model.getComponent(Transform.class).setRotation(new Vector3f(0, rotationAngle, 0));
|
||||
|
||||
forwardRenderer.render();
|
||||
window.update();
|
||||
}
|
||||
shaderManager.cleanup();
|
||||
window.destroy();
|
||||
}
|
||||
|
||||
private static ShaderManager loadShaders() throws IOException {
|
||||
ShaderManager shaderManager = new ShaderManager();
|
||||
URL fragmentShader = Main.class.getResource("/dev/euph/game/shader/default.fs.glsl");
|
||||
URL vertexShader = Main.class.getResource("/dev/euph/game/shader/default.vs.glsl");
|
||||
assert vertexShader != null;
|
||||
assert fragmentShader != null;
|
||||
shaderManager.addShader(new GLShader(
|
||||
"default",
|
||||
Main.class.getResourceAsStream("/dev/euph/game/shader/default.vs.glsl"),
|
||||
Main.class.getResourceAsStream("/dev/euph/game/shader/default.fs.glsl")
|
||||
));
|
||||
return shaderManager;
|
||||
}
|
||||
|
||||
private static Scene loadScene(Window window, ShaderManager shaderManager) {
|
||||
Scene scene = new Scene();
|
||||
Entity cameraEntity = new Entity("Camera")
|
||||
.addComponent(Transform.FromPosition(0, 0, 3))
|
||||
.addComponent(new Camera(window))
|
||||
.addComponent(new CameraController())
|
||||
.addToScene(scene);
|
||||
Entity light1 = new Entity("DirectionalLight")
|
||||
.addComponent(Transform.FromRotation(-0.5f, -1.0f, -0.5f))
|
||||
.addComponent(new DirectionalLight(Color.WHITE))
|
||||
.addToScene(scene);
|
||||
Entity light2 = new Entity("PointLight")
|
||||
.addComponent(Transform.FromRotation(-0.5f, -1.0f, -0.5f))
|
||||
.addComponent(new PointLight(Color.red, new Vector3f(10)))
|
||||
.addToScene(scene);
|
||||
|
||||
Mesh modelMesh = GLMeshLoader.loadTexturedMesh(Main.class.getResource("/dev/euph/game/models/human_rigged.obj"));
|
||||
Texture uvTexture = GLTextureLoader.loadTexture(Main.class.getResource("/dev/euph/game/textures/uv.png"));
|
||||
Material modelMaterial = new Material(shaderManager.getShader("default"))
|
||||
.setColor(new Color(7, 77, 255, 255))
|
||||
.setAlbedoTexture(uvTexture);
|
||||
|
||||
Entity modelEntity = new Entity("Model")
|
||||
.addComponent(Transform.FromPosition(0, -4, -6).setScale(3))
|
||||
.addComponent(new MeshRenderer(modelMesh, modelMaterial))
|
||||
.addToScene(scene);
|
||||
return scene;
|
||||
}
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
package dev.euph.game;
|
||||
|
||||
import dev.euph.engine.datastructs.octree.Octree;
|
||||
import dev.euph.engine.core.data.octree.Octree;
|
||||
import dev.euph.engine.core.data.octree.OctreeNode;
|
||||
import org.joml.Vector3f;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
|
@ -15,7 +16,11 @@ public class OctreeTest {
|
|||
|
||||
public static void main(String[] args) {
|
||||
// Create an octree with a center at (0, 0, 0) and a half-size of 20 units
|
||||
Octree<String> octree = new Octree<>(8, new Vector3f(0, 0, 0), 20);
|
||||
Octree<String> octree = new Octree<>(new OctreeNode<String>(
|
||||
8,
|
||||
new Vector3f(),
|
||||
20
|
||||
));
|
||||
|
||||
// Generate clustered noise for more interesting patterns
|
||||
Random random = new Random();
|
|
@ -1,7 +1,7 @@
|
|||
package dev.euph.game;
|
||||
|
||||
import dev.euph.engine.datastructs.pipeline.Pipeline;
|
||||
import dev.euph.engine.datastructs.pipeline.PipelineStage;
|
||||
import dev.euph.engine.core.data.pipeline.Pipeline;
|
||||
import dev.euph.engine.core.data.pipeline.PipelineStage;
|
||||
|
||||
public class PipelineTest {
|
||||
public static void main(String[] args) {
|
|
@ -1,66 +0,0 @@
|
|||
val lwjglVersion = "3.3.3"
|
||||
val jomlVersion = "1.10.5"
|
||||
|
||||
group = "dev.euph"
|
||||
version = "1.0-SNAPSHOT"
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
java.srcDirs("src")
|
||||
resources.srcDir("res")
|
||||
}
|
||||
}
|
||||
|
||||
plugins {
|
||||
java
|
||||
application
|
||||
}
|
||||
|
||||
val lwjglNatives = Pair(
|
||||
System.getProperty("os.name")!!,
|
||||
System.getProperty("os.arch")!!
|
||||
).let { (name, arch) ->
|
||||
when {
|
||||
arrayOf("Linux", "FreeBSD", "SunOS", "Unit").any { name.startsWith(it) } ->
|
||||
if (arrayOf("arm", "aarch64").any { arch.startsWith(it) })
|
||||
"natives-linux${if (arch.contains("64") || arch.startsWith("armv8")) "-arm64" else "-arm32"}"
|
||||
else if (arch.startsWith("ppc"))
|
||||
"natives-linux-ppc64le"
|
||||
else if (arch.startsWith("riscv"))
|
||||
"natives-linux-riscv64"
|
||||
else
|
||||
"natives-linux"
|
||||
arrayOf("Mac OS X", "Darwin").any { name.startsWith(it) } ->
|
||||
"natives-macos${if (arch.startsWith("aarch64")) "-arm64" else ""}"
|
||||
arrayOf("Windows").any { name.startsWith(it) } ->
|
||||
if (arch.contains("64"))
|
||||
"natives-windows${if (arch.startsWith("aarch64")) "-arm64" else ""}"
|
||||
else
|
||||
"natives-windows-x86"
|
||||
else -> throw Error("Unrecognized or unsupported platform. Please set \"lwjglNatives\" manually")
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(platform("org.lwjgl:lwjgl-bom:$lwjglVersion"))
|
||||
|
||||
implementation("org.lwjgl", "lwjgl")
|
||||
implementation("org.lwjgl", "lwjgl-assimp")
|
||||
implementation("org.lwjgl", "lwjgl-glfw")
|
||||
implementation("org.lwjgl", "lwjgl-openal")
|
||||
implementation("org.lwjgl", "lwjgl-opengl")
|
||||
implementation("org.lwjgl", "lwjgl-remotery")
|
||||
implementation("org.lwjgl", "lwjgl-stb")
|
||||
runtimeOnly("org.lwjgl", "lwjgl", classifier = lwjglNatives)
|
||||
runtimeOnly("org.lwjgl", "lwjgl-assimp", classifier = lwjglNatives)
|
||||
runtimeOnly("org.lwjgl", "lwjgl-glfw", classifier = lwjglNatives)
|
||||
runtimeOnly("org.lwjgl", "lwjgl-openal", classifier = lwjglNatives)
|
||||
runtimeOnly("org.lwjgl", "lwjgl-opengl", classifier = lwjglNatives)
|
||||
runtimeOnly("org.lwjgl", "lwjgl-remotery", classifier = lwjglNatives)
|
||||
runtimeOnly("org.lwjgl", "lwjgl-stb", classifier = lwjglNatives)
|
||||
implementation("org.joml", "joml", jomlVersion)
|
||||
}
|
7
gradle/wrapper/gradle-wrapper.properties
vendored
7
gradle/wrapper/gradle-wrapper.properties
vendored
|
@ -1,7 +0,0 @@
|
|||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
248
gradlew
vendored
248
gradlew
vendored
|
@ -1,248 +0,0 @@
|
|||
#!/bin/sh
|
||||
|
||||
#
|
||||
# Copyright © 2015-2021 the original authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
# Gradle start up script for POSIX generated by Gradle.
|
||||
#
|
||||
# Important for running:
|
||||
#
|
||||
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
||||
# noncompliant, but you have some other compliant shell such as ksh or
|
||||
# bash, then to run this script, type that shell name before the whole
|
||||
# command line, like:
|
||||
#
|
||||
# ksh Gradle
|
||||
#
|
||||
# Busybox and similar reduced shells will NOT work, because this script
|
||||
# requires all of these POSIX shell features:
|
||||
# * functions;
|
||||
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
||||
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
||||
# * compound commands having a testable exit status, especially «case»;
|
||||
# * various built-in commands including «command», «set», and «ulimit».
|
||||
#
|
||||
# Important for patching:
|
||||
#
|
||||
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
||||
# by Bash, Ksh, etc; in particular arrays are avoided.
|
||||
#
|
||||
# The "traditional" practice of packing multiple parameters into a
|
||||
# space-separated string is a well documented source of bugs and security
|
||||
# problems, so this is (mostly) avoided, by progressively accumulating
|
||||
# options in "$@", and eventually passing that to Java.
|
||||
#
|
||||
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
||||
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
||||
# see the in-line comments for details.
|
||||
#
|
||||
# There are tweaks for specific operating systems such as AIX, CygWin,
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
|
||||
# Resolve links: $0 may be a link
|
||||
app_path=$0
|
||||
|
||||
# Need this for daisy-chained symlinks.
|
||||
while
|
||||
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||
[ -h "$app_path" ]
|
||||
do
|
||||
ls=$( ls -ld "$app_path" )
|
||||
link=${ls#*' -> '}
|
||||
case $link in #(
|
||||
/*) app_path=$link ;; #(
|
||||
*) app_path=$APP_HOME$link ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# This is normally unused
|
||||
# shellcheck disable=SC2034
|
||||
APP_BASE_NAME=${0##*/}
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD=maximum
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
} >&2
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
} >&2
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "$( uname )" in #(
|
||||
CYGWIN* ) cygwin=true ;; #(
|
||||
Darwin* ) darwin=true ;; #(
|
||||
MSYS* | MINGW* ) msys=true ;; #(
|
||||
NONSTOP* ) nonstop=true ;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD=$JAVA_HOME/jre/sh/java
|
||||
else
|
||||
JAVACMD=$JAVA_HOME/bin/java
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD=java
|
||||
if ! command -v java >/dev/null 2>&1
|
||||
then
|
||||
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
case $MAX_FD in #(
|
||||
max*)
|
||||
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC3045
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
case $MAX_FD in #(
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC3045
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
fi
|
||||
|
||||
# Collect all arguments for the java command, stacking in reverse order:
|
||||
# * args from the command line
|
||||
# * the main class name
|
||||
# * -classpath
|
||||
# * -D...appname settings
|
||||
# * --module-path (only if needed)
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
||||
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if "$cygwin" || "$msys" ; then
|
||||
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
||||
|
||||
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
for arg do
|
||||
if
|
||||
case $arg in #(
|
||||
-*) false ;; # don't mess with options #(
|
||||
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
||||
[ -e "$t" ] ;; #(
|
||||
*) false ;;
|
||||
esac
|
||||
then
|
||||
arg=$( cygpath --path --ignore --mixed "$arg" )
|
||||
fi
|
||||
# Roll the args list around exactly as many times as the number of
|
||||
# args, so each arg winds up back in the position where it started, but
|
||||
# possibly modified.
|
||||
#
|
||||
# NB: a `for` loop captures its iteration list before it begins, so
|
||||
# changing the positional parameters here affects neither the number of
|
||||
# iterations, nor the values presented in `arg`.
|
||||
shift # remove old arg
|
||||
set -- "$@" "$arg" # push replacement arg
|
||||
done
|
||||
fi
|
||||
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Collect all arguments for the java command;
|
||||
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
||||
# shell script including quotes and variable substitutions, so put them in
|
||||
# double quotes to make sure that they get re-expanded; and
|
||||
# * put everything else in single quotes, so that it's not re-expanded.
|
||||
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
-classpath "$CLASSPATH" \
|
||||
org.gradle.wrapper.GradleWrapperMain \
|
||||
"$@"
|
||||
|
||||
# Stop when "xargs" is not available.
|
||||
if ! command -v xargs >/dev/null 2>&1
|
||||
then
|
||||
die "xargs is not available"
|
||||
fi
|
||||
|
||||
# Use "xargs" to parse quoted args.
|
||||
#
|
||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||
#
|
||||
# In Bash we could simply go:
|
||||
#
|
||||
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
||||
# set -- "${ARGS[@]}" "$@"
|
||||
#
|
||||
# but POSIX shell has neither arrays nor command substitution, so instead we
|
||||
# post-process each arg (as a line of input to sed) to backslash-escape any
|
||||
# character that might be a shell metacharacter, then use eval to reverse
|
||||
# that process (while maintaining the separation between arguments), and wrap
|
||||
# the whole thing up as a single "set" statement.
|
||||
#
|
||||
# This will of course break if any of these variables contains a newline or
|
||||
# an unmatched quote.
|
||||
#
|
||||
|
||||
eval "set -- $(
|
||||
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
||||
xargs -n1 |
|
||||
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
||||
tr '\n' ' '
|
||||
)" '"$@"'
|
||||
|
||||
exec "$JAVACMD" "$@"
|
175
profiler/LICENSE
175
profiler/LICENSE
|
@ -1,175 +0,0 @@
|
|||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
10142
profiler/lib/Remotery.c
10142
profiler/lib/Remotery.c
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -1,59 +0,0 @@
|
|||
//
|
||||
// Copyright 2014-2018 Celtoys Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
#include <Foundation/NSThread.h>
|
||||
#include <Foundation/NSDictionary.h>
|
||||
#include <Foundation/NSString.h>
|
||||
|
||||
#import <Metal/Metal.h>
|
||||
|
||||
// Store command buffer in thread-local so that each thread can point to its own
|
||||
static void SetCommandBuffer(id command_buffer)
|
||||
{
|
||||
NSMutableDictionary* thread_data = [[NSThread currentThread] threadDictionary];
|
||||
thread_data[@"rmtMTLCommandBuffer"] = command_buffer;
|
||||
}
|
||||
|
||||
static id GetCommandBuffer()
|
||||
{
|
||||
NSMutableDictionary* thread_data = [[NSThread currentThread] threadDictionary];
|
||||
return thread_data[@"rmtMTLCommandBuffer"];
|
||||
}
|
||||
|
||||
extern "C" void _rmt_BindMetal(id command_buffer)
|
||||
{
|
||||
SetCommandBuffer(command_buffer);
|
||||
}
|
||||
|
||||
extern "C" void _rmt_UnbindMetal()
|
||||
{
|
||||
SetCommandBuffer(0);
|
||||
}
|
||||
|
||||
// Needs to be in the same lib for this to work
|
||||
extern "C" unsigned long long rmtMetal_usGetTime();
|
||||
|
||||
static void SetTimestamp(void* data)
|
||||
{
|
||||
*((unsigned long long*)data) = rmtMetal_usGetTime();
|
||||
}
|
||||
|
||||
extern "C" void rmtMetal_MeasureCommandBuffer(unsigned long long* out_start, unsigned long long* out_end, unsigned int* out_ready)
|
||||
{
|
||||
id command_buffer = GetCommandBuffer();
|
||||
[command_buffer addScheduledHandler:^(id <MTLCommandBuffer>){ SetTimestamp(out_start); }];
|
||||
[command_buffer addCompletedHandler:^(id <MTLCommandBuffer>){ SetTimestamp(out_end); *out_ready = 1; }];
|
||||
}
|
|
@ -1,232 +0,0 @@
|
|||
Remotery
|
||||
--------
|
||||
|
||||
[![Build](https://github.com/Celtoys/Remotery/actions/workflows/build.yml/badge.svg?branch=main)](https://github.com/Celtoys/Remotery/actions/workflows/build.yml)
|
||||
|
||||
A realtime CPU/GPU profiler hosted in a single C file with a viewer that runs in a web browser.
|
||||
|
||||
![screenshot](screenshot.png?raw=true)
|
||||
|
||||
Features:
|
||||
|
||||
* Lightweight instrumentation of multiple threads running on the CPU.
|
||||
* Web viewer that runs in Chrome, Firefox and Safari; on Desktops, Mobiles or Tablets.
|
||||
* GPU UI rendering, bypassing the DOM completely, for real-time 60hz viewer updates at 10,000x the performance.
|
||||
* Automatic thread sampler that tells you what processor cores your threads are running on without requiring Administrator privileges.
|
||||
* Drop saved traces onto the Remotery window to load historical runs for inspection.
|
||||
* Console output for logging text.
|
||||
* Console input for sending commands to your game.
|
||||
* A Property API for recording named/typed values over time, alongside samples.
|
||||
* Profiles itself and shows how it's performing in the viewer.
|
||||
|
||||
Supported Profiling Platforms:
|
||||
|
||||
* Windows 7/8/10/11/UWP (Hololens), Linux, OSX, iOS, Android, Xbox One/Series, Free BSD.
|
||||
|
||||
Supported GPU Profiling APIS:
|
||||
|
||||
* D3D 11/12, OpenGL, CUDA, Metal.
|
||||
|
||||
Compiling
|
||||
---------
|
||||
|
||||
* Windows (MSVC) - add lib/Remotery.c and lib/Remotery.h to your program. Set include
|
||||
directories to add Remotery/lib path. The required library ws2_32.lib should be picked
|
||||
up through the use of the #pragma comment(lib, "ws2_32.lib") directive in Remotery.c.
|
||||
|
||||
* Mac OS X (XCode) - simply add lib/Remotery.c, lib/Remotery.h and lib/Remotery.mm to your program.
|
||||
|
||||
* Linux (GCC) - add the source in lib folder. Compilation of the code requires -pthreads for
|
||||
library linkage. For example to compile the same run: cc lib/Remotery.c sample/sample.c
|
||||
-I lib -pthread -lm
|
||||
|
||||
* FreeBSD - the easiest way is to take a look at the official port
|
||||
([devel/remotery](https://www.freshports.org/devel/remotery/)) and modify the port's
|
||||
Makefile if needed. There is also a package available via `pkg install remotery`.
|
||||
|
||||
You can define some extra macros to modify what features are compiled into Remotery:
|
||||
|
||||
Macro Default Description
|
||||
|
||||
RMT_ENABLED 1 Disable this to not include any bits of Remotery in your build
|
||||
RMT_USE_TINYCRT 0 Used by the Celtoys TinyCRT library (not released yet)
|
||||
RMT_USE_CUDA 0 Assuming CUDA headers/libs are setup, allow CUDA profiling
|
||||
RMT_USE_D3D11 0 Assuming Direct3D 11 headers/libs are setup, allow D3D11 GPU profiling
|
||||
RMT_USE_D3D12 0 Allow D3D12 GPU profiling
|
||||
RMT_USE_OPENGL 0 Allow OpenGL GPU profiling (dynamically links OpenGL libraries on available platforms)
|
||||
RMT_USE_METAL 0 Allow Metal profiling of command buffers
|
||||
|
||||
|
||||
Basic Use
|
||||
---------
|
||||
|
||||
See the sample directory for further examples. A quick example:
|
||||
|
||||
int main()
|
||||
{
|
||||
// Create the main instance of Remotery.
|
||||
// You need only do this once per program.
|
||||
Remotery* rmt;
|
||||
rmt_CreateGlobalInstance(&rmt);
|
||||
|
||||
// Explicit begin/end for C
|
||||
{
|
||||
rmt_BeginCPUSample(LogText, 0);
|
||||
rmt_LogText("Time me, please!");
|
||||
rmt_EndCPUSample();
|
||||
}
|
||||
|
||||
// Scoped begin/end for C++
|
||||
{
|
||||
rmt_ScopedCPUSample(LogText, 0);
|
||||
rmt_LogText("Time me, too!");
|
||||
}
|
||||
|
||||
// Destroy the main instance of Remotery.
|
||||
rmt_DestroyGlobalInstance(rmt);
|
||||
}
|
||||
|
||||
|
||||
Running the Viewer
|
||||
------------------
|
||||
|
||||
Double-click or launch `vis/index.html` from the browser.
|
||||
|
||||
|
||||
Sampling CUDA GPU activity
|
||||
--------------------------
|
||||
|
||||
Remotery allows for profiling multiple threads of CUDA execution using different asynchronous streams
|
||||
that must all share the same context. After initialising both Remotery and CUDA you need to bind the
|
||||
two together using the call:
|
||||
|
||||
rmtCUDABind bind;
|
||||
bind.context = m_Context;
|
||||
bind.CtxSetCurrent = &cuCtxSetCurrent;
|
||||
bind.CtxGetCurrent = &cuCtxGetCurrent;
|
||||
bind.EventCreate = &cuEventCreate;
|
||||
bind.EventDestroy = &cuEventDestroy;
|
||||
bind.EventRecord = &cuEventRecord;
|
||||
bind.EventQuery = &cuEventQuery;
|
||||
bind.EventElapsedTime = &cuEventElapsedTime;
|
||||
rmt_BindCUDA(&bind);
|
||||
|
||||
Explicitly pointing to the CUDA interface allows Remotery to be included anywhere in your project without
|
||||
need for you to link with the required CUDA libraries. After the bind completes you can safely sample any
|
||||
CUDA activity:
|
||||
|
||||
CUstream stream;
|
||||
|
||||
// Explicit begin/end for C
|
||||
{
|
||||
rmt_BeginCUDASample(UnscopedSample, stream);
|
||||
// ... CUDA code ...
|
||||
rmt_EndCUDASample(stream);
|
||||
}
|
||||
|
||||
// Scoped begin/end for C++
|
||||
{
|
||||
rmt_ScopedCUDASample(ScopedSample, stream);
|
||||
// ... CUDA code ...
|
||||
}
|
||||
|
||||
Remotery supports only one context for all threads and will use cuCtxGetCurrent and cuCtxSetCurrent to
|
||||
ensure the current thread has the context you specify in rmtCUDABind.context.
|
||||
|
||||
|
||||
Sampling Direct3D 11 GPU activity
|
||||
---------------------------------
|
||||
|
||||
Remotery allows sampling of D3D11 GPU activity on multiple devices on multiple threads. After initialising Remotery, you need to bind it to D3D11 with a single call from the thread that owns the device context:
|
||||
|
||||
// Parameters are ID3D11Device* and ID3D11DeviceContext*
|
||||
rmt_BindD3D11(d3d11_device, d3d11_context);
|
||||
|
||||
Sampling is then a simple case of:
|
||||
|
||||
// Explicit begin/end for C
|
||||
{
|
||||
rmt_BeginD3D11Sample(UnscopedSample);
|
||||
// ... D3D code ...
|
||||
rmt_EndD3D11Sample();
|
||||
}
|
||||
|
||||
// Scoped begin/end for C++
|
||||
{
|
||||
rmt_ScopedD3D11Sample(ScopedSample);
|
||||
// ... D3D code ...
|
||||
}
|
||||
|
||||
Subsequent sampling calls from the same thread will use that device/context combination. When you shutdown your D3D11 device and context, ensure you notify Remotery before shutting down Remotery itself:
|
||||
|
||||
rmt_UnbindD3D11();
|
||||
|
||||
|
||||
Sampling OpenGL GPU activity
|
||||
----------------------------
|
||||
|
||||
Remotery allows sampling of GPU activity on your main OpenGL context. After initialising Remotery, you need
|
||||
to bind it to OpenGL with the single call:
|
||||
|
||||
rmt_BindOpenGL();
|
||||
|
||||
Sampling is then a simple case of:
|
||||
|
||||
// Explicit begin/end for C
|
||||
{
|
||||
rmt_BeginOpenGLSample(UnscopedSample);
|
||||
// ... OpenGL code ...
|
||||
rmt_EndOpenGLSample();
|
||||
}
|
||||
|
||||
// Scoped begin/end for C++
|
||||
{
|
||||
rmt_ScopedOpenGLSample(ScopedSample);
|
||||
// ... OpenGL code ...
|
||||
}
|
||||
|
||||
Support for multiple contexts can be added pretty easily if there is demand for the feature. When you shutdown
|
||||
your OpenGL device and context, ensure you notify Remotery before shutting down Remotery itself:
|
||||
|
||||
rmt_UnbindOpenGL();
|
||||
|
||||
|
||||
Sampling Metal GPU activity
|
||||
---------------------------
|
||||
|
||||
Remotery can sample Metal command buffers issued to the GPU from multiple threads. As the Metal API does not
|
||||
support finer grained profiling, samples will return only the timing of the bound command buffer, irrespective
|
||||
of how many you issue. As such, make sure you bind and sample the command buffer for each call site:
|
||||
|
||||
rmt_BindMetal(mtl_command_buffer);
|
||||
rmt_ScopedMetalSample(command_buffer_name);
|
||||
|
||||
The C API supports begin/end also:
|
||||
|
||||
rmt_BindMetal(mtl_command_buffer);
|
||||
rmt_BeginMetalSample(command_buffer_name);
|
||||
...
|
||||
rmt_EndMetalSample();
|
||||
|
||||
|
||||
Applying Configuration Settings
|
||||
-------------------------------
|
||||
|
||||
Before creating your Remotery instance, you can configure its behaviour by retrieving its settings object:
|
||||
|
||||
rmtSettings* settings = rmt_Settings();
|
||||
|
||||
Some important settings are:
|
||||
|
||||
// Redirect any Remotery allocations to your own malloc/free, with an additional context pointer
|
||||
// that gets passed to your callbacks.
|
||||
settings->malloc;
|
||||
settings->free;
|
||||
settings->mm_context;
|
||||
|
||||
// Specify an input pipelineStage that receives text input from the Remotery console, with an additional
|
||||
// context pointer that gets passed to your callback.
|
||||
// The pipelineStage will be called from the Remotery thread so synchronization with a mutex or atomics
|
||||
// might be needed to avoid race conditions with your threads.
|
||||
settings->input_handler;
|
||||
settings->input_handler_context;
|
|
@ -1,184 +0,0 @@
|
|||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "../lib/Remotery.h"
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
rmt_PropertyDefine_Group(Game, "Game Properties");
|
||||
rmt_PropertyDefine_Bool(WasUpdated, RMT_FALSE, FrameReset, "Was the game loop executed this frame?", &Game);
|
||||
rmt_PropertyDefine_U32(RecursiveDepth, 0, FrameReset, "How deep did we go in recursiveFunction?", &Game);
|
||||
rmt_PropertyDefine_F32(Accumulated, 0, FrameReset, "What was the latest value?", &Game);
|
||||
rmt_PropertyDefine_U32(FrameCounter, 0, NoFlags, "What is the current frame number?", &Game);
|
||||
|
||||
|
||||
void aggregateFunction() {
|
||||
rmt_BeginCPUSample(aggregate, RMTSF_Aggregate);
|
||||
rmt_EndCPUSample();
|
||||
}
|
||||
void recursiveFunction(int depth) {
|
||||
rmt_PropertySet_U32(RecursiveDepth, depth);
|
||||
rmt_BeginCPUSample(recursive, RMTSF_Recursive);
|
||||
if (depth < 5) {
|
||||
recursiveFunction(depth + 1);
|
||||
}
|
||||
rmt_EndCPUSample();
|
||||
}
|
||||
|
||||
double delay() {
|
||||
int i, end;
|
||||
double j = 0;
|
||||
|
||||
rmt_BeginCPUSample(delay, 0);
|
||||
for( i = 0, end = rand()/100; i < end; ++i ) {
|
||||
double v = sin(i);
|
||||
j += v;
|
||||
|
||||
rmt_PropertyAdd_F32(Accumulated, v);
|
||||
}
|
||||
recursiveFunction(0);
|
||||
aggregateFunction();
|
||||
aggregateFunction();
|
||||
aggregateFunction();
|
||||
rmt_EndCPUSample();
|
||||
return j;
|
||||
}
|
||||
|
||||
void printIndent(int indent)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < indent; ++i) {
|
||||
printf(" ");
|
||||
}
|
||||
}
|
||||
|
||||
void printSample(rmtSample* sample, int indent)
|
||||
{
|
||||
const char* name = rmt_SampleGetName(sample);
|
||||
rmtU32 callcount = rmt_SampleGetCallCount(sample);
|
||||
rmtU64 time = rmt_SampleGetTime(sample);
|
||||
rmtU64 self_time = rmt_SampleGetSelfTime(sample);
|
||||
rmtSampleType type = rmt_SampleGetType(sample);
|
||||
rmtU8 r, g, b;
|
||||
rmt_SampleGetColour(sample, &r, &g, &b);
|
||||
|
||||
printIndent(indent); printf("%s %u time: %llu self: %llu type: %d color: 0x%02x%02x%02x\n", name, callcount, time, self_time, type, r, g, b);
|
||||
}
|
||||
|
||||
void printTree(rmtSample* sample, int indent)
|
||||
{
|
||||
rmtSampleIterator iter;
|
||||
|
||||
printSample(sample, indent);
|
||||
|
||||
rmt_IterateChildren(&iter, sample);
|
||||
while (rmt_IterateNext(&iter)) {
|
||||
printTree(iter.sample, indent+1);
|
||||
}
|
||||
}
|
||||
|
||||
void dumpTree(void* ctx, rmtSampleTree* sample_tree)
|
||||
{
|
||||
rmtSample* root = rmt_SampleTreeGetRootSample(sample_tree);
|
||||
const char* thread_name = rmt_SampleTreeGetThreadName(sample_tree);
|
||||
if (strcmp("Remotery", thread_name) == 0)
|
||||
{
|
||||
return; // to minimize the verbosity in this example
|
||||
}
|
||||
|
||||
printf("// ******************** DUMP TREE: %s ************************\n", thread_name);
|
||||
|
||||
printTree(root, 0);
|
||||
}
|
||||
|
||||
void printProperty(rmtProperty* property, int indent)
|
||||
{
|
||||
rmtPropertyIterator iter;
|
||||
|
||||
const char* name = rmt_PropertyGetName(property);
|
||||
rmtPropertyType type = rmt_PropertyGetType(property);
|
||||
rmtPropertyValue value = rmt_PropertyGetValue(property);
|
||||
|
||||
printIndent(indent); printf("%s: ", name);
|
||||
|
||||
switch(type)
|
||||
{
|
||||
case RMT_PropertyType_rmtBool: printf("%s\n", value.Bool ? "true":"false"); break;
|
||||
case RMT_PropertyType_rmtS32: printf("%d\n", value.S32); break;
|
||||
case RMT_PropertyType_rmtU32: printf("%u\n", value.U32); break;
|
||||
case RMT_PropertyType_rmtF32: printf("%f\n", value.F32); break;
|
||||
case RMT_PropertyType_rmtS64: printf("%lld\n", value.S64); break;
|
||||
case RMT_PropertyType_rmtU64: printf("%llu\n", value.U64); break;
|
||||
case RMT_PropertyType_rmtF64: printf("%g\n", value.F64); break;
|
||||
case RMT_PropertyType_rmtGroup: printf("\n"); break;
|
||||
default: break;
|
||||
};
|
||||
|
||||
rmt_PropertyIterateChildren(&iter, property);
|
||||
while (rmt_PropertyIterateNext(&iter)) {
|
||||
printProperty(iter.property, indent + 1);
|
||||
}
|
||||
}
|
||||
|
||||
void dumpProperties(void* ctx, rmtProperty* root)
|
||||
{
|
||||
rmtPropertyIterator iter;
|
||||
printf("// ******************** DUMP PROPERTIES: ************************\n");
|
||||
|
||||
rmt_PropertyIterateChildren(&iter, root);
|
||||
while (rmt_PropertyIterateNext(&iter)) {
|
||||
printProperty(iter.property, 0);
|
||||
}
|
||||
}
|
||||
|
||||
int sig = 0;
|
||||
|
||||
/// Allow to close cleanly with ctrl + c
|
||||
void sigintHandler(int sig_num) {
|
||||
sig = sig_num;
|
||||
printf("Interrupted\n");
|
||||
}
|
||||
|
||||
int main() {
|
||||
Remotery* rmt;
|
||||
rmtError error;
|
||||
|
||||
signal(SIGINT, sigintHandler);
|
||||
|
||||
rmtSettings* settings = rmt_Settings();
|
||||
if (settings)
|
||||
{
|
||||
settings->sampletree_handler = dumpTree;
|
||||
settings->sampletree_context = 0;
|
||||
|
||||
settings->snapshot_callback = dumpProperties;
|
||||
settings->snapshot_context = 0;
|
||||
}
|
||||
|
||||
error = rmt_CreateGlobalInstance(&rmt);
|
||||
|
||||
if( RMT_ERROR_NONE != error) {
|
||||
printf("Error launching Remotery %d\n", error);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int max_count = 5;
|
||||
|
||||
while (sig == 0 && --max_count > 0) {
|
||||
rmt_LogText("start profiling");
|
||||
delay();
|
||||
rmt_LogText("end profiling");
|
||||
|
||||
rmt_PropertySet_Bool(WasUpdated, RMT_TRUE);
|
||||
rmt_PropertyAdd_U32(FrameCounter, 1);
|
||||
|
||||
rmt_PropertySnapshotAll();
|
||||
rmt_PropertyFrameResetAll();
|
||||
}
|
||||
|
||||
rmt_DestroyGlobalInstance(rmt);
|
||||
printf("Cleaned up and quit\n");
|
||||
return 0;
|
||||
}
|
|
@ -1,64 +0,0 @@
|
|||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include "../lib/Remotery.h"
|
||||
|
||||
void aggregateFunction() {
|
||||
rmt_BeginCPUSample(aggregate, RMTSF_Aggregate);
|
||||
rmt_EndCPUSample();
|
||||
}
|
||||
void recursiveFunction(int depth) {
|
||||
rmt_BeginCPUSample(recursive, RMTSF_Recursive);
|
||||
if (depth < 5) {
|
||||
recursiveFunction(depth + 1);
|
||||
}
|
||||
rmt_EndCPUSample();
|
||||
}
|
||||
|
||||
double delay() {
|
||||
int i, end;
|
||||
double j = 0;
|
||||
|
||||
rmt_BeginCPUSample(delay, 0);
|
||||
for( i = 0, end = rand()/100; i < end; ++i ) {
|
||||
j += sin(i);
|
||||
}
|
||||
recursiveFunction(0);
|
||||
aggregateFunction();
|
||||
aggregateFunction();
|
||||
aggregateFunction();
|
||||
rmt_EndCPUSample();
|
||||
return j;
|
||||
}
|
||||
|
||||
int sig = 0;
|
||||
|
||||
/// Allow to close cleanly with ctrl + c
|
||||
void sigintHandler(int sig_num) {
|
||||
sig = sig_num;
|
||||
printf("Interrupted\n");
|
||||
}
|
||||
|
||||
int main( ) {
|
||||
Remotery *rmt;
|
||||
rmtError error;
|
||||
|
||||
signal(SIGINT, sigintHandler);
|
||||
|
||||
error = rmt_CreateGlobalInstance(&rmt);
|
||||
if( RMT_ERROR_NONE != error) {
|
||||
printf("Error launching Remotery %d\n", error);
|
||||
return -1;
|
||||
}
|
||||
|
||||
while (sig == 0) {
|
||||
rmt_LogText("start profiling");
|
||||
delay();
|
||||
rmt_LogText("end profiling");
|
||||
}
|
||||
|
||||
rmt_DestroyGlobalInstance(rmt);
|
||||
printf("Cleaned up and quit\n");
|
||||
return 0;
|
||||
}
|
Binary file not shown.
Before Width: | Height: | Size: 164 KiB |
|
@ -1,218 +0,0 @@
|
|||
|
||||
Console = (function()
|
||||
{
|
||||
var BORDER = 10;
|
||||
var HEIGHT = 200;
|
||||
|
||||
|
||||
function Console(wm, server)
|
||||
{
|
||||
// Create the window and its controls
|
||||
this.Window = wm.AddWindow("Console", 10, 10, 100, 100);
|
||||
this.PageContainer = this.Window.AddControlNew(new WM.Container(10, 10, 400, 160));
|
||||
DOM.Node.AddClass(this.PageContainer.Node, "ConsoleText");
|
||||
this.AppContainer = this.Window.AddControlNew(new WM.Container(10, 10, 400, 160));
|
||||
DOM.Node.AddClass(this.AppContainer.Node, "ConsoleText");
|
||||
this.UserInput = this.Window.AddControlNew(new WM.EditBox(10, 5, 400, 30, "Input", ""));
|
||||
this.UserInput.SetChangeHandler(Bind(ProcessInput, this));
|
||||
this.Window.ShowNoAnim();
|
||||
|
||||
// This accumulates log text as fast as is required
|
||||
this.PageTextBuffer = "";
|
||||
this.PageTextUpdatePending = false;
|
||||
this.AppTextBuffer = "";
|
||||
this.AppTextUpdatePending = false;
|
||||
|
||||
// Setup command history control
|
||||
this.CommandHistory = LocalStore.Get("App", "Global", "CommandHistory", [ ]);
|
||||
this.CommandIndex = 0;
|
||||
this.MaxNbCommands = 10000;
|
||||
DOM.Event.AddHandler(this.UserInput.EditNode, "keydown", Bind(OnKeyPress, this));
|
||||
DOM.Event.AddHandler(this.UserInput.EditNode, "focus", Bind(OnFocus, this));
|
||||
|
||||
// At a much lower frequency this will update the console window
|
||||
window.setInterval(Bind(UpdateHTML, this), 500);
|
||||
|
||||
// Setup log requests from the server
|
||||
this.Server = server;
|
||||
server.SetConsole(this);
|
||||
server.AddMessageHandler("LOGM", Bind(OnLog, this));
|
||||
|
||||
this.Window.SetOnResize(Bind(OnUserResize, this));
|
||||
}
|
||||
|
||||
|
||||
Console.prototype.Log = function(text)
|
||||
{
|
||||
this.PageTextBuffer = LogText(this.PageTextBuffer, text);
|
||||
this.PageTextUpdatePending = true;
|
||||
}
|
||||
|
||||
|
||||
Console.prototype.WindowResized = function(width, height)
|
||||
{
|
||||
// Place window
|
||||
this.Window.SetPosition(BORDER, height - BORDER - 200);
|
||||
this.Window.SetSize(width - 2 * BORDER, HEIGHT);
|
||||
|
||||
ResizeInternals(this);
|
||||
}
|
||||
|
||||
|
||||
Console.prototype.TriggerUpdate = function()
|
||||
{
|
||||
this.AppTextUpdatePending = true;
|
||||
}
|
||||
|
||||
|
||||
function OnLog(self, socket, data_view_reader)
|
||||
{
|
||||
var text = data_view_reader.GetString();
|
||||
self.AppTextBuffer = LogText(self.AppTextBuffer, text);
|
||||
|
||||
// Don't register text as updating if disconnected as this implies a trace is being loaded, which we want to speed up
|
||||
if (self.Server.Connected())
|
||||
{
|
||||
self.AppTextUpdatePending = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function LogText(existing_text, new_text)
|
||||
{
|
||||
// Filter the text a little to make it safer
|
||||
if (new_text == null)
|
||||
new_text = "NULL";
|
||||
|
||||
// Find and convert any HTML entities, ensuring the browser doesn't parse any embedded HTML code
|
||||
// This also allows the log to contain arbitrary C++ code (e.g. assert comparison operators)
|
||||
new_text = Convert.string_to_html_entities(new_text);
|
||||
|
||||
// Prefix date and end with new line
|
||||
var d = new Date();
|
||||
new_text = "[" + d.toLocaleTimeString() + "] " + new_text + "<br>";
|
||||
|
||||
// Append to local text buffer and ensure clip the oldest text to ensure a max size
|
||||
existing_text = existing_text + new_text;
|
||||
var max_len = 100 * 1024;
|
||||
var len = existing_text.length;
|
||||
if (len > max_len)
|
||||
existing_text = existing_text.substr(len - max_len, max_len);
|
||||
|
||||
return existing_text;
|
||||
}
|
||||
|
||||
function OnUserResize(self, evt)
|
||||
{
|
||||
ResizeInternals(self);
|
||||
}
|
||||
|
||||
function ResizeInternals(self)
|
||||
{
|
||||
// Place controls
|
||||
var parent_size = self.Window.Size;
|
||||
var mid_w = parent_size[0] / 3;
|
||||
self.UserInput.SetPosition(BORDER, parent_size[1] - 2 * BORDER - 30);
|
||||
self.UserInput.SetSize(parent_size[0] - 100, 18);
|
||||
var output_height = self.UserInput.Position[1] - 2 * BORDER;
|
||||
self.PageContainer.SetPosition(BORDER, BORDER);
|
||||
self.PageContainer.SetSize(mid_w - 2 * BORDER, output_height);
|
||||
self.AppContainer.SetPosition(mid_w, BORDER);
|
||||
self.AppContainer.SetSize(parent_size[0] - mid_w - BORDER, output_height);
|
||||
}
|
||||
|
||||
|
||||
function UpdateHTML(self)
|
||||
{
|
||||
// Reset the current text buffer as html
|
||||
|
||||
if (self.PageTextUpdatePending)
|
||||
{
|
||||
var page_node = self.PageContainer.Node;
|
||||
page_node.innerHTML = self.PageTextBuffer;
|
||||
page_node.scrollTop = page_node.scrollHeight;
|
||||
self.PageTextUpdatePending = false;
|
||||
}
|
||||
|
||||
if (self.AppTextUpdatePending)
|
||||
{
|
||||
var app_node = self.AppContainer.Node;
|
||||
app_node.innerHTML = self.AppTextBuffer;
|
||||
app_node.scrollTop = app_node.scrollHeight;
|
||||
self.AppTextUpdatePending = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function ProcessInput(self, node)
|
||||
{
|
||||
// Send the message exactly
|
||||
var msg = node.value;
|
||||
self.Server.Send("CONI" + msg);
|
||||
|
||||
// Emit to console and clear
|
||||
self.Log("> " + msg);
|
||||
self.UserInput.SetValue("");
|
||||
|
||||
// Keep track of recently issued commands, with an upper bound
|
||||
self.CommandHistory.push(msg);
|
||||
var extra_commands = self.CommandHistory.length - self.MaxNbCommands;
|
||||
if (extra_commands > 0)
|
||||
self.CommandHistory.splice(0, extra_commands);
|
||||
|
||||
// Set command history index to the most recent command
|
||||
self.CommandIndex = self.CommandHistory.length;
|
||||
|
||||
// Backup to local store
|
||||
LocalStore.Set("App", "Global", "CommandHistory", self.CommandHistory);
|
||||
|
||||
// Keep focus with the edit box
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
function OnKeyPress(self, evt)
|
||||
{
|
||||
evt = DOM.Event.Get(evt);
|
||||
|
||||
if (evt.keyCode == Keyboard.Codes.UP)
|
||||
{
|
||||
if (self.CommandHistory.length > 0)
|
||||
{
|
||||
// Cycle backwards through the command history
|
||||
self.CommandIndex--;
|
||||
if (self.CommandIndex < 0)
|
||||
self.CommandIndex = self.CommandHistory.length - 1;
|
||||
var command = self.CommandHistory[self.CommandIndex];
|
||||
self.UserInput.SetValue(command);
|
||||
}
|
||||
|
||||
// Stops default behaviour of moving cursor to the beginning
|
||||
DOM.Event.StopDefaultAction(evt);
|
||||
}
|
||||
|
||||
else if (evt.keyCode == Keyboard.Codes.DOWN)
|
||||
{
|
||||
if (self.CommandHistory.length > 0)
|
||||
{
|
||||
// Cycle fowards through the command history
|
||||
self.CommandIndex = (self.CommandIndex + 1) % self.CommandHistory.length;
|
||||
var command = self.CommandHistory[self.CommandIndex];
|
||||
self.UserInput.SetValue(command);
|
||||
}
|
||||
|
||||
// Stops default behaviour of moving cursor to the end
|
||||
DOM.Event.StopDefaultAction(evt);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function OnFocus(self)
|
||||
{
|
||||
// Reset command index on focus
|
||||
self.CommandIndex = self.CommandHistory.length;
|
||||
}
|
||||
|
||||
|
||||
return Console;
|
||||
})();
|
|
@ -1,94 +0,0 @@
|
|||
|
||||
//
|
||||
// Simple wrapper around DataView that auto-advances the read offset and provides
|
||||
// a few common data type conversions specific to this app
|
||||
//
|
||||
DataViewReader = (function ()
|
||||
{
|
||||
function DataViewReader(data_view, offset)
|
||||
{
|
||||
this.DataView = data_view;
|
||||
this.Offset = offset;
|
||||
}
|
||||
|
||||
DataViewReader.prototype.AtEnd = function()
|
||||
{
|
||||
return this.Offset >= this.DataView.byteLength;
|
||||
}
|
||||
|
||||
DataViewReader.prototype.GetBool = function ()
|
||||
{
|
||||
let v = this.DataView.getUint8(this.Offset);
|
||||
this.Offset++;
|
||||
return v;
|
||||
}
|
||||
|
||||
DataViewReader.prototype.GetUInt8 = function ()
|
||||
{
|
||||
let v = this.DataView.getUint8(this.Offset);
|
||||
this.Offset++;
|
||||
return v;
|
||||
}
|
||||
|
||||
DataViewReader.prototype.GetInt32 = function ()
|
||||
{
|
||||
let v = this.DataView.getInt32(this.Offset, true);
|
||||
this.Offset += 4;
|
||||
return v;
|
||||
}
|
||||
|
||||
DataViewReader.prototype.GetUInt32 = function ()
|
||||
{
|
||||
var v = this.DataView.getUint32(this.Offset, true);
|
||||
this.Offset += 4;
|
||||
return v;
|
||||
}
|
||||
|
||||
DataViewReader.prototype.GetFloat32 = function ()
|
||||
{
|
||||
const v = this.DataView.getFloat32(this.Offset, true);
|
||||
this.Offset += 4;
|
||||
return v;
|
||||
}
|
||||
|
||||
DataViewReader.prototype.GetInt64 = function ()
|
||||
{
|
||||
var v = this.DataView.getFloat64(this.Offset, true);
|
||||
this.Offset += 8;
|
||||
return v;
|
||||
}
|
||||
|
||||
DataViewReader.prototype.GetUInt64 = function ()
|
||||
{
|
||||
var v = this.DataView.getFloat64(this.Offset, true);
|
||||
this.Offset += 8;
|
||||
return v;
|
||||
}
|
||||
|
||||
DataViewReader.prototype.GetFloat64 = function ()
|
||||
{
|
||||
var v = this.DataView.getFloat64(this.Offset, true);
|
||||
this.Offset += 8;
|
||||
return v;
|
||||
}
|
||||
|
||||
DataViewReader.prototype.GetStringOfLength = function (string_length)
|
||||
{
|
||||
var string = "";
|
||||
for (var i = 0; i < string_length; i++)
|
||||
{
|
||||
string += String.fromCharCode(this.DataView.getInt8(this.Offset));
|
||||
this.Offset++;
|
||||
}
|
||||
|
||||
return string;
|
||||
}
|
||||
|
||||
DataViewReader.prototype.GetString = function ()
|
||||
{
|
||||
var string_length = this.GetUInt32();
|
||||
return this.GetStringOfLength(string_length);
|
||||
}
|
||||
|
||||
return DataViewReader;
|
||||
})();
|
|
@ -1,123 +0,0 @@
|
|||
|
||||
class GLCanvas
|
||||
{
|
||||
constructor(width, height)
|
||||
{
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
|
||||
// Create a WebGL 2 canvas without premultiplied alpha
|
||||
this.glCanvas = document.createElement("canvas");
|
||||
this.glCanvas.width = width;
|
||||
this.glCanvas.height = height;
|
||||
this.gl = this.glCanvas.getContext("webgl2", { premultipliedAlpha: false, antialias: false });
|
||||
|
||||
// Overlay the canvas on top of everything and make sure mouse events click-through
|
||||
this.glCanvas.style.position = "fixed";
|
||||
this.glCanvas.style.pointerEvents = "none";
|
||||
this.glCanvas.style.zIndex = 1000;
|
||||
document.body.appendChild(this.glCanvas);
|
||||
|
||||
// Hook up resize event handler
|
||||
DOM.Event.AddHandler(window, "resize", () => this.OnResizeWindow());
|
||||
this.OnResizeWindow();
|
||||
|
||||
// Compile needed shaders
|
||||
this.timelineProgram = glCreateProgramFromSource(this.gl, "TimelineVShader", TimelineVShader, "TimelineFShader", TimelineFShader);
|
||||
this.timelineHighlightProgram = glCreateProgramFromSource(this.gl, "TimelineHighlightVShader", TimelineHighlightVShader, "TimelineHighlightFShader", TimelineHighlightFShader);
|
||||
this.timelineGpuToCpuProgram = glCreateProgramFromSource(this.gl, "TimelineGpuToCpuVShader", TimelineGpuToCpuVShader, "TimelineGpuToCpuFShader", TimelineGpuToCpuFShader);
|
||||
this.timelineBackgroundProgram = glCreateProgramFromSource(this.gl, "TimelineBackgroundVShader", TimelineBackgroundVShader, "TimelineBackgroundFShader", TimelineBackgroundFShader);
|
||||
this.gridProgram = glCreateProgramFromSource(this.gl, "GridVShader", GridVShader, "GridFShader", GridFShader);
|
||||
this.gridNumberProgram = glCreateProgramFromSource(this.gl, "GridNumberVShader", GridNumberVShader, "GridNumberFShader", GridNumberFShader);
|
||||
this.windowProgram = glCreateProgramFromSource(this.gl, "WindowVShader", WindowVShader, "WindowFShader", WindowFShader);
|
||||
|
||||
// Create the shader font resources
|
||||
this.font = new glFont(this.gl);
|
||||
this.textBuffer = new glTextBuffer(this.gl, this.font);
|
||||
this.nameMap = new NameMap(this.textBuffer);
|
||||
|
||||
// Kick off the rendering refresh
|
||||
this.OnDrawHandler = null;
|
||||
this.Draw(performance.now());
|
||||
}
|
||||
|
||||
SetOnDraw(handler)
|
||||
{
|
||||
this.OnDrawHandler = handler;
|
||||
}
|
||||
|
||||
ClearTextResources()
|
||||
{
|
||||
this.nameMap = new NameMap(this.textBuffer);
|
||||
}
|
||||
|
||||
OnResizeWindow()
|
||||
{
|
||||
// Resize to match the window
|
||||
this.width = window.innerWidth;
|
||||
this.height = window.innerHeight;
|
||||
this.glCanvas.width = window.innerWidth;
|
||||
this.glCanvas.height = window.innerHeight;
|
||||
}
|
||||
|
||||
SetFontUniforms(program)
|
||||
{
|
||||
// Font texture may not be loaded yet
|
||||
if (this.font.atlasTexture != null)
|
||||
{
|
||||
const gl = this.gl;
|
||||
glSetUniform(gl, program, "inFontAtlasTexture", this.font.atlasTexture, 0);
|
||||
glSetUniform(gl, program, "inTextBufferDesc.fontWidth", this.font.fontWidth);
|
||||
glSetUniform(gl, program, "inTextBufferDesc.fontHeight", this.font.fontHeight);
|
||||
}
|
||||
}
|
||||
|
||||
SetTextUniforms(program)
|
||||
{
|
||||
const gl = this.gl;
|
||||
this.SetFontUniforms(program);
|
||||
this.textBuffer.SetAsUniform(gl, program, "inTextBuffer", 1);
|
||||
}
|
||||
|
||||
SetContainerUniforms(program, container)
|
||||
{
|
||||
const gl = this.gl;
|
||||
const container_rect = container.getBoundingClientRect();
|
||||
glSetUniform(gl, program, "inContainer.x0", container_rect.left);
|
||||
glSetUniform(gl, program, "inContainer.y0", container_rect.top);
|
||||
glSetUniform(gl, program, "inContainer.x1", container_rect.left + container_rect.width);
|
||||
glSetUniform(gl, program, "inContainer.y1", container_rect.top + container_rect.height);
|
||||
}
|
||||
|
||||
EnableBlendPremulAlpha()
|
||||
{
|
||||
const gl = this.gl;
|
||||
gl.enable(gl.BLEND);
|
||||
gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
|
||||
}
|
||||
|
||||
DisableBlend()
|
||||
{
|
||||
const gl = this.gl;
|
||||
gl.disable(gl.BLEND);
|
||||
}
|
||||
|
||||
Draw(timestamp)
|
||||
{
|
||||
// Setup the viewport and clear the screen
|
||||
const gl = this.gl;
|
||||
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
|
||||
gl.clearColor(0, 0, 0, 0);
|
||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||
|
||||
// Chain to the Draw handler
|
||||
const seconds = timestamp / 1000.0;
|
||||
if (this.OnDrawHandler != null)
|
||||
{
|
||||
this.OnDrawHandler(gl, seconds);
|
||||
}
|
||||
|
||||
// Reschedule
|
||||
window.requestAnimationFrame((timestamp) => this.Draw(timestamp));
|
||||
}
|
||||
};
|
|
@ -1,291 +0,0 @@
|
|||
|
||||
class GridConfigSamples
|
||||
{
|
||||
constructor()
|
||||
{
|
||||
this.nbFloatsPerSample = g_nbFloatsPerSample;
|
||||
|
||||
this.columns = [];
|
||||
this.columns.push(new GridColumn("Sample Name", 196));
|
||||
this.columns.push(new GridColumn("Time (ms)", 56, g_sampleOffsetFloats_Length, 4));
|
||||
this.columns.push(new GridColumn("Self (ms)", 56, g_sampleOffsetFloats_Self, 4));
|
||||
this.columns.push(new GridColumn("Calls", 34, g_sampleOffsetFloats_Calls, 0));
|
||||
this.columns.push(new GridColumn("Depth", 34, g_sampleOffsetFloats_Recurse, 0));
|
||||
}
|
||||
}
|
||||
|
||||
class GridConfigProperties
|
||||
{
|
||||
constructor()
|
||||
{
|
||||
this.nbFloatsPerSample = 10;
|
||||
|
||||
this.columns = [];
|
||||
this.columns.push(new GridColumn("Property Name", 196));
|
||||
this.columns.push(new GridColumn("Value", 90, 4, 4));
|
||||
this.columns.push(new GridColumn("Prev Value", 90, 6, 4));
|
||||
}
|
||||
}
|
||||
|
||||
class GridColumn
|
||||
{
|
||||
static ColumnTemplate = `<div class="GridNameHeader"></div>`;
|
||||
|
||||
constructor(name, width, number_offset, nb_float_chars)
|
||||
{
|
||||
// Description
|
||||
this.name = name;
|
||||
this.width = width;
|
||||
this.numberOffset = number_offset;
|
||||
this.nbFloatChars = nb_float_chars;
|
||||
|
||||
// Constants
|
||||
this.rowHeight = 15;
|
||||
}
|
||||
|
||||
Attach(parent_node)
|
||||
{
|
||||
// Generate HTML for the header and parent it
|
||||
const column = DOM.Node.CreateHTML(GridColumn.ColumnTemplate);
|
||||
column.innerHTML = this.name;
|
||||
column.style.width = (this.width - 4) + "px";
|
||||
this.headerNode = parent_node.appendChild(column);
|
||||
}
|
||||
|
||||
Draw(gl_canvas, x, buffer, scroll_pos, clip, nb_entries, nb_floats_per_sample)
|
||||
{
|
||||
// If a number offset in the data stream is provided, we're rendering numbers and not names
|
||||
if (this.numberOffset !== undefined)
|
||||
{
|
||||
this._DrawNumbers(gl_canvas, x, buffer, scroll_pos, clip, nb_entries, nb_floats_per_sample);
|
||||
}
|
||||
else
|
||||
{
|
||||
this._DrawNames(gl_canvas, x, buffer, scroll_pos, clip, nb_entries, nb_floats_per_sample);
|
||||
}
|
||||
}
|
||||
|
||||
_DrawNames(gl_canvas, x, buffer, scroll_pos, clip, nb_entries, nb_floats_per_sample)
|
||||
{
|
||||
const gl = gl_canvas.gl;
|
||||
const program = gl_canvas.gridProgram;
|
||||
|
||||
gl.useProgram(program);
|
||||
gl_canvas.SetTextUniforms(program);
|
||||
|
||||
this._DrawAny(gl, program, x, buffer, scroll_pos, clip, nb_entries, nb_floats_per_sample);
|
||||
}
|
||||
|
||||
_DrawNumbers(gl_canvas, x, buffer, scroll_pos, clip, nb_entries, nb_floats_per_sample)
|
||||
{
|
||||
const gl = gl_canvas.gl;
|
||||
const program = gl_canvas.gridNumberProgram;
|
||||
|
||||
gl.useProgram(program);
|
||||
gl_canvas.SetFontUniforms(program);
|
||||
glSetUniform(gl, program, "inNumberOffset", this.numberOffset);
|
||||
glSetUniform(gl, program, "inNbFloatChars", this.nbFloatChars);
|
||||
|
||||
this._DrawAny(gl, program, x, buffer, scroll_pos, clip, nb_entries, nb_floats_per_sample);
|
||||
}
|
||||
|
||||
_DrawAny(gl, program, x, buffer, scroll_pos, clip, nb_entries, nb_floats_per_sample)
|
||||
{
|
||||
const clip_min_x = clip[0];
|
||||
const clip_min_y = clip[1];
|
||||
const clip_max_x = clip[2];
|
||||
const clip_max_y = clip[3];
|
||||
|
||||
// Scrolled position of the grid
|
||||
const pos_x = clip_min_x + scroll_pos[0] + x;
|
||||
const pos_y = clip_min_y + scroll_pos[1];
|
||||
|
||||
// Clip column to the window
|
||||
const min_x = Math.min(Math.max(clip_min_x, pos_x), clip_max_x);
|
||||
const min_y = Math.min(Math.max(clip_min_y, pos_y), clip_max_y);
|
||||
const max_x = Math.max(Math.min(clip_max_x, pos_x + this.width), clip_min_x);
|
||||
const max_y = Math.max(Math.min(clip_max_y, pos_y + nb_entries * this.rowHeight), clip_min_y);
|
||||
|
||||
// Don't render if outside the bounds of the main window
|
||||
if (min_x > gl.canvas.width || max_x < 0 || min_y > gl.canvas.height || max_y < 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const pixel_offset_x = Math.max(min_x - pos_x, 0);
|
||||
const pixel_offset_y = Math.max(min_y - pos_y, 0);
|
||||
|
||||
// Viewport constants
|
||||
glSetUniform(gl, program, "inViewport.width", gl.canvas.width);
|
||||
glSetUniform(gl, program, "inViewport.height", gl.canvas.height);
|
||||
|
||||
// Grid constants
|
||||
glSetUniform(gl, program, "inGrid.minX", min_x);
|
||||
glSetUniform(gl, program, "inGrid.minY", min_y);
|
||||
glSetUniform(gl, program, "inGrid.maxX", max_x);
|
||||
glSetUniform(gl, program, "inGrid.maxY", max_y);
|
||||
glSetUniform(gl, program, "inGrid.pixelOffsetX", pixel_offset_x);
|
||||
glSetUniform(gl, program, "inGrid.pixelOffsetY", pixel_offset_y);
|
||||
|
||||
// Source data set buffers
|
||||
glSetUniform(gl, program, "inSamples", buffer.texture, 2);
|
||||
glSetUniform(gl, program, "inSamplesLength", buffer.nbEntries);
|
||||
glSetUniform(gl, program, "inFloatsPerSample", nb_floats_per_sample);
|
||||
glSetUniform(gl, program, "inNbSamples", buffer.nbEntries / nb_floats_per_sample);
|
||||
|
||||
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
|
||||
}
|
||||
}
|
||||
|
||||
class GridWindow
|
||||
{
|
||||
static GridTemplate = `
|
||||
<div style="height:100%; overflow: hidden; position: relative; display:flex; flex-flow: column;">
|
||||
<div style="height: 16px; flex: 0 1 auto;"></div>
|
||||
<div style="flex: 1 1 auto;"></div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
constructor(wm, name, offset, gl_canvas, config)
|
||||
{
|
||||
this.nbEntries = 0;
|
||||
this.scrollPos = [ 0, 0 ];
|
||||
|
||||
// Window setup
|
||||
this.xPos = 10 + offset * 410;
|
||||
this.window = wm.AddWindow(name, 100, 100, 100, 100, null, this);
|
||||
this.window.ShowNoAnim();
|
||||
this.visible = true;
|
||||
|
||||
// Cache how much internal padding the window has, for clipping
|
||||
const style = getComputedStyle(this.window.BodyNode);
|
||||
this.bodyPadding = parseFloat(style.padding);
|
||||
|
||||
// Create the Grid host HTML
|
||||
const grid_node = DOM.Node.CreateHTML(GridWindow.GridTemplate);
|
||||
this.gridNode = this.window.BodyNode.appendChild(grid_node);
|
||||
this.headerNode = this.gridNode.children[0];
|
||||
this.contentNode = this.gridNode.children[1];
|
||||
|
||||
// Build column data
|
||||
this.nbFloatsPerSample = config.nbFloatsPerSample;
|
||||
this.columns = config.columns;
|
||||
for (let column of this.columns)
|
||||
{
|
||||
column.Attach(this.headerNode);
|
||||
}
|
||||
this._PositionHeaders();
|
||||
|
||||
// Header nodes have 1 pixel borders so the first column is required to have a width 1 less than everything else
|
||||
// To counter that, shift the first header node one to the left (it will clip) so that it can have its full width
|
||||
this.columns[0].headerNode.style.marginLeft = "-1px";
|
||||
|
||||
// Setup for pan/wheel scrolling
|
||||
this.mouseInteraction = new MouseInteraction(this.window.BodyNode);
|
||||
this.mouseInteraction.onMoveHandler = (mouse_state, mx, my) => this._OnMouseMove(mouse_state, mx, my);
|
||||
this.mouseInteraction.onScrollHandler = (mouse_state) => this._OnMouseScroll(mouse_state);
|
||||
|
||||
const gl = gl_canvas.gl;
|
||||
this.glCanvas = gl_canvas;
|
||||
this.sampleBuffer = new glDynamicBuffer(gl, glDynamicBufferType.Texture, gl.FLOAT, 1);
|
||||
}
|
||||
|
||||
Close()
|
||||
{
|
||||
this.window.Close();
|
||||
}
|
||||
|
||||
static AnimatedMove(self, top_window, bottom_window, val)
|
||||
{
|
||||
self.xPos = val;
|
||||
self.WindowResized(top_window, bottom_window);
|
||||
}
|
||||
|
||||
SetXPos(xpos, top_window, bottom_window)
|
||||
{
|
||||
Anim.Animate(
|
||||
Bind(GridWindow.AnimatedMove, this, top_window, bottom_window),
|
||||
this.xPos, 10 + xpos * 410, 0.25);
|
||||
}
|
||||
|
||||
SetVisible(visible)
|
||||
{
|
||||
if (visible != this.visible)
|
||||
{
|
||||
if (visible == true)
|
||||
this.window.ShowNoAnim();
|
||||
else
|
||||
this.window.HideNoAnim();
|
||||
|
||||
this.visible = visible;
|
||||
}
|
||||
}
|
||||
|
||||
WindowResized(top_window, bottom_window)
|
||||
{
|
||||
const top = top_window.Position[1] + top_window.Size[1] + 10;
|
||||
this.window.SetPosition(this.xPos, top_window.Position[1] + top_window.Size[1] + 10);
|
||||
this.window.SetSize(400, bottom_window.Position[1] - 10 - top);
|
||||
}
|
||||
|
||||
UpdateEntries(nb_entries, samples)
|
||||
{
|
||||
// This tracks the latest actual entry count
|
||||
this.nbEntries = nb_entries;
|
||||
|
||||
// Resize buffers to match any new entry count
|
||||
if (nb_entries * this.nbFloatsPerSample > this.sampleBuffer.nbEntries)
|
||||
{
|
||||
this.sampleBuffer.ResizeToFitNextPow2(nb_entries * this.nbFloatsPerSample);
|
||||
}
|
||||
|
||||
// Copy and upload the entry data
|
||||
this.sampleBuffer.cpuArray.set(samples);
|
||||
this.sampleBuffer.UploadData();
|
||||
}
|
||||
|
||||
Draw()
|
||||
{
|
||||
// Establish content node clipping rectangle
|
||||
const rect = this.contentNode.getBoundingClientRect();
|
||||
const clip = [
|
||||
rect.left,
|
||||
rect.top,
|
||||
rect.left + rect.width,
|
||||
rect.top + rect.height,
|
||||
];
|
||||
|
||||
// Draw columns, left-to-right
|
||||
let x = 0;
|
||||
for (let column of this.columns)
|
||||
{
|
||||
column.Draw(this.glCanvas, x, this.sampleBuffer, this.scrollPos, clip, this.nbEntries, this.nbFloatsPerSample);
|
||||
x += column.width + 1;
|
||||
}
|
||||
}
|
||||
|
||||
_PositionHeaders()
|
||||
{
|
||||
let x = this.scrollPos[0];
|
||||
for (let i in this.columns)
|
||||
{
|
||||
const column = this.columns[i];
|
||||
column.headerNode.style.left = x + "px";
|
||||
x += column.width;
|
||||
x += (i >= 1) ? 1 : 0;
|
||||
}
|
||||
}
|
||||
|
||||
_OnMouseMove(mouse_state, mouse_offset_x, mouse_offset_y)
|
||||
{
|
||||
this.scrollPos[0] = Math.min(0, this.scrollPos[0] + mouse_offset_x);
|
||||
this.scrollPos[1] = Math.min(0, this.scrollPos[1] + mouse_offset_y);
|
||||
|
||||
this._PositionHeaders();
|
||||
}
|
||||
|
||||
_OnMouseScroll(mouse_state)
|
||||
{
|
||||
this.scrollPos[1] = Math.min(0, this.scrollPos[1] + mouse_state.WheelDelta * 15);
|
||||
}
|
||||
}
|
|
@ -1,106 +0,0 @@
|
|||
class MouseInteraction
|
||||
{
|
||||
constructor(node)
|
||||
{
|
||||
this.node = node;
|
||||
|
||||
// Current interaction state
|
||||
this.mouseDown = false;
|
||||
this.lastMouseState = null;
|
||||
this.mouseMoved = false;
|
||||
|
||||
// Empty user handlers
|
||||
this.onClickHandler = null;
|
||||
this.onMoveHandler = null;
|
||||
this.onHoverHandler = null;
|
||||
this.onScrollHandler = null;
|
||||
|
||||
// Setup DOM handlers
|
||||
DOM.Event.AddHandler(this.node, "mousedown", (evt) => this._OnMouseDown(evt));
|
||||
DOM.Event.AddHandler(this.node, "mouseup", (evt) => this._OnMouseUp(evt));
|
||||
DOM.Event.AddHandler(this.node, "mousemove", (evt) => this._OnMouseMove(evt));
|
||||
DOM.Event.AddHandler(this.node, "mouseleave", (evt) => this._OnMouseLeave(evt));
|
||||
|
||||
// Mouse wheel is a little trickier
|
||||
const mouse_wheel_event = (/Firefox/i.test(navigator.userAgent)) ? "DOMMouseScroll" : "mousewheel";
|
||||
DOM.Event.AddHandler(this.node, mouse_wheel_event, (evt) => this._OnMouseScroll(evt));
|
||||
}
|
||||
|
||||
_OnMouseDown(evt)
|
||||
{
|
||||
this.mouseDown = true;
|
||||
this.lastMouseState = new Mouse.State(evt);
|
||||
this.mouseMoved = false;
|
||||
DOM.Event.StopDefaultAction(evt);
|
||||
}
|
||||
|
||||
_OnMouseUp(evt)
|
||||
{
|
||||
const mouse_state = new Mouse.State(evt);
|
||||
|
||||
this.mouseDown = false;
|
||||
|
||||
// Chain to on click event when released without movement
|
||||
if (!this.mouseMoved)
|
||||
{
|
||||
if (this.onClickHandler)
|
||||
{
|
||||
this.onClickHandler(mouse_state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_OnMouseMove(evt)
|
||||
{
|
||||
const mouse_state = new Mouse.State(evt);
|
||||
|
||||
if (this.mouseDown)
|
||||
{
|
||||
// Has the mouse moved while being held down?
|
||||
const move_offset_x = mouse_state.Position[0] - this.lastMouseState.Position[0];
|
||||
const move_offset_y = mouse_state.Position[1] - this.lastMouseState.Position[1];
|
||||
if (move_offset_x != 0 || move_offset_y != 0)
|
||||
{
|
||||
this.mouseMoved = true;
|
||||
|
||||
// Chain to move handler
|
||||
if (this.onMoveHandler)
|
||||
{
|
||||
this.onMoveHandler(mouse_state, move_offset_x, move_offset_y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Chain to hover handler
|
||||
else if (this.onHoverHandler)
|
||||
{
|
||||
this.onHoverHandler(mouse_state);
|
||||
}
|
||||
|
||||
this.lastMouseState = mouse_state;
|
||||
}
|
||||
|
||||
_OnMouseLeave(evt)
|
||||
{
|
||||
// Cancel panning
|
||||
this.mouseDown = false;
|
||||
|
||||
// Cancel hovering
|
||||
if (this.onHoverHandler)
|
||||
{
|
||||
this.onHoverHandler(null);
|
||||
}
|
||||
}
|
||||
|
||||
_OnMouseScroll(evt)
|
||||
{
|
||||
const mouse_state = new Mouse.State(evt);
|
||||
if (this.onScrollHandler)
|
||||
{
|
||||
this.onScrollHandler(mouse_state);
|
||||
|
||||
// Prevent vertical scrolling on mouse-wheel
|
||||
DOM.Event.StopDefaultAction(evt);
|
||||
}
|
||||
}
|
||||
};
|
|
@ -1,53 +0,0 @@
|
|||
class NameMap
|
||||
{
|
||||
constructor(text_buffer)
|
||||
{
|
||||
this.names = { };
|
||||
this.textBuffer = text_buffer;
|
||||
}
|
||||
|
||||
Get(name_hash)
|
||||
{
|
||||
// Return immediately if it's in the hash
|
||||
let name = this.names[name_hash];
|
||||
if (name != undefined)
|
||||
{
|
||||
return [ true, name ];
|
||||
}
|
||||
|
||||
// Create a temporary name that uses the hash
|
||||
name = {
|
||||
string: name_hash.toString(),
|
||||
hash: name_hash
|
||||
};
|
||||
this.names[name_hash] = name;
|
||||
|
||||
// Add to the text buffer the first time this name is encountered
|
||||
name.textEntry = this.textBuffer.AddText(name.string);
|
||||
|
||||
return [ false, name ];
|
||||
}
|
||||
|
||||
Set(name_hash, name_string)
|
||||
{
|
||||
// Create the name on-demand if its hash doesn't exist
|
||||
let name = this.names[name_hash];
|
||||
if (name == undefined)
|
||||
{
|
||||
name = {
|
||||
string: name_string,
|
||||
hash: name_hash
|
||||
};
|
||||
this.names[name_hash] = name;
|
||||
}
|
||||
else
|
||||
{
|
||||
name.string = name_string;
|
||||
}
|
||||
|
||||
// Apply the updated text to the buffer
|
||||
name.textEntry = this.textBuffer.AddText(name_string);
|
||||
|
||||
return name;
|
||||
}
|
||||
}
|
|
@ -1,61 +0,0 @@
|
|||
|
||||
class PixelTimeRange
|
||||
{
|
||||
constructor(start_us, span_us, span_px)
|
||||
{
|
||||
this.Span_px = span_px;
|
||||
this.Set(start_us, span_us);
|
||||
}
|
||||
|
||||
Set(start_us, span_us)
|
||||
{
|
||||
this.Start_us = start_us;
|
||||
this.Span_us = span_us;
|
||||
this.End_us = this.Start_us + span_us;
|
||||
this.usPerPixel = this.Span_px / this.Span_us;
|
||||
}
|
||||
|
||||
SetStart(start_us)
|
||||
{
|
||||
this.Start_us = start_us;
|
||||
this.End_us = start_us + this.Span_us;
|
||||
}
|
||||
|
||||
SetEnd(end_us)
|
||||
{
|
||||
this.End_us = end_us;
|
||||
this.Start_us = end_us - this.Span_us;
|
||||
}
|
||||
|
||||
SetPixelSpan(span_px)
|
||||
{
|
||||
this.Span_px = span_px;
|
||||
this.usPerPixel = this.Span_px / this.Span_us;
|
||||
}
|
||||
|
||||
PixelOffset(time_us)
|
||||
{
|
||||
return Math.floor((time_us - this.Start_us) * this.usPerPixel);
|
||||
}
|
||||
|
||||
PixelSize(time_us)
|
||||
{
|
||||
return Math.floor(time_us * this.usPerPixel);
|
||||
}
|
||||
|
||||
TimeAtPosition(position)
|
||||
{
|
||||
return this.Start_us + position / this.usPerPixel;
|
||||
}
|
||||
|
||||
Clone()
|
||||
{
|
||||
return new PixelTimeRange(this.Start_us, this.Span_us, this.Span_px);
|
||||
}
|
||||
|
||||
SetAsUniform(gl, program)
|
||||
{
|
||||
glSetUniform(gl, program, "inTimeRange.msStart", this.Start_us / 1000.0);
|
||||
glSetUniform(gl, program, "inTimeRange.msPerPixel", this.usPerPixel * 1000.0);
|
||||
}
|
||||
}
|
|
@ -1,756 +0,0 @@
|
|||
|
||||
//
|
||||
// TODO: Window resizing needs finer-grain control
|
||||
// TODO: Take into account where user has moved the windows
|
||||
// TODO: Controls need automatic resizing within their parent windows
|
||||
//
|
||||
|
||||
|
||||
Settings = (function()
|
||||
{
|
||||
function Settings()
|
||||
{
|
||||
this.IsPaused = false;
|
||||
this.SyncTimelines = true;
|
||||
}
|
||||
|
||||
return Settings;
|
||||
|
||||
})();
|
||||
|
||||
|
||||
Remotery = (function()
|
||||
{
|
||||
// crack the url and get the parameter we want
|
||||
var getUrlParameter = function getUrlParameter( search_param)
|
||||
{
|
||||
var page_url = decodeURIComponent( window.location.search.substring(1) ),
|
||||
url_vars = page_url.split('&'),
|
||||
param_name,
|
||||
i;
|
||||
|
||||
for (i = 0; i < url_vars.length; i++)
|
||||
{
|
||||
param_name = url_vars[i].split('=');
|
||||
|
||||
if (param_name[0] === search_param)
|
||||
{
|
||||
return param_name[1] === undefined ? true : param_name[1];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function Remotery()
|
||||
{
|
||||
this.WindowManager = new WM.WindowManager();
|
||||
this.Settings = new Settings();
|
||||
|
||||
// "addr" param is ip:port and will override the local store version if passed in the URL
|
||||
var addr = getUrlParameter( "addr" );
|
||||
if ( addr != null )
|
||||
this.ConnectionAddress = "ws://" + addr + "/rmt";
|
||||
else
|
||||
this.ConnectionAddress = LocalStore.Get("App", "Global", "ConnectionAddress", "ws://127.0.0.1:17815/rmt");
|
||||
|
||||
this.Server = new WebSocketConnection();
|
||||
this.Server.AddConnectHandler(Bind(OnConnect, this));
|
||||
this.Server.AddDisconnectHandler(Bind(OnDisconnect, this));
|
||||
|
||||
this.glCanvas = new GLCanvas(100, 100);
|
||||
this.glCanvas.SetOnDraw((gl, seconds) => this.OnGLCanvasDraw(gl, seconds));
|
||||
|
||||
// Create the console up front as everything reports to it
|
||||
this.Console = new Console(this.WindowManager, this.Server);
|
||||
|
||||
// Create required windows
|
||||
this.TitleWindow = new TitleWindow(this.WindowManager, this.Settings, this.Server, this.ConnectionAddress);
|
||||
this.TitleWindow.SetConnectionAddressChanged(Bind(OnAddressChanged, this));
|
||||
this.SampleTimelineWindow = new TimelineWindow(this.WindowManager, "Sample Timeline", this.Settings, Bind(OnTimelineCheck, this), this.glCanvas);
|
||||
this.SampleTimelineWindow.SetOnHover(Bind(OnSampleHover, this));
|
||||
this.SampleTimelineWindow.SetOnSelected(Bind(OnSampleSelected, this));
|
||||
this.ProcessorTimelineWindow = new TimelineWindow(this.WindowManager, "Processor Timeline", this.Settings, null, this.glCanvas);
|
||||
|
||||
this.SampleTimelineWindow.SetOnMoved(Bind(OnTimelineMoved, this));
|
||||
this.ProcessorTimelineWindow.SetOnMoved(Bind(OnTimelineMoved, this));
|
||||
|
||||
this.TraceDrop = new TraceDrop(this);
|
||||
|
||||
this.nbGridWindows = 0;
|
||||
this.gridWindows = { };
|
||||
this.FrameHistory = { };
|
||||
this.ProcessorFrameHistory = { };
|
||||
this.PropertyFrameHistory = [ ];
|
||||
this.SelectedFrames = { };
|
||||
|
||||
this.Server.AddMessageHandler("SMPL", Bind(OnSamples, this));
|
||||
this.Server.AddMessageHandler("SSMP", Bind(OnSampleName, this));
|
||||
this.Server.AddMessageHandler("PRTH", Bind(OnProcessorThreads, this));
|
||||
this.Server.AddMessageHandler("PSNP", Bind(OnPropertySnapshots, this));
|
||||
|
||||
// Kick-off the auto-connect loop
|
||||
AutoConnect(this);
|
||||
|
||||
// Hook up resize event handler
|
||||
DOM.Event.AddHandler(window, "resize", Bind(OnResizeWindow, this));
|
||||
OnResizeWindow(this);
|
||||
}
|
||||
|
||||
|
||||
Remotery.prototype.Clear = function()
|
||||
{
|
||||
// Clear timelines
|
||||
this.SampleTimelineWindow.Clear();
|
||||
this.ProcessorTimelineWindow.Clear();
|
||||
|
||||
// Close and clear all sample windows
|
||||
for (var i in this.gridWindows)
|
||||
{
|
||||
const grid_window = this.gridWindows[i];
|
||||
grid_window.Close();
|
||||
}
|
||||
this.nbGridWindows = 0;
|
||||
this.gridWindows = { };
|
||||
|
||||
this.propertyGridWindow = this.AddGridWindow("__rmt__global__properties__", "Global Properties", new GridConfigProperties());
|
||||
|
||||
// Clear runtime data
|
||||
this.FrameHistory = { };
|
||||
this.ProcessorFrameHistory = { };
|
||||
this.PropertyFrameHistory = [ ];
|
||||
this.SelectedFrames = { };
|
||||
this.glCanvas.ClearTextResources();
|
||||
|
||||
// Resize everything to fit new layout
|
||||
OnResizeWindow(this);
|
||||
}
|
||||
|
||||
function DrawWindowMask(gl, program, window_node)
|
||||
{
|
||||
gl.useProgram(program);
|
||||
|
||||
// Using depth as a mask
|
||||
gl.enable(gl.DEPTH_TEST);
|
||||
gl.depthFunc(gl.LEQUAL);
|
||||
|
||||
// Viewport constants
|
||||
glSetUniform(gl, program, "inViewport.width", gl.canvas.width);
|
||||
glSetUniform(gl, program, "inViewport.height", gl.canvas.height);
|
||||
|
||||
// Window dimensions
|
||||
const rect = window_node.getBoundingClientRect();
|
||||
glSetUniform(gl, program, "minX", rect.left);
|
||||
glSetUniform(gl, program, "minY", rect.top);
|
||||
glSetUniform(gl, program, "maxX", rect.left + rect.width);
|
||||
glSetUniform(gl, program, "maxY", rect.top + rect.height);
|
||||
|
||||
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
|
||||
}
|
||||
|
||||
Remotery.prototype.OnGLCanvasDraw = function(gl, seconds)
|
||||
{
|
||||
this.glCanvas.textBuffer.UploadData();
|
||||
|
||||
// Draw windows in their z-order, front-to-back
|
||||
// Depth test is enabled, rejecting equal z, and windows render transparent
|
||||
// Draw window content first, then draw the invisible window mask
|
||||
// Any windows that come after another window will not draw where the previous window already masked out depth
|
||||
for (let window of this.WindowManager.Windows)
|
||||
{
|
||||
// Some windows might not have WebGL drawing on them; they need to occlude as well
|
||||
DrawWindowMask(gl, this.glCanvas.windowProgram, window.Node);
|
||||
|
||||
if (window.userData != null)
|
||||
{
|
||||
window.userData.Draw();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function AutoConnect(self)
|
||||
{
|
||||
// Only attempt to connect if there isn't already a connection or an attempt to connect
|
||||
if (!self.Server.Connected() && !self.Server.Connecting())
|
||||
{
|
||||
self.Server.Connect(self.ConnectionAddress);
|
||||
}
|
||||
|
||||
// Always schedule another check
|
||||
window.setTimeout(Bind(AutoConnect, self), 2000);
|
||||
}
|
||||
|
||||
|
||||
function OnConnect(self)
|
||||
{
|
||||
// Connection address has been validated
|
||||
LocalStore.Set("App", "Global", "ConnectionAddress", self.ConnectionAddress);
|
||||
|
||||
self.Clear();
|
||||
|
||||
// Ensure the viewer is ready for realtime updates
|
||||
self.TitleWindow.Unpause();
|
||||
}
|
||||
|
||||
function OnDisconnect(self)
|
||||
{
|
||||
// Pause so the user can inspect the trace
|
||||
self.TitleWindow.Pause();
|
||||
}
|
||||
|
||||
|
||||
function OnAddressChanged(self, node)
|
||||
{
|
||||
// Update and disconnect, relying on auto-connect to reconnect
|
||||
self.ConnectionAddress = node.value;
|
||||
self.Server.Disconnect();
|
||||
|
||||
// Give input focus away
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
Remotery.prototype.AddGridWindow = function(name, display_name, config)
|
||||
{
|
||||
const grid_window = new GridWindow(this.WindowManager, display_name, this.nbGridWindows, this.glCanvas, config);
|
||||
this.gridWindows[name] = grid_window;
|
||||
this.gridWindows[name].WindowResized(this.SampleTimelineWindow.Window, this.Console.Window);
|
||||
this.nbGridWindows++;
|
||||
MoveGridWindows(this);
|
||||
return grid_window;
|
||||
}
|
||||
|
||||
|
||||
function DecodeSampleHeader(self, data_view_reader, length)
|
||||
{
|
||||
// Message-specific header
|
||||
let message = { };
|
||||
message.messageStart = data_view_reader.Offset;
|
||||
message.thread_name = data_view_reader.GetString();
|
||||
message.nb_samples = data_view_reader.GetUInt32();
|
||||
message.partial_tree = data_view_reader.GetUInt32();
|
||||
|
||||
// Align sample reading to 32-bit boundary
|
||||
const align = ((4 - (data_view_reader.Offset & 3)) & 3);
|
||||
data_view_reader.Offset += align;
|
||||
message.samplesStart = data_view_reader.Offset;
|
||||
message.samplesLength = length - (message.samplesStart - message.messageStart);
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
|
||||
function SetNanosecondsAsMilliseconds(samples_view, offset)
|
||||
{
|
||||
samples_view.setFloat32(offset, samples_view.getFloat64(offset, true) / 1000.0, true);
|
||||
}
|
||||
|
||||
|
||||
function SetUint32AsFloat32(samples_view, offset)
|
||||
{
|
||||
samples_view.setFloat32(offset, samples_view.getUint32(offset, true), true);
|
||||
}
|
||||
|
||||
|
||||
function ProcessSampleTree(self, sample_data, message)
|
||||
{
|
||||
const empty_text_entry = {
|
||||
offset: 0,
|
||||
length: 1,
|
||||
};
|
||||
|
||||
const samples_length = message.nb_samples * g_nbBytesPerSample;
|
||||
const samples_view = new DataView(sample_data, message.samplesStart, samples_length);
|
||||
message.sampleDataView = samples_view;
|
||||
|
||||
for (let offset = 0; offset < samples_length; offset += g_nbBytesPerSample)
|
||||
{
|
||||
// Get name hash and lookup in name map
|
||||
const name_hash = samples_view.getUint32(offset, true);
|
||||
const [ name_exists, name ] = self.glCanvas.nameMap.Get(name_hash);
|
||||
|
||||
// If the name doesn't exist in the map yet, request it from the server
|
||||
if (!name_exists)
|
||||
{
|
||||
if (self.Server.Connected())
|
||||
{
|
||||
self.Server.Send("GSMP" + name_hash);
|
||||
}
|
||||
}
|
||||
|
||||
// Add sample name text buffer location
|
||||
const text_entry = name.textEntry != null ? name.textEntry : empty_text_entry;
|
||||
samples_view.setFloat32(offset + g_sampleOffsetBytes_NameOffset, text_entry.offset, true);
|
||||
samples_view.setFloat32(offset + g_sampleOffsetBytes_NameLength, text_entry.length, true);
|
||||
|
||||
// Time in milliseconds
|
||||
SetNanosecondsAsMilliseconds(samples_view, offset + g_sampleOffsetBytes_Start);
|
||||
SetNanosecondsAsMilliseconds(samples_view, offset + g_sampleOffsetBytes_Length);
|
||||
SetNanosecondsAsMilliseconds(samples_view, offset + g_sampleOffsetBytes_Self);
|
||||
SetNanosecondsAsMilliseconds(samples_view, offset + g_sampleOffsetBytes_GpuToCpu);
|
||||
|
||||
// Convert call count/recursion integers to float
|
||||
SetUint32AsFloat32(samples_view, offset + g_sampleOffsetBytes_Calls);
|
||||
SetUint32AsFloat32(samples_view, offset + g_sampleOffsetBytes_Recurse);
|
||||
}
|
||||
|
||||
// Convert to floats for GPU
|
||||
message.sampleFloats = new Float32Array(sample_data, message.samplesStart, message.nb_samples * g_nbFloatsPerSample);
|
||||
}
|
||||
|
||||
function OnSamples(self, socket, data_view_reader, length)
|
||||
{
|
||||
// Discard any new samples while paused and connected
|
||||
// Otherwise this stops a paused Remotery from loading new samples from disk
|
||||
if (self.Settings.IsPaused && self.Server.Connected())
|
||||
return;
|
||||
|
||||
// Binary decode incoming sample data
|
||||
var message = DecodeSampleHeader(self, data_view_reader, length);
|
||||
if (message.nb_samples == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var name = message.thread_name;
|
||||
ProcessSampleTree(self, data_view_reader.DataView.buffer, message);
|
||||
|
||||
// Add to frame history for this thread
|
||||
var thread_frame = new ThreadFrame(message);
|
||||
if (!(name in self.FrameHistory))
|
||||
{
|
||||
self.FrameHistory[name] = [ ];
|
||||
}
|
||||
var frame_history = self.FrameHistory[name];
|
||||
if (frame_history.length > 0 && frame_history[frame_history.length - 1].PartialTree)
|
||||
{
|
||||
// Always overwrite partial trees with new information
|
||||
frame_history[frame_history.length - 1] = thread_frame;
|
||||
}
|
||||
else
|
||||
{
|
||||
frame_history.push(thread_frame);
|
||||
}
|
||||
|
||||
// Discard old frames to keep memory-use constant
|
||||
var max_nb_frames = 10000;
|
||||
var extra_frames = frame_history.length - max_nb_frames;
|
||||
if (extra_frames > 0)
|
||||
frame_history.splice(0, extra_frames);
|
||||
|
||||
// Create sample windows on-demand
|
||||
if (!(name in self.gridWindows))
|
||||
{
|
||||
self.AddGridWindow(name, name, new GridConfigSamples());
|
||||
}
|
||||
|
||||
// Set on the window and timeline if connected as this implies a trace is being loaded, which we want to speed up
|
||||
if (self.Server.Connected())
|
||||
{
|
||||
self.gridWindows[name].UpdateEntries(message.nb_samples, message.sampleFloats);
|
||||
|
||||
self.SampleTimelineWindow.OnSamples(name, frame_history);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function OnSampleName(self, socket, data_view_reader)
|
||||
{
|
||||
// Add any names sent by the server to the local map
|
||||
let name_hash = data_view_reader.GetUInt32();
|
||||
let name_string = data_view_reader.GetString();
|
||||
self.glCanvas.nameMap.Set(name_hash, name_string);
|
||||
}
|
||||
|
||||
|
||||
function OnProcessorThreads(self, socket, data_view_reader)
|
||||
{
|
||||
// Discard any new samples while paused and connected
|
||||
// Otherwise this stops a paused Remotery from loading new samples from disk
|
||||
if (self.Settings.IsPaused && self.Server.Connected())
|
||||
return;
|
||||
|
||||
let nb_processors = data_view_reader.GetUInt32();
|
||||
let message_index = data_view_reader.GetUInt64();
|
||||
|
||||
const empty_text_entry = {
|
||||
offset: 0,
|
||||
length: 1,
|
||||
};
|
||||
|
||||
// Decode each processor
|
||||
for (let i = 0; i < nb_processors; i++)
|
||||
{
|
||||
let thread_id = data_view_reader.GetUInt32();
|
||||
let thread_name_hash = data_view_reader.GetUInt32();
|
||||
let sample_time = data_view_reader.GetUInt64();
|
||||
|
||||
// Add frame history for this processor
|
||||
let processor_name = "Processor " + i.toString();
|
||||
if (!(processor_name in self.ProcessorFrameHistory))
|
||||
{
|
||||
self.ProcessorFrameHistory[processor_name] = [ ];
|
||||
}
|
||||
let frame_history = self.ProcessorFrameHistory[processor_name];
|
||||
|
||||
if (thread_id == 0xFFFFFFFF)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Try to merge this frame's samples with the previous frame if the are the same thread
|
||||
if (frame_history.length > 0)
|
||||
{
|
||||
let last_thread_frame = frame_history[frame_history.length - 1];
|
||||
if (last_thread_frame.threadId == thread_id && last_thread_frame.messageIndex == message_index - 1)
|
||||
{
|
||||
// Update last frame message index so that the next frame can check for continuity
|
||||
last_thread_frame.messageIndex = message_index;
|
||||
|
||||
// Sum time elapsed on the previous frame
|
||||
const us_length = sample_time - last_thread_frame.usLastStart;
|
||||
last_thread_frame.usLastStart = sample_time;
|
||||
last_thread_frame.EndTime_us += us_length;
|
||||
const last_length = last_thread_frame.sampleDataView.getFloat32(g_sampleOffsetBytes_Length, true);
|
||||
last_thread_frame.sampleDataView.setFloat32(g_sampleOffsetBytes_Length, last_length + us_length / 1000.0, true);
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Discard old frames to keep memory-use constant
|
||||
var max_nb_frames = 10000;
|
||||
var extra_frames = frame_history.length - max_nb_frames;
|
||||
if (extra_frames > 0)
|
||||
{
|
||||
frame_history.splice(0, extra_frames);
|
||||
}
|
||||
|
||||
// Lookup the thread name
|
||||
let [ name_exists, thread_name ] = self.glCanvas.nameMap.Get(thread_name_hash);
|
||||
|
||||
// If the name doesn't exist in the map yet, request it from the server
|
||||
if (!name_exists)
|
||||
{
|
||||
if (self.Server.Connected())
|
||||
{
|
||||
self.Server.Send("GSMP" + thread_name_hash);
|
||||
}
|
||||
}
|
||||
|
||||
// We are co-opting the sample rendering functionality of the timeline window to display processor threads as
|
||||
// thread samples. Fabricate a thread frame message, packing the processor info into one root sample.
|
||||
// TODO(don): Abstract the timeline window for pure range display as this is quite inefficient.
|
||||
let thread_message = { };
|
||||
thread_message.nb_samples = 1;
|
||||
thread_message.sampleData = new ArrayBuffer(g_nbBytesPerSample);
|
||||
thread_message.sampleDataView = new DataView(thread_message.sampleData);
|
||||
const sample_data_view = thread_message.sampleDataView;
|
||||
|
||||
// Set the name
|
||||
const text_entry = thread_name.textEntry != null ? thread_name.textEntry : empty_text_entry;
|
||||
sample_data_view.setFloat32(g_sampleOffsetBytes_NameOffset, text_entry.offset, true);
|
||||
sample_data_view.setFloat32(g_sampleOffsetBytes_NameLength, text_entry.length, true);
|
||||
|
||||
// Make a pastel-y colour from the thread name hash
|
||||
const hash = thread_name.hash;
|
||||
sample_data_view.setUint8(g_sampleOffsetBytes_Colour + 0, 127 + (hash & 255) / 2);
|
||||
sample_data_view.setUint8(g_sampleOffsetBytes_Colour + 1, 127 + ((hash >> 4) & 255) / 2);
|
||||
sample_data_view.setUint8(g_sampleOffsetBytes_Colour + 2, 127 + ((hash >> 8) & 255) / 2);
|
||||
|
||||
// Set the time
|
||||
sample_data_view.setFloat32(g_sampleOffsetBytes_Start, sample_time / 1000.0, true);
|
||||
sample_data_view.setFloat32(g_sampleOffsetBytes_Length, 0.25, true);
|
||||
|
||||
thread_message.sampleFloats = new Float32Array(thread_message.sampleData, 0, thread_message.nb_samples * g_nbFloatsPerSample);
|
||||
|
||||
// Create a thread frame and annotate with data required to merge processor samples
|
||||
let thread_frame = new ThreadFrame(thread_message);
|
||||
thread_frame.threadId = thread_id;
|
||||
thread_frame.messageIndex = message_index;
|
||||
thread_frame.usLastStart = sample_time;
|
||||
frame_history.push(thread_frame);
|
||||
|
||||
if (self.Server.Connected())
|
||||
{
|
||||
self.ProcessorTimelineWindow.OnSamples(processor_name, frame_history);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function UInt64ToFloat32(view, offset)
|
||||
{
|
||||
// Unpack as two 32-bit integers so we have a vague attempt at reconstructing the value
|
||||
const a = view.getUint32(offset + 0, true);
|
||||
const b = view.getUint32(offset + 4, true);
|
||||
|
||||
// Can't do bit arithmetic above 32-bits in JS so combine using power-of-two math
|
||||
const v = a + (b * Math.pow(2, 32));
|
||||
|
||||
// TODO(don): Potentially massive data loss!
|
||||
snapshots_view.setFloat32(offset, v);
|
||||
}
|
||||
|
||||
|
||||
function SInt64ToFloat32(view, offset)
|
||||
{
|
||||
// Unpack as two 32-bit integers so we have a vague attempt at reconstructing the value
|
||||
const a = view.getUint32(offset + 0, true);
|
||||
const b = view.getUint32(offset + 4, true);
|
||||
|
||||
// Is this negative?
|
||||
if (b & 0x80000000)
|
||||
{
|
||||
// Can only convert from twos-complement with 32-bit arithmetic so shave off the upper 32-bits
|
||||
// TODO(don): Crazy data loss here
|
||||
const v = -(~(a - 1));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Can't do bit arithmetic above 32-bits in JS so combine using power-of-two math
|
||||
const v = a + (b * Math.pow(2, 32));
|
||||
}
|
||||
|
||||
// TODO(don): Potentially massive data loss!
|
||||
snapshots_view.setFloat32(offset, v);
|
||||
}
|
||||
|
||||
|
||||
function DecodeSnapshotHeader(self, data_view_reader, length)
|
||||
{
|
||||
// Message-specific header
|
||||
let message = { };
|
||||
message.messageStart = data_view_reader.Offset;
|
||||
message.nbSnapshots = data_view_reader.GetUInt32();
|
||||
message.propertyFrame = data_view_reader.GetUInt32();
|
||||
message.snapshotsStart = data_view_reader.Offset;
|
||||
message.snapshotsLength = length - (message.snapshotsStart - message.messageStart);
|
||||
return message;
|
||||
}
|
||||
|
||||
|
||||
function ProcessSnapshots(self, snapshot_data, message)
|
||||
{
|
||||
if (self.Settings.IsPaused)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
const empty_text_entry = {
|
||||
offset: 0,
|
||||
length: 1,
|
||||
};
|
||||
|
||||
const snapshots_length = message.nbSnapshots * g_nbBytesPerSnapshot;
|
||||
const snapshots_view = new DataView(snapshot_data, message.snapshotsStart, snapshots_length);
|
||||
|
||||
for (let offset = 0; offset < snapshots_length; offset += g_nbBytesPerSnapshot)
|
||||
{
|
||||
// Get name hash and lookup in name map
|
||||
const name_hash = snapshots_view.getUint32(offset, true);
|
||||
const [ name_exists, name ] = self.glCanvas.nameMap.Get(name_hash);
|
||||
|
||||
// If the name doesn't exist in the map yet, request it from the server
|
||||
if (!name_exists)
|
||||
{
|
||||
if (self.Server.Connected())
|
||||
{
|
||||
self.Server.Send("GSMP" + name_hash);
|
||||
}
|
||||
}
|
||||
|
||||
// Add snapshot name text buffer location
|
||||
const text_entry = name.textEntry != null ? name.textEntry : empty_text_entry;
|
||||
snapshots_view.setFloat32(offset + 0, text_entry.offset, true);
|
||||
snapshots_view.setFloat32(offset + 4, text_entry.length, true);
|
||||
|
||||
// Heat colour style falloff to quickly identify modified properties
|
||||
let r = 255, g = 255, b = 255;
|
||||
const prev_value_frame = snapshots_view.getUint32(offset + 32, true);
|
||||
const frame_delta = message.propertyFrame - prev_value_frame;
|
||||
if (frame_delta < 64)
|
||||
{
|
||||
g = Math.min(Math.min(frame_delta, 32) * 8, 255);
|
||||
b = Math.min(frame_delta * 4, 255);
|
||||
}
|
||||
snapshots_view.setUint8(offset + 8, r);
|
||||
snapshots_view.setUint8(offset + 9, g);
|
||||
snapshots_view.setUint8(offset + 10, b);
|
||||
|
||||
const snapshot_type = snapshots_view.getUint32(offset + 12, true);
|
||||
switch (snapshot_type)
|
||||
{
|
||||
case 1:
|
||||
case 2:
|
||||
case 3:
|
||||
case 4:
|
||||
case 7:
|
||||
snapshots_view.setFloat32(offset + 16, snapshots_view.getFloat64(offset + 16, true), true);
|
||||
snapshots_view.setFloat32(offset + 24, snapshots_view.getFloat64(offset + 24, true), true);
|
||||
break;
|
||||
|
||||
// Unpack 64-bit integers stored full precision in the logs and view them to the best of our current abilities
|
||||
case 5:
|
||||
SInt64ToFloat32(snapshots_view, offset + 16);
|
||||
SInt64ToFloat32(snapshots_view, offset + 24);
|
||||
case 6:
|
||||
UInt64ToFloat32(snapshots_view, offset + 16);
|
||||
UInt64ToFloat32(snapshots_view, offset + 24);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Convert to floats for GPU
|
||||
return new Float32Array(snapshot_data, message.snapshotsStart, message.nbSnapshots * g_nbFloatsPerSnapshot);
|
||||
}
|
||||
|
||||
|
||||
function OnPropertySnapshots(self, socket, data_view_reader, length)
|
||||
{
|
||||
// Discard any new snapshots while paused and connected
|
||||
// Otherwise this stops a paused Remotery from loading new samples from disk
|
||||
if (self.Settings.IsPaused && self.Server.Connected())
|
||||
return;
|
||||
|
||||
// Binary decode incoming snapshot data
|
||||
const message = DecodeSnapshotHeader(self, data_view_reader, length);
|
||||
message.snapshotFloats = ProcessSnapshots(self, data_view_reader.DataView.buffer, message);
|
||||
|
||||
// Add to frame history
|
||||
const thread_frame = new PropertySnapshotFrame(message);
|
||||
const frame_history = self.PropertyFrameHistory;
|
||||
frame_history.push(thread_frame);
|
||||
|
||||
// Discard old frames to keep memory-use constant
|
||||
var max_nb_frames = 10000;
|
||||
var extra_frames = frame_history.length - max_nb_frames;
|
||||
if (extra_frames > 0)
|
||||
frame_history.splice(0, extra_frames);
|
||||
|
||||
// Set on the window if connected as this implies a trace is being loaded, which we want to speed up
|
||||
if (self.Server.Connected())
|
||||
{
|
||||
self.propertyGridWindow.UpdateEntries(message.nbSnapshots, message.snapshotFloats);
|
||||
}
|
||||
}
|
||||
|
||||
function OnTimelineCheck(self, name, evt)
|
||||
{
|
||||
// Show/hide the equivalent sample window and move all the others to occupy any left-over space
|
||||
var target = DOM.Event.GetNode(evt);
|
||||
self.gridWindows[name].SetVisible(target.checked);
|
||||
MoveGridWindows(self);
|
||||
}
|
||||
|
||||
|
||||
function MoveGridWindows(self)
|
||||
{
|
||||
// Stack all windows next to each other
|
||||
let xpos = 0;
|
||||
for (let i in self.gridWindows)
|
||||
{
|
||||
const grid_window = self.gridWindows[i];
|
||||
if (grid_window.visible)
|
||||
{
|
||||
grid_window.SetXPos(xpos++, self.SampleTimelineWindow.Window, self.Console.Window);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function OnSampleHover(self, thread_name, hover)
|
||||
{
|
||||
if (!self.Settings.IsPaused)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Search for the grid window for the thread being hovered over
|
||||
for (let window_thread_name in self.gridWindows)
|
||||
{
|
||||
if (window_thread_name == thread_name)
|
||||
{
|
||||
const grid_window = self.gridWindows[thread_name];
|
||||
|
||||
// Populate with the sample under hover
|
||||
if (hover != null)
|
||||
{
|
||||
const frame = hover[0];
|
||||
grid_window.UpdateEntries(frame.NbSamples, frame.sampleFloats);
|
||||
}
|
||||
|
||||
// When there's no hover, go back to the selected frame
|
||||
else if (self.SelectedFrames[thread_name])
|
||||
{
|
||||
const frame = self.SelectedFrames[thread_name];
|
||||
grid_window.UpdateEntries(frame.NbSamples, frame.sampleFloats);
|
||||
}
|
||||
|
||||
// Otherwise display the last sample in the frame
|
||||
else
|
||||
{
|
||||
const frames = self.FrameHistory[thread_name];
|
||||
const frame = frames[frames.length - 1];
|
||||
grid_window.UpdateEntries(frame.NbSamples, frame.sampleFloats);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function OnSampleSelected(self, thread_name, select)
|
||||
{
|
||||
// Lookup sample window
|
||||
if (thread_name in self.gridWindows)
|
||||
{
|
||||
const grid_window = self.gridWindows[thread_name];
|
||||
|
||||
// Set the grid window to the selected frame if valid
|
||||
if (select)
|
||||
{
|
||||
const frame = select[0];
|
||||
self.SelectedFrames[thread_name] = frame;
|
||||
grid_window.UpdateEntries(frame.NbSamples, frame.sampleFloats);
|
||||
}
|
||||
|
||||
// Otherwise deselect
|
||||
else
|
||||
{
|
||||
const frames = self.FrameHistory[thread_name];
|
||||
const frame = frames[frames.length - 1];
|
||||
self.SelectedFrames[thread_name] = null;
|
||||
grid_window.UpdateEntries(frame.NbSamples, frame.sampleFloats);
|
||||
self.SampleTimelineWindow.Deselect(thread_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function OnResizeWindow(self)
|
||||
{
|
||||
var w = window.innerWidth;
|
||||
var h = window.innerHeight;
|
||||
|
||||
// Resize windows
|
||||
self.Console.WindowResized(w, h);
|
||||
self.TitleWindow.WindowResized(w, h);
|
||||
self.SampleTimelineWindow.WindowResized(10, w / 2 - 5, self.TitleWindow.Window);
|
||||
self.ProcessorTimelineWindow.WindowResized(w / 2 + 5, w / 2 - 5, self.TitleWindow.Window);
|
||||
for (var i in self.gridWindows)
|
||||
{
|
||||
self.gridWindows[i].WindowResized(self.SampleTimelineWindow.Window, self.Console.Window);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function OnTimelineMoved(self, timeline)
|
||||
{
|
||||
if (self.Settings.SyncTimelines)
|
||||
{
|
||||
let other_timeline = timeline == self.ProcessorTimelineWindow ? self.SampleTimelineWindow : self.ProcessorTimelineWindow;
|
||||
other_timeline.SetTimeRange(timeline.TimeRange.Start_us, timeline.TimeRange.Span_us);
|
||||
}
|
||||
}
|
||||
|
||||
return Remotery;
|
||||
})();
|
|
@ -1,28 +0,0 @@
|
|||
|
||||
// Sample properties when viewed as an array of floats
|
||||
const g_nbFloatsPerSample = 14;
|
||||
const g_sampleOffsetFloats_NameOffset = 0;
|
||||
const g_sampleOffsetFloats_NameLength = 1;
|
||||
const g_sampleOffsetFloats_Start = 3;
|
||||
const g_sampleOffsetFloats_Length = 5;
|
||||
const g_sampleOffsetFloats_Self = 7;
|
||||
const g_sampleOffsetFloats_GpuToCpu = 9;
|
||||
const g_sampleOffsetFloats_Calls = 11;
|
||||
const g_sampleOffsetFloats_Recurse = 12;
|
||||
|
||||
// Sample properties when viewed as bytes
|
||||
const g_nbBytesPerSample = g_nbFloatsPerSample * 4;
|
||||
const g_sampleOffsetBytes_NameOffset = g_sampleOffsetFloats_NameOffset * 4;
|
||||
const g_sampleOffsetBytes_NameLength = g_sampleOffsetFloats_NameLength * 4;
|
||||
const g_sampleOffsetBytes_Colour = 8;
|
||||
const g_sampleOffsetBytes_Depth = 11;
|
||||
const g_sampleOffsetBytes_Start = g_sampleOffsetFloats_Start * 4;
|
||||
const g_sampleOffsetBytes_Length = g_sampleOffsetFloats_Length * 4;
|
||||
const g_sampleOffsetBytes_Self = g_sampleOffsetFloats_Self * 4;
|
||||
const g_sampleOffsetBytes_GpuToCpu = g_sampleOffsetFloats_GpuToCpu * 4;
|
||||
const g_sampleOffsetBytes_Calls = g_sampleOffsetFloats_Calls * 4;
|
||||
const g_sampleOffsetBytes_Recurse = g_sampleOffsetFloats_Recurse * 4;
|
||||
|
||||
// Snapshot properties
|
||||
const g_nbFloatsPerSnapshot = 10;
|
||||
const g_nbBytesPerSnapshot = g_nbFloatsPerSnapshot * 4;
|
|
@ -1,162 +0,0 @@
|
|||
const GridShaderShared = ShaderShared + `
|
||||
|
||||
#define RowHeight 15.0
|
||||
|
||||
struct Grid
|
||||
{
|
||||
float minX;
|
||||
float minY;
|
||||
float maxX;
|
||||
float maxY;
|
||||
float pixelOffsetX;
|
||||
float pixelOffsetY;
|
||||
};
|
||||
|
||||
uniform Viewport inViewport;
|
||||
uniform Grid inGrid;
|
||||
|
||||
float Row(vec2 pixel_position)
|
||||
{
|
||||
return floor(pixel_position.y / RowHeight);
|
||||
}
|
||||
|
||||
vec3 RowColour(float row)
|
||||
{
|
||||
float row_grey = (int(row) & 1) == 0 ? 0.25 : 0.23;
|
||||
return vec3(row_grey);
|
||||
}
|
||||
|
||||
`;
|
||||
|
||||
// -------------------------------------------------------------------------------------------------------------------------------
|
||||
// Vertex Shader
|
||||
// -------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
const GridVShader = GridShaderShared + `
|
||||
|
||||
out vec2 varPixelPosition;
|
||||
|
||||
void main()
|
||||
{
|
||||
vec2 position = QuadPosition(gl_VertexID, inGrid.minX, inGrid.minY, inGrid.maxX, inGrid.maxY);
|
||||
vec4 ndc_pos = UVToNDC(inViewport, position);
|
||||
|
||||
gl_Position = ndc_pos;
|
||||
varPixelPosition = position - vec2(inGrid.minX, inGrid.minY) + vec2(inGrid.pixelOffsetX, inGrid.pixelOffsetY);
|
||||
}
|
||||
`;
|
||||
|
||||
const GridNumberVShader = GridShaderShared + `
|
||||
|
||||
out vec2 varPixelPosition;
|
||||
|
||||
void main()
|
||||
{
|
||||
vec2 position = QuadPosition(gl_VertexID, inGrid.minX, inGrid.minY, inGrid.maxX, inGrid.maxY);
|
||||
vec4 ndc_pos = UVToNDC(inViewport, position);
|
||||
|
||||
gl_Position = ndc_pos;
|
||||
varPixelPosition = position - vec2(inGrid.minX, inGrid.minY) + vec2(inGrid.pixelOffsetX, inGrid.pixelOffsetY);
|
||||
}
|
||||
`;
|
||||
|
||||
// -------------------------------------------------------------------------------------------------------------------------------
|
||||
// Fragment Shader
|
||||
// -------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
const GridFShader = GridShaderShared + `
|
||||
|
||||
// Array of samples
|
||||
uniform sampler2D inSamples;
|
||||
uniform float inSamplesLength;
|
||||
uniform float inFloatsPerSample;
|
||||
uniform float inNbSamples;
|
||||
|
||||
in vec2 varPixelPosition;
|
||||
|
||||
out vec4 outColour;
|
||||
|
||||
void main()
|
||||
{
|
||||
// Font description
|
||||
float font_width_px = inTextBufferDesc.fontWidth;
|
||||
float font_height_px = inTextBufferDesc.fontHeight;
|
||||
float text_buffer_length = inTextBufferDesc.textBufferLength;
|
||||
|
||||
// Which row are we on?
|
||||
float row = Row(varPixelPosition);
|
||||
vec3 row_colour = RowColour(row);
|
||||
|
||||
float text_weight = 0.0;
|
||||
vec3 text_colour = vec3(0.0);
|
||||
if (row < inNbSamples)
|
||||
{
|
||||
// Unpack colour and depth
|
||||
int colour_depth = floatBitsToInt(TextureBufferLookup(inSamples, row * inFloatsPerSample + 2.0, inSamplesLength).r);
|
||||
text_colour.r = float(colour_depth & 255) / 255.0;
|
||||
text_colour.g = float((colour_depth >> 8) & 255) / 255.0;
|
||||
text_colour.b = float((colour_depth >> 16) & 255) / 255.0;
|
||||
float depth = float(colour_depth >> 24);
|
||||
|
||||
float text_buffer_offset = TextureBufferLookup(inSamples, row * inFloatsPerSample + 0.0, inSamplesLength).r;
|
||||
float text_length_chars = TextureBufferLookup(inSamples, row * inFloatsPerSample + 1.0, inSamplesLength).r;
|
||||
float text_length_px = text_length_chars * font_width_px;
|
||||
|
||||
// Pixel position within the row
|
||||
vec2 pos_in_box_px;
|
||||
pos_in_box_px.x = varPixelPosition.x;
|
||||
pos_in_box_px.y = varPixelPosition.y - row * RowHeight;
|
||||
|
||||
// Get text at this position
|
||||
vec2 text_start_px = vec2(4.0 + depth * 10.0, 3.0);
|
||||
text_weight = LookupText(pos_in_box_px - text_start_px, text_buffer_offset, text_length_chars);
|
||||
}
|
||||
|
||||
outColour = vec4(mix(row_colour, text_colour, text_weight), 1.0);
|
||||
}
|
||||
`;
|
||||
|
||||
const GridNumberFShader = GridShaderShared + `
|
||||
|
||||
// Array of samples
|
||||
uniform sampler2D inSamples;
|
||||
uniform float inSamplesLength;
|
||||
uniform float inFloatsPerSample;
|
||||
uniform float inNbSamples;
|
||||
|
||||
// Offset within the sample
|
||||
uniform float inNumberOffset;
|
||||
|
||||
uniform float inNbFloatChars;
|
||||
|
||||
in vec2 varPixelPosition;
|
||||
|
||||
out vec4 outColour;
|
||||
|
||||
void main()
|
||||
{
|
||||
// Font description
|
||||
float font_width_px = inTextBufferDesc.fontWidth;
|
||||
float font_height_px = inTextBufferDesc.fontHeight;
|
||||
float text_buffer_length = inTextBufferDesc.textBufferLength;
|
||||
|
||||
// Which row are we on?
|
||||
float row = Row(varPixelPosition);
|
||||
vec3 row_colour = RowColour(row);
|
||||
float text_weight = 0.0;
|
||||
if (row < inNbSamples)
|
||||
{
|
||||
// Pixel position within the row
|
||||
vec2 pos_in_box_px;
|
||||
pos_in_box_px.x = varPixelPosition.x;
|
||||
pos_in_box_px.y = varPixelPosition.y - row * RowHeight;
|
||||
|
||||
// Get the number at this pixel
|
||||
const vec2 text_start_px = vec2(4.0, 3.0);
|
||||
float number = TextureBufferLookup(inSamples, row * inFloatsPerSample + inNumberOffset, inSamplesLength).r;
|
||||
text_weight = LookupNumber(pos_in_box_px - text_start_px, number, inNbFloatChars);
|
||||
}
|
||||
|
||||
outColour = vec4(mix(row_colour, vec3(1.0), text_weight), 1.0);
|
||||
}
|
||||
`;
|
|
@ -1,154 +0,0 @@
|
|||
const ShaderShared = `#version 300 es
|
||||
|
||||
precision mediump float;
|
||||
|
||||
struct Viewport
|
||||
{
|
||||
float width;
|
||||
float height;
|
||||
};
|
||||
|
||||
vec2 QuadPosition(int vertex_id, float min_x, float min_y, float max_x, float max_y)
|
||||
{
|
||||
// Quad indices are:
|
||||
//
|
||||
// 2 3
|
||||
// +----+
|
||||
// | |
|
||||
// +----+
|
||||
// 0 1
|
||||
//
|
||||
vec2 position;
|
||||
position.x = (vertex_id & 1) == 0 ? min_x : max_x;
|
||||
position.y = (vertex_id & 2) == 0 ? min_y : max_y;
|
||||
return position;
|
||||
}
|
||||
|
||||
vec4 UVToNDC(Viewport viewport, vec2 uv)
|
||||
{
|
||||
//
|
||||
// NDC is:
|
||||
// -1 to 1, left to right
|
||||
// -1 to 1, bottom to top
|
||||
//
|
||||
vec4 ndc_pos;
|
||||
ndc_pos.x = (uv.x / viewport.width) * 2.0 - 1.0;
|
||||
ndc_pos.y = 1.0 - (uv.y / viewport.height) * 2.0;
|
||||
ndc_pos.z = 0.0;
|
||||
ndc_pos.w = 1.0;
|
||||
return ndc_pos;
|
||||
}
|
||||
|
||||
vec4 TextureBufferLookup(sampler2D sampler, float index, float length)
|
||||
{
|
||||
vec2 uv = vec2((index + 0.5) / length, 0.5);
|
||||
return texture(sampler, uv);
|
||||
}
|
||||
|
||||
struct TextBufferDesc
|
||||
{
|
||||
float fontWidth;
|
||||
float fontHeight;
|
||||
float textBufferLength;
|
||||
};
|
||||
|
||||
uniform sampler2D inFontAtlasTexture;
|
||||
uniform sampler2D inTextBuffer;
|
||||
uniform TextBufferDesc inTextBufferDesc;
|
||||
|
||||
float LookupCharacter(float char_ascii, float pos_x, float pos_y, float font_width_px, float font_height_px)
|
||||
{
|
||||
// 2D index of the ASCII character in the font atlas
|
||||
float char_index_y = floor(char_ascii / 16.0);
|
||||
float char_index_x = char_ascii - char_index_y * 16.0;
|
||||
|
||||
// Start UV of the character in the font atlas
|
||||
float char_base_uv_x = char_index_x / 16.0;
|
||||
float char_base_uv_y = char_index_y / 16.0;
|
||||
|
||||
// UV within the character itself, scaled to the font atlas
|
||||
float char_uv_x = pos_x / (font_width_px * 16.0);
|
||||
float char_uv_y = pos_y / (font_height_px * 16.0);
|
||||
|
||||
vec2 uv;
|
||||
uv.x = char_base_uv_x + char_uv_x;
|
||||
uv.y = char_base_uv_y + char_uv_y;
|
||||
|
||||
// Strip colour and return alpha only
|
||||
return texture(inFontAtlasTexture, uv).a;
|
||||
}
|
||||
|
||||
float LookupText(vec2 render_pos_px, float text_buffer_offset, float text_length_chars)
|
||||
{
|
||||
// Font description
|
||||
float font_width_px = inTextBufferDesc.fontWidth;
|
||||
float font_height_px = inTextBufferDesc.fontHeight;
|
||||
float text_buffer_length = inTextBufferDesc.textBufferLength;
|
||||
float text_length_px = text_length_chars * font_width_px;
|
||||
|
||||
// Text pixel position clamped to the bounds of the full word, allowing leakage to neighbouring NULL characters to pad zeroes
|
||||
vec2 text_pixel_pos;
|
||||
text_pixel_pos.x = max(min(render_pos_px.x, text_length_px), -1.0);
|
||||
text_pixel_pos.y = max(min(render_pos_px.y, font_height_px - 1.0), 0.0);
|
||||
|
||||
// Index of the current character in the text buffer
|
||||
float text_index = text_buffer_offset + floor(text_pixel_pos.x / font_width_px);
|
||||
|
||||
// Sample the 1D text buffer to get the ASCII character index
|
||||
float char_ascii = TextureBufferLookup(inTextBuffer, text_index, text_buffer_length).a * 255.0;
|
||||
|
||||
return LookupCharacter(char_ascii,
|
||||
text_pixel_pos.x - (text_index - text_buffer_offset) * font_width_px,
|
||||
text_pixel_pos.y,
|
||||
font_width_px, font_height_px);
|
||||
}
|
||||
|
||||
float NbIntegerCharsForNumber(float number)
|
||||
{
|
||||
float number_int = floor(number);
|
||||
return number_int == 0.0 ? 1.0 : floor(log(number_int) / 2.302585092994046) + 1.0;
|
||||
}
|
||||
|
||||
// Base-10 lookup table for shifting digits of a float to the range 0-9 where they can be rendered
|
||||
const float g_Multipliers[14] = float[14](
|
||||
// Decimal part multipliers
|
||||
1000.0, 100.0, 10.0,
|
||||
// Zero entry for maintaining the ASCII "." base when rendering the period
|
||||
0.0,
|
||||
// Integer part multipliers
|
||||
1.0, 0.1, 0.01, 0.001, 0.0001, 0.00001, 0.000001, 0.0000001, 0.00000001, 0.000000001 );
|
||||
|
||||
float LookupNumber(vec2 render_pos_px, float number, float nb_float_chars)
|
||||
{
|
||||
// Font description
|
||||
float font_width_px = inTextBufferDesc.fontWidth;
|
||||
float font_height_px = inTextBufferDesc.fontHeight;
|
||||
float text_buffer_length = inTextBufferDesc.textBufferLength;
|
||||
|
||||
float number_integer_chars = NbIntegerCharsForNumber(number);
|
||||
|
||||
// Clip
|
||||
render_pos_px.y = max(min(render_pos_px.y, font_height_px - 1.0), 0.0);
|
||||
|
||||
float number_index = floor(render_pos_px.x / font_width_px);
|
||||
|
||||
if (number_index >= 0.0 && number_index < number_integer_chars + nb_float_chars)
|
||||
{
|
||||
// When we are indexing the period separating integer and decimal, set the base to ASCII "."
|
||||
// The lookup table stores zero for this entry, multipying with the addend to produce no shift from this base
|
||||
float base = (number_index == number_integer_chars) ? 46.0 : 48.0;
|
||||
|
||||
// Calculate digit using the current number index base-10 shift
|
||||
float multiplier = g_Multipliers[int(number_integer_chars - number_index) + 3];
|
||||
float number_shifted_int = floor(number * multiplier);
|
||||
float number_digit = floor(mod(number_shifted_int, 10.0));
|
||||
|
||||
return LookupCharacter(base + number_digit,
|
||||
render_pos_px.x - number_index * font_width_px,
|
||||
render_pos_px.y,
|
||||
font_width_px, font_height_px);
|
||||
}
|
||||
|
||||
return 0.0;
|
||||
}
|
||||
`;
|
|
@ -1,337 +0,0 @@
|
|||
const TimelineShaderShared = ShaderShared + `
|
||||
|
||||
#define SAMPLE_HEIGHT 16.0
|
||||
#define SAMPLE_BORDER 2.0
|
||||
#define SAMPLE_Y_SPACING (SAMPLE_HEIGHT + SAMPLE_BORDER * 2.0)
|
||||
|
||||
#define PIXEL_ROUNDED_OFFSETS
|
||||
|
||||
struct Container
|
||||
{
|
||||
float x0;
|
||||
float y0;
|
||||
float x1;
|
||||
float y1;
|
||||
};
|
||||
|
||||
struct TimeRange
|
||||
{
|
||||
float msStart;
|
||||
float msPerPixel;
|
||||
};
|
||||
|
||||
struct Row
|
||||
{
|
||||
float yOffset;
|
||||
};
|
||||
|
||||
uniform Viewport inViewport;
|
||||
uniform TimeRange inTimeRange;
|
||||
uniform Container inContainer;
|
||||
uniform Row inRow;
|
||||
|
||||
float PixelOffset(float time_ms)
|
||||
{
|
||||
float offset = (time_ms - inTimeRange.msStart) * inTimeRange.msPerPixel;
|
||||
#ifdef PIXEL_ROUNDED_OFFSETS
|
||||
return floor(offset);
|
||||
#else
|
||||
return offset;
|
||||
#endif
|
||||
}
|
||||
|
||||
float PixelSize(float time_ms)
|
||||
{
|
||||
float size = time_ms * inTimeRange.msPerPixel;
|
||||
#ifdef PIXEL_ROUNDED_OFFSETS
|
||||
return floor(size);
|
||||
#else
|
||||
return size;
|
||||
#endif
|
||||
}
|
||||
|
||||
vec4 SampleQuad(int vertex_id, vec4 in_sample_textoffset, float padding, out vec4 out_quad_pos_size_px)
|
||||
{
|
||||
// Unpack input data
|
||||
float ms_start = in_sample_textoffset.x;
|
||||
float ms_length = in_sample_textoffset.y;
|
||||
float depth = in_sample_textoffset.z;
|
||||
|
||||
// Determine pixel range of the sample
|
||||
float x0 = PixelOffset(ms_start);
|
||||
float x1 = x0 + PixelSize(ms_length);
|
||||
|
||||
// Calculate box to render
|
||||
// Ensure no sample is less than one pixel in length and so is always visible
|
||||
float offset_x = inContainer.x0 + x0 - padding;
|
||||
float offset_y = inRow.yOffset + (depth - 1.0) * SAMPLE_Y_SPACING + SAMPLE_BORDER - padding;
|
||||
float size_x = max(x1 - x0, 1.0) + padding * 2.0;
|
||||
float size_y = SAMPLE_HEIGHT + padding * 2.0;
|
||||
|
||||
// Box range clipped to container bounds
|
||||
float min_x = min(max(offset_x, inContainer.x0), inContainer.x1);
|
||||
float min_y = min(max(offset_y, inContainer.y0), inContainer.y1);
|
||||
float max_x = min(max(offset_x + size_x, inContainer.x0), inContainer.x1);
|
||||
float max_y = min(max(offset_y + size_y, inContainer.y0), inContainer.y1);
|
||||
|
||||
// Box quad position in NDC
|
||||
vec2 position = QuadPosition(vertex_id, min_x, min_y, max_x, max_y);
|
||||
vec4 ndc_pos = UVToNDC(inViewport, position);
|
||||
|
||||
out_quad_pos_size_px.xy = vec2(position.x - offset_x, position.y - offset_y);
|
||||
out_quad_pos_size_px.zw = vec2(max_x - min_x, max_y - min_y);
|
||||
|
||||
return ndc_pos;
|
||||
}
|
||||
|
||||
`;
|
||||
|
||||
// -------------------------------------------------------------------------------------------------------------------------------
|
||||
// Sample Rendering
|
||||
// -------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
const TimelineVShader = TimelineShaderShared + `
|
||||
|
||||
in vec4 inSample_TextOffset;
|
||||
in vec4 inColour_TextLength;
|
||||
|
||||
out vec4 varColour_TimeMs;
|
||||
out vec4 varPosInBoxPx_TextEntry;
|
||||
out float varTimeChars;
|
||||
|
||||
void main()
|
||||
{
|
||||
// Unpack input data
|
||||
float ms_length = inSample_TextOffset.y;
|
||||
float text_buffer_offset = inSample_TextOffset.w;
|
||||
vec3 box_colour = inColour_TextLength.rgb;
|
||||
float text_length_chars = inColour_TextLength.w;
|
||||
|
||||
// Calculate number of characters required to display the millisecond time
|
||||
float time_chars = NbIntegerCharsForNumber(ms_length);
|
||||
|
||||
// Calculate sample quad vertex positions
|
||||
vec4 quad_pos_size_px;
|
||||
gl_Position = SampleQuad(gl_VertexID, inSample_TextOffset, 0.0, quad_pos_size_px);
|
||||
|
||||
// Pack for fragment shader
|
||||
varColour_TimeMs = vec4(box_colour / 255.0, ms_length);
|
||||
varPosInBoxPx_TextEntry = vec4(quad_pos_size_px.x, quad_pos_size_px.y, text_buffer_offset, text_length_chars);
|
||||
varTimeChars = time_chars;
|
||||
}
|
||||
`;
|
||||
|
||||
const TimelineFShader = TimelineShaderShared + `
|
||||
|
||||
in vec4 varColour_TimeMs;
|
||||
in vec4 varPosInBoxPx_TextEntry;
|
||||
in float varTimeChars;
|
||||
|
||||
out vec4 outColour;
|
||||
|
||||
void main()
|
||||
{
|
||||
// Font description
|
||||
float font_width_px = inTextBufferDesc.fontWidth;
|
||||
float font_height_px = inTextBufferDesc.fontHeight;
|
||||
|
||||
// Text range in the text buffer
|
||||
vec2 pos_in_box_px = varPosInBoxPx_TextEntry.xy;
|
||||
float text_buffer_offset = varPosInBoxPx_TextEntry.z;
|
||||
float text_length_chars = varPosInBoxPx_TextEntry.w;
|
||||
float text_length_px = text_length_chars * font_width_px;
|
||||
|
||||
// Text placement offset within the box
|
||||
const vec2 text_start_px = vec2(10.0, 3.0);
|
||||
|
||||
vec3 box_colour = varColour_TimeMs.rgb;
|
||||
|
||||
// Add a subtle border to the box so that you can visually separate samples when they are next to each other
|
||||
vec2 top_left = min(pos_in_box_px.xy, 2.0);
|
||||
float both = min(top_left.x, top_left.y);
|
||||
box_colour *= (0.8 + both * 0.1);
|
||||
|
||||
float text_weight = 0.0;
|
||||
|
||||
// Are we over the time number or the text?
|
||||
float text_end_px = text_start_px.x + text_length_px;
|
||||
float number_start_px = text_end_px + font_width_px * 2.0;
|
||||
if (pos_in_box_px.x > number_start_px)
|
||||
{
|
||||
vec2 time_pixel_pos;
|
||||
time_pixel_pos.x = pos_in_box_px.x - number_start_px;
|
||||
time_pixel_pos.y = max(min(pos_in_box_px.y - text_start_px.y, font_height_px - 1.0), 0.0);
|
||||
|
||||
// Time number
|
||||
float time_ms = varColour_TimeMs.w;
|
||||
float time_index = floor(time_pixel_pos.x / font_width_px);
|
||||
if (time_index < varTimeChars + 4.0)
|
||||
{
|
||||
text_weight = LookupNumber(time_pixel_pos, time_ms, 4.0);
|
||||
}
|
||||
|
||||
// " ms" label at the end of the time
|
||||
else if (time_index < varTimeChars + 7.0)
|
||||
{
|
||||
const float ms[3] = float[3] ( 32.0, 109.0, 115.0 );
|
||||
float char = ms[int(time_index - (varTimeChars + 4.0))];
|
||||
text_weight = LookupCharacter(char, time_pixel_pos.x - time_index * font_width_px, time_pixel_pos.y, font_width_px, font_height_px);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
text_weight = LookupText(pos_in_box_px - text_start_px, text_buffer_offset, text_length_chars);
|
||||
}
|
||||
|
||||
// Blend text onto the box
|
||||
vec3 text_colour = vec3(0.0, 0.0, 0.0);
|
||||
outColour = vec4(mix(box_colour, text_colour, text_weight), 1.0);
|
||||
}
|
||||
`;
|
||||
|
||||
// -------------------------------------------------------------------------------------------------------------------------------
|
||||
// Sample Highlights
|
||||
// -------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
const TimelineHighlightVShader = TimelineShaderShared + `
|
||||
|
||||
uniform float inStartMs;
|
||||
uniform float inLengthMs;
|
||||
uniform float inDepth;
|
||||
|
||||
out vec4 varPosSize;
|
||||
|
||||
void main()
|
||||
{
|
||||
// Calculate sample quad vertex positions
|
||||
gl_Position = SampleQuad(gl_VertexID, vec4(inStartMs, inLengthMs, inDepth, 0.0), 1.0, varPosSize);
|
||||
}
|
||||
`;
|
||||
|
||||
const TimelineHighlightFShader = TimelineShaderShared + `
|
||||
|
||||
// TODO(don): Vector uniforms, please!
|
||||
uniform float inColourR;
|
||||
uniform float inColourG;
|
||||
uniform float inColourB;
|
||||
|
||||
in vec4 varPosSize;
|
||||
|
||||
out vec4 outColour;
|
||||
|
||||
void main()
|
||||
{
|
||||
// Rounded pixel co-ordinates interpolating across the sample
|
||||
vec2 pos = floor(varPosSize.xy);
|
||||
|
||||
// Sample size in pixel co-ordinates
|
||||
vec2 size = floor(varPosSize.zw);
|
||||
|
||||
// Highlight thickness
|
||||
float t = 2.0;
|
||||
|
||||
// Distance along axes to highlight edges
|
||||
vec2 dmin = abs(pos - 0.0);
|
||||
vec2 dmax = abs(pos - (size - 1.0));
|
||||
|
||||
// Take the closest distance
|
||||
float dx = min(dmin.x, dmax.x);
|
||||
float dy = min(dmin.y, dmax.y);
|
||||
float d = min(dx, dy);
|
||||
|
||||
// Invert the distance and clamp to thickness
|
||||
d = (t + 1.0) - min(d, t + 1.0);
|
||||
|
||||
// Scale with thickness for uniform intensity
|
||||
d = d / (t + 1.0);
|
||||
outColour = vec4(inColourR * d, inColourG * d, inColourB * d, d);
|
||||
}
|
||||
`;
|
||||
|
||||
// -------------------------------------------------------------------------------------------------------------------------------
|
||||
// GPU->CPU Sample Sources
|
||||
// -------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
const TimelineGpuToCpuVShader = TimelineShaderShared + `
|
||||
|
||||
uniform float inStartMs;
|
||||
uniform float inLengthMs;
|
||||
uniform float inDepth;
|
||||
|
||||
out vec4 varPosSize;
|
||||
|
||||
void main()
|
||||
{
|
||||
// Calculate sample quad vertex positions
|
||||
gl_Position = SampleQuad(gl_VertexID, vec4(inStartMs, inLengthMs, inDepth, 0.0), 1.0, varPosSize);
|
||||
}
|
||||
`;
|
||||
|
||||
|
||||
const TimelineGpuToCpuFShader = TimelineShaderShared + `
|
||||
|
||||
in vec4 varPosSize;
|
||||
|
||||
out vec4 outColour;
|
||||
|
||||
void main()
|
||||
{
|
||||
// Rounded pixel co-ordinates interpolating across the sample
|
||||
vec2 pos = floor(varPosSize.xy);
|
||||
|
||||
// Sample size in pixel co-ordinates
|
||||
vec2 size = floor(varPosSize.zw);
|
||||
|
||||
// Distance to centre line, bumped out every period to create a dash
|
||||
float dc = abs(pos.y - size.y / 2.0);
|
||||
dc += (int(pos.x / 3.0) & 1) == 0 ? 100.0 : 0.0;
|
||||
|
||||
// Min with the start line
|
||||
float ds = abs(pos.x - 0.0);
|
||||
float d = min(dc, ds);
|
||||
|
||||
// Invert the distance for highlight
|
||||
d = 1.0 - min(d, 1.0);
|
||||
|
||||
outColour = vec4(d, d, d, d);
|
||||
}
|
||||
|
||||
`;
|
||||
|
||||
// -------------------------------------------------------------------------------------------------------------------------------
|
||||
// Background
|
||||
// -------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
const TimelineBackgroundVShader = TimelineShaderShared + `
|
||||
|
||||
uniform float inYOffset;
|
||||
|
||||
out vec2 varPosition;
|
||||
|
||||
void main()
|
||||
{
|
||||
|
||||
// Container quad position in NDC
|
||||
vec2 position = QuadPosition(gl_VertexID, inContainer.x0, inContainer.y0, inContainer.x1, inContainer.y1);
|
||||
gl_Position = UVToNDC(inViewport, position);
|
||||
|
||||
// Offset Y with scroll position
|
||||
varPosition = vec2(position.x, position.y - inYOffset);
|
||||
}
|
||||
|
||||
`;
|
||||
|
||||
const TimelineBackgroundFShader = TimelineShaderShared + `
|
||||
|
||||
in vec2 varPosition;
|
||||
|
||||
out vec4 outColour;
|
||||
|
||||
void main()
|
||||
{
|
||||
vec2 pos = floor(varPosition);
|
||||
float f = round(fract(pos.y / SAMPLE_Y_SPACING) * SAMPLE_Y_SPACING);
|
||||
float g = f >= 1.0 && f <= (SAMPLE_Y_SPACING - 2.0) ? 0.30 : 0.23;
|
||||
outColour = vec4(g, g, g, 1.0);
|
||||
}
|
||||
`;
|
|
@ -1,33 +0,0 @@
|
|||
// -------------------------------------------------------------------------------------------------------------------------------
|
||||
// Vertex Shader
|
||||
// -------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
const WindowVShader = ShaderShared + `
|
||||
|
||||
uniform Viewport inViewport;
|
||||
|
||||
uniform float minX;
|
||||
uniform float minY;
|
||||
uniform float maxX;
|
||||
uniform float maxY;
|
||||
|
||||
void main()
|
||||
{
|
||||
vec2 position = QuadPosition(gl_VertexID, minX, minY, maxX, maxY);
|
||||
gl_Position = UVToNDC(inViewport, position);
|
||||
}
|
||||
`;
|
||||
|
||||
// -------------------------------------------------------------------------------------------------------------------------------
|
||||
// Fragment Shader
|
||||
// -------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
const WindowFShader = ShaderShared + `
|
||||
|
||||
out vec4 outColour;
|
||||
|
||||
void main()
|
||||
{
|
||||
outColour = vec4(1.0, 1.0, 1.0, 0.0);
|
||||
}
|
||||
`;
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue