summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDan Goodliffe <dan@randomdan.homeip.net>2023-04-22 19:06:42 +0100
committerDan Goodliffe <dan@randomdan.homeip.net>2023-04-22 19:06:42 +0100
commitd1f79b4c438ebf0822741e103b2cb06bdee4514e (patch)
treea4d574d12cd81d5e33886ce7ca961f7dbe872fcf
parentRename lots of shader files (diff)
parentKeep the instance unused vector sorted and binary search it (diff)
downloadilt-d1f79b4c438ebf0822741e103b2cb06bdee4514e.tar.bz2
ilt-d1f79b4c438ebf0822741e103b2cb06bdee4514e.tar.xz
ilt-d1f79b4c438ebf0822741e103b2cb06bdee4514e.zip
Merge branch 'instancing'
-rw-r--r--application/main.cpp8
-rw-r--r--game/gamestate.h2
-rw-r--r--game/scenary/foliage.cpp22
-rw-r--r--game/scenary/foliage.h10
-rw-r--r--game/scenary/plant.cpp12
-rw-r--r--game/scenary/plant.h12
-rw-r--r--gfx/gl/instanceVertices.h211
-rw-r--r--gfx/gl/sceneRenderer.cpp6
-rw-r--r--gfx/gl/sceneShader.cpp29
-rw-r--r--gfx/gl/sceneShader.h3
-rw-r--r--gfx/gl/shaders/dynamicPointInst.vs21
-rw-r--r--gfx/gl/shaders/spotLight.vs2
-rw-r--r--gfx/gl/vertexArrayObject.hpp107
-rw-r--r--gfx/models/mesh.cpp27
-rw-r--r--gfx/models/mesh.h4
-rw-r--r--lib/gl_traits.hpp17
-rw-r--r--test/Jamfile.jam3
-rw-r--r--test/test-assetFactory.cpp8
-rw-r--r--test/test-instancing.cpp181
-rw-r--r--test/test-render.cpp3
-rw-r--r--test/testMainWindow.cpp3
-rw-r--r--ui/gameMainWindow.cpp10
22 files changed, 598 insertions, 103 deletions
diff --git a/application/main.cpp b/application/main.cpp
index aea3d2e..9feb80b 100644
--- a/application/main.cpp
+++ b/application/main.cpp
@@ -44,9 +44,9 @@ public:
windows.create<GameMainWindow>(DISPLAY_WIDTH, DISPLAY_HEIGHT);
world.create<Terrain>(geoData);
+ assets = AssetFactory::loadAll("res");
{
- const auto assets = AssetFactory::loadAll("res");
auto rl = world.create<RailLinks>();
const glm::vec3 j {-1120, -1100, 3}, k {-1100, -1000, 15}, l {-1000, -800, 20}, m {-900, -600, 30},
n {-600, -500, 32}, o {-500, -800, 30}, p {-600, -900, 25}, q {-1025, -1175, 10},
@@ -79,7 +79,11 @@ public:
train->currentActivity = train->orders.current()->createActivity();
auto foliage = std::dynamic_pointer_cast<Foliage>(assets.at("Tree-01-1"));
- world.create<Plant>(foliage, Location {{-1100, -1100, 0}});
+ for (float x = 900; x < 1100; x += 3) {
+ for (float y = 900; y < 1100; y += 3) {
+ world.create<Plant>(foliage, Location {geoData->positionAt({-x, -y})});
+ }
+ }
}
auto t_start = std::chrono::high_resolution_clock::now();
diff --git a/game/gamestate.h b/game/gamestate.h
index 605aac4..db223c0 100644
--- a/game/gamestate.h
+++ b/game/gamestate.h
@@ -1,5 +1,6 @@
#pragma once
+#include "assetFactory/assetFactory.h"
#include <collection.hpp>
#include <memory>
#include <special_members.hpp>
@@ -16,5 +17,6 @@ public:
Collection<WorldObject> world;
std::shared_ptr<GeoData> geoData;
+ AssetFactory::Assets assets;
};
extern GameState * gameState;
diff --git a/game/scenary/foliage.cpp b/game/scenary/foliage.cpp
index d39d500..35be051 100644
--- a/game/scenary/foliage.cpp
+++ b/game/scenary/foliage.cpp
@@ -1,7 +1,9 @@
#include "foliage.h"
#include "gfx/gl/sceneShader.h"
#include "gfx/gl/shadowMapper.h"
+#include "gfx/gl/vertexArrayObject.hpp"
#include "gfx/models/texture.h"
+#include "location.hpp"
bool
Foliage::persist(Persistence::PersistenceStore & store)
@@ -13,21 +15,25 @@ void
Foliage::postLoad()
{
texture = getTexture();
+ bodyMesh->configureVAO(instanceVAO).addAttribs<glm::mat4>(instances.bufferName(), 1);
}
void
-Foliage::render(const SceneShader & shader, const Location & loc) const
+Foliage::render(const SceneShader & shader) const
{
- shader.basic.use(loc);
- if (texture) {
- texture->bind();
+ if (const auto count = instances.count()) {
+ shader.basicInst.use();
+ if (texture) {
+ texture->bind();
+ }
+ glBindVertexArray(instanceVAO);
+ glDrawElementsInstanced(
+ bodyMesh->type(), bodyMesh->count(), GL_UNSIGNED_INT, nullptr, static_cast<GLsizei>(count));
+ glBindVertexArray(0);
}
- bodyMesh->Draw();
}
void
-Foliage::shadows(const ShadowMapper & mapper, const Location & loc) const
+Foliage::shadows(const ShadowMapper &) const
{
- mapper.dynamicPoint.use(loc);
- bodyMesh->Draw();
}
diff --git a/game/scenary/foliage.h b/game/scenary/foliage.h
index b85aab2..b72a9c2 100644
--- a/game/scenary/foliage.h
+++ b/game/scenary/foliage.h
@@ -1,19 +1,23 @@
#pragma once
#include "assetFactory/asset.h"
+#include "gfx/gl/instanceVertices.h"
+#include "gfx/renderable.h"
class SceneShader;
class ShadowMapper;
class Location;
class Texture;
-class Foliage : public Asset, public StdTypeDefs<Foliage> {
+class Foliage : public Asset, public Renderable, public StdTypeDefs<Foliage> {
Mesh::Ptr bodyMesh;
std::shared_ptr<Texture> texture;
+ glVertexArray instanceVAO;
public:
- void render(const SceneShader &, const Location &) const;
- void shadows(const ShadowMapper &, const Location &) const;
+ mutable InstanceVertices<glm::mat4> instances;
+ void render(const SceneShader &) const override;
+ void shadows(const ShadowMapper &) const override;
protected:
friend Persistence::SelectionPtrBase<std::shared_ptr<Foliage>>;
diff --git a/game/scenary/plant.cpp b/game/scenary/plant.cpp
index 2b01bee..678d4a7 100644
--- a/game/scenary/plant.cpp
+++ b/game/scenary/plant.cpp
@@ -1,13 +1,7 @@
#include "plant.h"
-void
-Plant::render(const SceneShader & shader) const
+Plant::Plant(std::shared_ptr<const Foliage> type, Location position) :
+ type {std::move(type)},
+ location {this->type->instances.acquire(glm::translate(position.pos) * rotate_ypr(position.rot))}
{
- type->render(shader, position);
-}
-
-void
-Plant::shadows(const ShadowMapper & mapper) const
-{
- type->shadows(mapper, position);
}
diff --git a/game/scenary/plant.h b/game/scenary/plant.h
index 55acca1..77c5979 100644
--- a/game/scenary/plant.h
+++ b/game/scenary/plant.h
@@ -2,15 +2,13 @@
#include "foliage.h"
#include "game/worldobject.h"
-#include "gfx/renderable.h"
#include "location.hpp"
+#include "maths.h"
+#include <glm/gtx/transform.hpp>
-class Plant : public Renderable, public WorldObject {
+class Plant : public WorldObject {
std::shared_ptr<const Foliage> type;
- Location position;
-
- void render(const SceneShader & shader) const override;
- void shadows(const ShadowMapper & shadowMapper) const override;
+ InstanceVertices<glm::mat4>::InstanceProxy location;
void
tick(TickDuration) override
@@ -18,5 +16,5 @@ class Plant : public Renderable, public WorldObject {
}
public:
- Plant(std::shared_ptr<const Foliage> type, Location position) : type(std::move(type)), position(position) { }
+ Plant(std::shared_ptr<const Foliage> type, Location position);
};
diff --git a/gfx/gl/instanceVertices.h b/gfx/gl/instanceVertices.h
new file mode 100644
index 0000000..228020d
--- /dev/null
+++ b/gfx/gl/instanceVertices.h
@@ -0,0 +1,211 @@
+#pragma once
+
+#include "glArrays.h"
+#include <iterator>
+#include <span>
+#include <special_members.hpp>
+#include <utility>
+#include <vector>
+
+template<typename T> class InstanceVertices {
+public:
+ InstanceVertices(size_t initialSize = 16)
+ {
+ allocBuffer(initialSize);
+ }
+
+ class [[nodiscard]] InstanceProxy {
+ public:
+ InstanceProxy(InstanceVertices * iv, std::size_t idx) : instances {iv}, index {idx} { }
+ InstanceProxy(InstanceProxy && other) : instances {std::exchange(other.instances, nullptr)}, index {other.index}
+ {
+ }
+ NO_COPY(InstanceProxy);
+
+ ~InstanceProxy()
+ {
+ if (instances) {
+ instances->release(index);
+ }
+ }
+
+ InstanceProxy &
+ operator=(InstanceProxy && other)
+ {
+ if (instances) {
+ instances->release(index);
+ }
+ instances = std::exchange(other.instances, nullptr);
+ index = other.index;
+ return *this;
+ }
+ template<typename U>
+ T &
+ operator=(U && v)
+ {
+ return instances->at(index) = std::forward<U>(v);
+ }
+
+ [[nodiscard]]
+ operator T &()
+ {
+ return instances->at(index);
+ }
+ [[nodiscard]] operator const T &() const
+ {
+ return instances->at(index);
+ }
+ [[nodiscard]] T *
+ get()
+ {
+ return &instances->at(index);
+ }
+ [[nodiscard]] const T *
+ get() const
+ {
+ return &instances->at(index);
+ }
+ [[nodiscard]] T *
+ operator->()
+ {
+ return get();
+ }
+ [[nodiscard]] const T *
+ operator->() const
+ {
+ return get();
+ }
+ [[nodiscard]] T &
+ operator*()
+ {
+ return instances->at(index);
+ }
+ [[nodiscard]] const T &
+ operator*() const
+ {
+ return instances->at(index);
+ }
+
+ private:
+ InstanceVertices<T> * instances;
+ std::size_t index;
+ };
+
+ template<typename... Params>
+ [[nodiscard]] InstanceProxy
+ acquire(Params &&... params)
+ {
+ map();
+ if (!unused.empty()) {
+ auto idx = unused.back();
+ unused.pop_back();
+ index[idx] = next++;
+ new (&at(idx)) T(std::forward<Params>(params)...);
+ return InstanceProxy {this, idx};
+ }
+ if (next >= capacity) {
+ resize(capacity * 2);
+ }
+ index.emplace_back(next++);
+ new (data + index.back()) T(std::forward<Params>(params)...);
+ return InstanceProxy {this, index.size() - 1};
+ }
+
+ [[nodiscard]] const auto &
+ bufferName() const
+ {
+ return buffer;
+ }
+
+ [[nodiscard]] auto
+ count() const
+ {
+ unmap();
+ return next;
+ }
+
+protected:
+ friend InstanceProxy;
+
+ void
+ release(const size_t pidx)
+ {
+ // Destroy p's object
+ at(pidx).~T();
+ if (--next != index[pidx]) {
+ // Move last object into p's slot
+ new (&at(pidx)) T {std::move(data[next])};
+ (data[next]).~T();
+ *std::find_if(index.begin(), index.end(), [this](const auto & i) {
+ return i == next && !std::binary_search(unused.begin(), unused.end(), &i - index.data());
+ }) = index[pidx];
+ }
+ if (pidx == index.size() - 1) {
+ index.pop_back();
+ }
+ else {
+ // Remember p.index is free index now, keeping it sorted
+ unused.insert(std::upper_bound(unused.begin(), unused.end(), pidx), pidx);
+ }
+ }
+
+ void
+ allocBuffer(std::size_t newCapacity)
+ {
+ glBindBuffer(GL_ARRAY_BUFFER, buffer);
+ glBufferData(GL_ARRAY_BUFFER, static_cast<GLsizeiptr>(sizeof(T) * newCapacity), nullptr, GL_DYNAMIC_DRAW);
+ glBindBuffer(GL_ARRAY_BUFFER, 0);
+ capacity = newCapacity;
+ data = nullptr;
+ }
+
+ void
+ resize(size_t newCapacity)
+ {
+ const auto maintain = std::min(newCapacity, capacity);
+ std::vector<T> existing;
+ const auto maintaind = static_cast<typename decltype(existing)::difference_type>(maintain);
+ existing.reserve(maintain);
+ map();
+ std::move(data, data + maintain, std::back_inserter(existing));
+ allocBuffer(newCapacity);
+ map();
+ std::move(existing.begin(), existing.begin() + maintaind, data);
+ capacity = newCapacity;
+ }
+
+ [[nodiscard]] T &
+ at(size_t pindex)
+ {
+ map();
+ return data[index[pindex]];
+ }
+
+ void
+ map() const
+ {
+ if (!data) {
+ data = static_cast<T *>(glMapNamedBuffer(buffer, GL_READ_WRITE));
+ }
+ }
+
+ void
+ unmap() const
+ {
+ if (data) {
+ glUnmapNamedBuffer(buffer);
+ data = nullptr;
+ }
+ }
+
+ glBuffer buffer;
+ mutable T * data {};
+ // Size of buffer
+ std::size_t capacity {};
+ // # used of capacity
+ std::size_t next {};
+ // Index into buffer given to nth proxy
+ std::vector<size_t> index;
+ // List of free spaces in index
+ std::vector<size_t> unused;
+};
diff --git a/gfx/gl/sceneRenderer.cpp b/gfx/gl/sceneRenderer.cpp
index 873dc5b..6542bea 100644
--- a/gfx/gl/sceneRenderer.cpp
+++ b/gfx/gl/sceneRenderer.cpp
@@ -18,7 +18,7 @@ SceneRenderer::SceneRenderer(glm::ivec2 s, GLuint o) :
lighting {lighting_vs, lighting_fs}, shadowMapper {{2048, 2048}}
{
shader.setViewPort({0, 0, size.x, size.y});
- VertexArrayObject<glm::i8vec4>::configure(displayVAO, displayVBO, displayVAOdata);
+ VertexArrayObject {displayVAO}.addAttribs<glm::i8vec4>(displayVBO, displayVAOdata);
glBindFramebuffer(GL_FRAMEBUFFER, gBuffer);
const auto configuregdata
@@ -128,8 +128,8 @@ SceneRenderer::renderQuad() const
SceneRenderer::DirectionalLightProgram::DirectionalLightProgram() :
Program {lighting_vs, directionalLight_fs}, directionLoc {*this, "lightDirection"},
colourLoc {*this, "lightColour"}, lightViewProjectionLoc {*this, "lightViewProjection"},
- lightViewProjectionCountLoc {*this, "lightViewProjectionCount"}, lightViewShadowMapRegionLoc {
- *this, "shadowMapRegion"}
+ lightViewProjectionCountLoc {*this, "lightViewProjectionCount"},
+ lightViewShadowMapRegionLoc {*this, "shadowMapRegion"}
{
}
diff --git a/gfx/gl/sceneShader.cpp b/gfx/gl/sceneShader.cpp
index bcd0590..00d9826 100644
--- a/gfx/gl/sceneShader.cpp
+++ b/gfx/gl/sceneShader.cpp
@@ -8,6 +8,7 @@
#include <gfx/gl/shaders/gs-pointLight.h>
#include <gfx/gl/shaders/gs-spotLight.h>
#include <gfx/gl/shaders/vs-dynamicPoint.h>
+#include <gfx/gl/shaders/vs-dynamicPointInst.h>
#include <gfx/gl/shaders/vs-fixedPoint.h>
#include <gfx/gl/shaders/vs-pointLight.h>
#include <gfx/gl/shaders/vs-spotLight.h>
@@ -18,13 +19,17 @@
#include <location.hpp>
#include <maths.h>
-SceneShader::SceneShader() : landmass {fixedPoint_vs, landmass_fs}, absolute {fixedPoint_vs, material_fs} { }
+SceneShader::SceneShader() :
+ basicInst {dynamicPointInst_vs, material_fs}, landmass {fixedPoint_vs, landmass_fs},
+ absolute {fixedPoint_vs, material_fs}
+{
+}
void
SceneShader::setViewProjection(const glm::mat4 & viewProjection) const
{
- for (const auto & prog :
- std::array<const SceneProgram *, 6> {&basic, &water, &landmass, &absolute, &pointLight, &spotLight}) {
+ for (const auto & prog : std::array<const SceneProgram *, 7> {
+ &basic, &basicInst, &water, &landmass, &absolute, &pointLight, &spotLight}) {
prog->setViewProjection(viewProjection);
}
}
@@ -32,8 +37,8 @@ SceneShader::setViewProjection(const glm::mat4 & viewProjection) const
void
SceneShader::setViewPort(const glm::ivec4 & viewPort) const
{
- for (const auto & prog :
- std::array<const SceneProgram *, 6> {&basic, &water, &landmass, &absolute, &pointLight, &spotLight}) {
+ for (const auto & prog : std::array<const SceneProgram *, 7> {
+ &basic, &basicInst, &water, &landmass, &absolute, &pointLight, &spotLight}) {
prog->setViewPort(viewPort);
}
}
@@ -83,7 +88,7 @@ SceneShader::WaterProgram::use(float waveCycle) const
SceneShader::PointLightShader::PointLightShader() :
SceneProgram {pointLight_vs, pointLight_gs, pointLight_fs}, colourLoc {*this, "colour"}, kqLoc {*this, "kq"}
{
- VertexArrayObject<glm::vec3>::configure(va, b);
+ VertexArrayObject {va}.addAttribs<glm::vec3>(b);
}
void
@@ -94,16 +99,16 @@ SceneShader::PointLightShader::add(const glm::vec3 & position, const glm::vec3 &
glBindBuffer(GL_ARRAY_BUFFER, b);
glUniform3fv(colourLoc, 1, glm::value_ptr(colour));
glUniform1f(kqLoc, kq);
- glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(glm::vec3), glm::value_ptr(position));
+ glBufferData(GL_ARRAY_BUFFER, sizeof(glm::vec3), glm::value_ptr(position), GL_DYNAMIC_DRAW);
glDrawArrays(GL_POINTS, 0, 1);
}
SceneShader::SpotLightShader::SpotLightShader() :
- SceneProgram {spotLight_vs, spotLight_gs, spotLight_fs}, colourLoc {*this, "colour"}, kqLoc {*this, "kq"},
- arcLoc {*this, "arc"}
+ SceneProgram {spotLight_vs, spotLight_gs, spotLight_fs}, directionLoc {*this, "v_direction"},
+ colourLoc {*this, "colour"}, kqLoc {*this, "kq"}, arcLoc {*this, "arc"}
{
using v3pair = std::pair<glm::vec3, glm::vec3>;
- VertexArrayObject<v3pair>::configure<&v3pair::first, &v3pair::second>(va, b);
+ VertexArrayObject {va}.addAttribs<v3pair, &v3pair::first, &v3pair::second>(b);
}
void
@@ -114,9 +119,9 @@ SceneShader::SpotLightShader::add(const glm::vec3 & position, const glm::vec3 &
glBindVertexArray(va);
glBindBuffer(GL_ARRAY_BUFFER, b);
glUniform3fv(colourLoc, 1, glm::value_ptr(colour));
+ glUniform3fv(directionLoc, 1, glm::value_ptr(direction));
glUniform1f(kqLoc, kq);
glUniform1f(arcLoc, arc);
- glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(glm::vec3), glm::value_ptr(position));
- glBufferSubData(GL_ARRAY_BUFFER, sizeof(glm::vec3), sizeof(glm::vec3), glm::value_ptr(direction));
+ glBufferData(GL_ARRAY_BUFFER, sizeof(glm::vec3), glm::value_ptr(position), GL_DYNAMIC_DRAW);
glDrawArrays(GL_POINTS, 0, 1);
}
diff --git a/gfx/gl/sceneShader.h b/gfx/gl/sceneShader.h
index ed1bb79..ead184e 100644
--- a/gfx/gl/sceneShader.h
+++ b/gfx/gl/sceneShader.h
@@ -69,6 +69,7 @@ class SceneShader {
const float arc) const;
private:
+ UniformLocation directionLoc;
UniformLocation colourLoc;
UniformLocation kqLoc;
UniformLocation arcLoc;
@@ -81,7 +82,7 @@ public:
BasicProgram basic;
WaterProgram water;
- AbsolutePosProgram landmass, absolute;
+ AbsolutePosProgram basicInst, landmass, absolute;
PointLightShader pointLight;
SpotLightShader spotLight;
diff --git a/gfx/gl/shaders/dynamicPointInst.vs b/gfx/gl/shaders/dynamicPointInst.vs
new file mode 100644
index 0000000..1c66979
--- /dev/null
+++ b/gfx/gl/shaders/dynamicPointInst.vs
@@ -0,0 +1,21 @@
+#version 330 core
+
+include(`meshIn.glsl')
+layout(location = 5) in mat4 model;
+include(`materialInterface.glsl')
+
+uniform mat4 viewProjection;
+
+void
+main()
+{
+ vec4 worldPos = model * vec4(position, 1.0);
+
+ FragPos = worldPos.xyz;
+ TexCoords = texCoord;
+ Normal = (model * vec4(normal, 0.0)).xyz;
+ Colour = colour;
+ Material = material;
+
+ gl_Position = viewProjection * worldPos;
+}
diff --git a/gfx/gl/shaders/spotLight.vs b/gfx/gl/shaders/spotLight.vs
index e648553..dca0854 100644
--- a/gfx/gl/shaders/spotLight.vs
+++ b/gfx/gl/shaders/spotLight.vs
@@ -1,8 +1,8 @@
#version 330 core
layout(location = 0) in vec3 v_position;
-layout(location = 1) in vec3 v_direction;
+uniform vec3 v_direction;
uniform vec3 colour;
uniform float kq;
uniform float arc;
diff --git a/gfx/gl/vertexArrayObject.hpp b/gfx/gl/vertexArrayObject.hpp
index 5b9fc60..7ded03e 100644
--- a/gfx/gl/vertexArrayObject.hpp
+++ b/gfx/gl/vertexArrayObject.hpp
@@ -2,50 +2,65 @@
#include "collections.hpp"
#include "gl_traits.hpp"
+#include "special_members.hpp"
#include <GL/glew.h>
-#include <glm/common.hpp>
-template<typename Vertex> class VertexArrayObject {
+class VertexArrayObject {
public:
- template<auto Vertex::*... attribs>
- static void
- configure(const GLuint arrayObject, const GLuint arrayBuffer, const GLuint indexBuffer,
- const SequentialCollection<Vertex> auto & vertices, const SequentialCollection<unsigned int> auto & indices)
+ template<typename T> [[nodiscard]] VertexArrayObject(const T & arrayObject)
{
glBindVertexArray(arrayObject);
-
- configure_attribs<attribs...>(arrayBuffer);
- data(vertices, arrayBuffer, GL_ARRAY_BUFFER);
- data(indices, indexBuffer, GL_ELEMENT_ARRAY_BUFFER);
-
+ }
+ ~VertexArrayObject()
+ {
glBindVertexArray(0);
}
+ NO_MOVE(VertexArrayObject);
+ NO_COPY(VertexArrayObject);
- template<auto Vertex::*... attribs>
- static void
- configure(const GLuint arrayObject, const GLuint arrayBuffer, const SequentialCollection<Vertex> auto & vertices)
- {
- glBindVertexArray(arrayObject);
+ template<typename m, typename T> struct MP {
+ constexpr MP(m T::*p) : P {p} { }
+ operator void *() const
+ {
+ return &(static_cast<T *>(nullptr)->*P);
+ }
+ m T::*P;
+ using value_type = m;
+ };
+ template<typename m, typename T> MP(m T::*) -> MP<m, T>;
- configure_attribs<attribs...>(arrayBuffer);
+ template<typename VertexT, MP... attribs>
+ VertexArrayObject &
+ addAttribs(const GLuint arrayBuffer, const SequentialCollection<VertexT> auto & vertices, const GLuint divisor = 0)
+ {
+ addAttribs<VertexT, attribs...>(arrayBuffer, divisor);
data(vertices, arrayBuffer, GL_ARRAY_BUFFER);
-
- glBindVertexArray(0);
+ return *this;
}
- template<auto Vertex::*... attribs>
- static void
- configure(const GLuint arrayObject, const GLuint arrayBuffer)
+ template<typename VertexT, MP... attribs>
+ VertexArrayObject &
+ addAttribs(const GLuint arrayBuffer, const GLuint divisor = 0)
{
- glBindVertexArray(arrayObject);
+ configure_attribs<VertexT, attribs...>(arrayBuffer, divisor);
+ return *this;
+ }
- configure_attribs<attribs...>(arrayBuffer);
- glBufferData(GL_ARRAY_BUFFER, static_cast<GLsizeiptr>(sizeof(Vertex)), nullptr, GL_DYNAMIC_DRAW);
+ template<typename Indices>
+ VertexArrayObject &
+ addIndices(const GLuint arrayBuffer, const Indices & indices)
+ {
+ data(indices, arrayBuffer, GL_ELEMENT_ARRAY_BUFFER);
+ return *this;
+ }
- glBindVertexArray(0);
+ VertexArrayObject &
+ addIndices(const GLuint arrayBuffer)
+ {
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, arrayBuffer);
+ return *this;
}
-private:
template<typename Data>
static void
data(const Data & data, const GLuint arrayBuffer, GLenum target)
@@ -55,34 +70,40 @@ private:
glBufferData(target, static_cast<GLsizeiptr>(sizeof(Value) * data.size()), data.data(), GL_STATIC_DRAW);
}
- template<typename T>
- static void
- set_pointer(const GLuint vertexArrayId, const void * ptr)
+private:
+ template<typename VertexT, typename T>
+ static auto
+ set_pointer(const GLuint vertexArrayId, const void * ptr, const GLuint divisor)
{
- glEnableVertexAttribArray(vertexArrayId);
using traits = gl_traits<T>;
- traits::vertexAttribFunc(vertexArrayId, traits::size, traits::type, sizeof(Vertex), ptr);
+ const auto usedAttribs
+ = traits::vertexAttribFunc(vertexArrayId, traits::size, traits::type, sizeof(VertexT), ptr);
+ for (GLuint i {}; i < usedAttribs; i++) {
+ glEnableVertexAttribArray(vertexArrayId + i);
+ glVertexAttribDivisor(vertexArrayId + i, divisor);
+ }
+ return usedAttribs;
}
- template<auto Vertex::*attrib>
- static void
- set_pointer(const GLuint vertexArrayId)
+ template<typename VertexT, MP attrib>
+ static auto
+ set_pointer(const GLuint vertexArrayId, const GLuint divisor)
{
- set_pointer<std::decay_t<decltype(std::declval<Vertex>().*attrib)>>(
- vertexArrayId, &(static_cast<const Vertex *>(nullptr)->*attrib));
+ return set_pointer<VertexT, typename decltype(attrib)::value_type>(vertexArrayId, attrib, divisor);
}
- template<auto Vertex::*... attribs>
- static void
- configure_attribs(const GLuint arrayBuffer)
+ template<typename VertexT, MP... attribs>
+ void
+ configure_attribs(const GLuint arrayBuffer, const GLuint divisor)
{
glBindBuffer(GL_ARRAY_BUFFER, arrayBuffer);
if constexpr (sizeof...(attribs) == 0) {
- set_pointer<Vertex>(0, nullptr);
+ vertexArrayId += set_pointer<VertexT, VertexT>(vertexArrayId, nullptr, divisor);
}
else {
- GLuint vertexArrayId {};
- (set_pointer<attribs>(vertexArrayId++), ...);
+ ((vertexArrayId += set_pointer<VertexT, attribs>(vertexArrayId, divisor)), ...);
}
}
+
+ GLuint vertexArrayId {};
};
diff --git a/gfx/models/mesh.cpp b/gfx/models/mesh.cpp
index 2719211..55759cb 100644
--- a/gfx/models/mesh.cpp
+++ b/gfx/models/mesh.cpp
@@ -7,9 +7,30 @@
Mesh::Mesh(const std::span<const Vertex> vertices, const std::span<const unsigned int> indices, GLenum m) :
m_numIndices {static_cast<GLsizei>(indices.size())}, mode {m}
{
- VertexArrayObject<Vertex>::configure<&Vertex::pos, &Vertex::texCoord, &Vertex::normal, &Vertex::colour,
- &Vertex::material>(
- m_vertexArrayObject, m_vertexArrayBuffers[0], m_vertexArrayBuffers[1], vertices, indices);
+ VertexArrayObject::data(vertices, m_vertexArrayBuffers[0], GL_ARRAY_BUFFER);
+ VertexArrayObject::data(indices, m_vertexArrayBuffers[1], GL_ARRAY_BUFFER);
+ configureVAO(m_vertexArrayObject);
+}
+
+VertexArrayObject &
+Mesh::configureVAO(VertexArrayObject && vao) const
+{
+ return vao
+ .addAttribs<Vertex, &Vertex::pos, &Vertex::texCoord, &Vertex::normal, &Vertex::colour, &Vertex::material>(
+ m_vertexArrayBuffers[0])
+ .addIndices(m_vertexArrayBuffers[1]);
+}
+
+GLsizei
+Mesh::count() const
+{
+ return m_numIndices;
+}
+
+GLenum
+Mesh::type() const
+{
+ return mode;
}
void
diff --git a/gfx/models/mesh.h b/gfx/models/mesh.h
index 25a9064..472b7ed 100644
--- a/gfx/models/mesh.h
+++ b/gfx/models/mesh.h
@@ -7,12 +7,16 @@
#include <stdTypeDefs.hpp>
class Vertex;
+class VertexArrayObject;
class Mesh : public ConstTypeDefs<Mesh> {
public:
Mesh(const std::span<const Vertex> vertices, const std::span<const unsigned int> indices, GLenum = GL_TRIANGLES);
void Draw() const;
+ VertexArrayObject & configureVAO(VertexArrayObject &&) const;
+ GLsizei count() const;
+ GLenum type() const;
private:
glVertexArray m_vertexArrayObject;
diff --git a/lib/gl_traits.hpp b/lib/gl_traits.hpp
index e2e689d..6ff8905 100644
--- a/lib/gl_traits.hpp
+++ b/lib/gl_traits.hpp
@@ -11,20 +11,23 @@ struct gl_traits_base {
};
struct gl_traits_float : public gl_traits_base {
static constexpr auto vertexAttribFunc {
- [](GLuint index, GLint size, GLenum type, GLsizei stride, const void * pointer) {
+ [](GLuint index, GLint size, GLenum type, GLsizei stride, const void * pointer) -> GLuint {
glVertexAttribPointer(index, size, type, GL_FALSE, stride, pointer);
+ return 1;
}};
};
struct gl_traits_longfloat : public gl_traits_base {
static constexpr auto vertexAttribFunc {
- [](GLuint index, GLint size, GLenum type, GLsizei stride, const void * pointer) {
+ [](GLuint index, GLint size, GLenum type, GLsizei stride, const void * pointer) -> GLuint {
glVertexAttribLPointer(index, size, type, stride, pointer);
+ return 1;
}};
};
struct gl_traits_integer : public gl_traits_base {
static constexpr auto vertexAttribFunc {
- [](GLuint index, GLint size, GLenum type, GLsizei stride, const void * pointer) {
+ [](GLuint index, GLint size, GLenum type, GLsizei stride, const void * pointer) -> GLuint {
glVertexAttribIPointer(index, size, type, stride, pointer);
+ return 1;
}};
};
template<> struct gl_traits<glm::f32> : public gl_traits_float {
@@ -63,4 +66,12 @@ template<glm::length_t L, typename T, glm::qualifier Q> struct gl_traits<glm::ve
template<glm::length_t C, glm::length_t R, typename T, glm::qualifier Q>
struct gl_traits<glm::mat<C, R, T, Q>> : public gl_traits<T> {
static constexpr GLint size {C * R};
+ static constexpr auto vertexAttribFunc {
+ [](GLuint index, GLint, GLenum type, GLsizei stride, const void * pointer) -> GLuint {
+ const auto base = static_cast<const T *>(pointer);
+ for (GLuint r = 0; r < R; r++) {
+ glVertexAttribPointer(index + r, C, type, GL_FALSE, stride, base + (r * C));
+ }
+ return R;
+ }};
};
diff --git a/test/Jamfile.jam b/test/Jamfile.jam
index b0eed5e..1ce73b7 100644
--- a/test/Jamfile.jam
+++ b/test/Jamfile.jam
@@ -53,10 +53,11 @@ run test-text.cpp ;
run test-enumDetails.cpp ;
run test-render.cpp : -- : test-assetFactory : <library>test ;
run test-glContextBhvr.cpp ;
-run test-assetFactory.cpp : -- : [ sequence.insertion-sort [ glob-tree $(res) : *.* ] fixtures/rgb.txt ] : <library>test ;
+run test-assetFactory.cpp : -- : [ sequence.insertion-sort [ glob-tree $(res) : *.* ] fixtures/rgb.txt test-instancing ] : <library>test ;
run perf-assetFactory.cpp : -- : test-assetFactory : <library>benchmark <library>test ;
run perf-persistence.cpp : -- : test-persistence : <library>benchmark <library>test ;
run test-worker.cpp ;
+run test-instancing.cpp : : : <library>test ;
compile test-static-enumDetails.cpp ;
compile test-static-stream_support.cpp ;
explicit perf-assetFactory ;
diff --git a/test/test-assetFactory.cpp b/test/test-assetFactory.cpp
index 3d79213..82a1825 100644
--- a/test/test-assetFactory.cpp
+++ b/test/test-assetFactory.cpp
@@ -29,7 +29,6 @@ public:
FactoryFixture() : sceneRenderer {size, output} { }
~FactoryFixture()
{
- glDisable(GL_DEBUG_OUTPUT);
auto outpath = (TMP / boost::unit_test::framework::current_test_case().full_name()).replace_extension(".tga");
std::filesystem::create_directories(outpath.parent_path());
Texture::save(outImage, outpath.c_str());
@@ -108,8 +107,11 @@ BOOST_AUTO_TEST_CASE(foliage, *boost::unit_test::timeout(5))
auto tree_01_1_f = std::dynamic_pointer_cast<Foliage>(tree_01_1);
BOOST_REQUIRE(tree_01_1_f);
- auto plant = std::make_shared<Plant>(tree_01_1_f, Location {{-2, 2, 0}, {}});
- objects.objects.push_back(plant);
+ auto plant1 = std::make_shared<Plant>(tree_01_1_f, Location {{-2, 2, 0}, {0, 0, 0}});
+ auto plant2 = std::make_shared<Plant>(tree_01_1_f, Location {{3, -4, 0}, {0, 1, 0}});
+ auto plant3 = std::make_shared<Plant>(tree_01_1_f, Location {{-2, -4, 0}, {0, 2, 0}});
+ auto plant4 = std::make_shared<Plant>(tree_01_1_f, Location {{3, 2, 0}, {0, 3, 0}});
+ objects.objects.push_back(tree_01_1_f);
render(5);
}
diff --git a/test/test-instancing.cpp b/test/test-instancing.cpp
new file mode 100644
index 0000000..c743ce0
--- /dev/null
+++ b/test/test-instancing.cpp
@@ -0,0 +1,181 @@
+#define BOOST_TEST_MODULE instancing
+
+#include "stream_support.hpp"
+#include "testHelpers.h"
+#include "testMainWindow.h"
+#include "ui/applicationBase.h"
+#include <boost/test/data/test_case.hpp>
+#include <boost/test/unit_test.hpp>
+#include <set>
+
+#include <gfx/gl/instanceVertices.h>
+
+BOOST_GLOBAL_FIXTURE(ApplicationBase);
+BOOST_GLOBAL_FIXTURE(TestMainWindow);
+
+BOOST_FIXTURE_TEST_SUITE(i, InstanceVertices<int>)
+
+BOOST_AUTO_TEST_CASE(createDestroy)
+{
+ BOOST_CHECK(!data);
+ map();
+ BOOST_REQUIRE(data);
+ BOOST_CHECK_EQUAL(0, next);
+ BOOST_CHECK(unused.empty());
+ BOOST_CHECK(index.empty());
+ unmap();
+ BOOST_CHECK(!data);
+}
+
+BOOST_AUTO_TEST_CASE(acquireRelease)
+{
+ {
+ auto proxy = acquire();
+ *proxy = 20;
+ BOOST_CHECK_EQUAL(1, next);
+ BOOST_REQUIRE_EQUAL(1, index.size());
+ BOOST_CHECK_EQUAL(0, index.front());
+ BOOST_CHECK(unused.empty());
+ }
+ BOOST_CHECK_EQUAL(0, next);
+ BOOST_CHECK(unused.empty());
+ BOOST_CHECK(index.empty());
+}
+
+BOOST_AUTO_TEST_CASE(acquireReleaseMove)
+{
+ {
+ auto proxy1 = acquire();
+ *proxy1 = 20;
+ BOOST_CHECK_EQUAL(1, next);
+ auto proxy2 = std::move(proxy1);
+ proxy2 = 40;
+ BOOST_CHECK_EQUAL(1, next);
+ BOOST_CHECK_EQUAL(data[0], 40);
+ }
+ BOOST_CHECK_EQUAL(0, next);
+ BOOST_CHECK(unused.empty());
+ BOOST_CHECK(index.empty());
+}
+
+BOOST_AUTO_TEST_CASE(autoMapUnmap)
+{
+ {
+ auto proxy = acquire();
+ BOOST_CHECK(data);
+ std::ignore = bufferName();
+ BOOST_CHECK(data);
+ BOOST_CHECK_EQUAL(1, count());
+ BOOST_CHECK(!data);
+ }
+ BOOST_CHECK_EQUAL(0, count());
+}
+
+BOOST_AUTO_TEST_CASE(initialize)
+{
+ auto proxy = acquire(5);
+ const auto & constProxy = proxy;
+ BOOST_CHECK_EQUAL(*proxy, 5);
+ BOOST_CHECK_EQUAL(*constProxy, 5);
+ BOOST_CHECK_EQUAL(constProxy.get(), constProxy.get());
+}
+
+BOOST_AUTO_TEST_CASE(resize)
+{
+ constexpr auto COUNT = 500;
+ std::vector<decltype(acquire())> proxies;
+ std::vector<int> expected;
+ for (auto n = 0; n < COUNT; n++) {
+ proxies.push_back(acquire(n));
+ expected.emplace_back(n);
+ }
+ BOOST_CHECK_EQUAL_COLLECTIONS(expected.begin(), expected.end(), data, data + COUNT);
+ BOOST_CHECK_EQUAL_COLLECTIONS(expected.begin(), expected.end(), proxies.begin(), proxies.end());
+}
+
+BOOST_AUTO_TEST_CASE(shuffle)
+{
+ std::vector<decltype(acquire())> proxies;
+ BOOST_CHECK_EQUAL(0, proxies.emplace_back(acquire(0)));
+ BOOST_CHECK_EQUAL(1, proxies.emplace_back(acquire(1)));
+ BOOST_CHECK_EQUAL(2, proxies.emplace_back(acquire(2)));
+ BOOST_CHECK_EQUAL(3, proxies.emplace_back(acquire(3)));
+ BOOST_CHECK_EQUAL(4, next);
+ BOOST_CHECK_EQUAL(data + 0, proxies[0].get());
+ BOOST_CHECK_EQUAL(data + 1, proxies[1].get());
+ BOOST_CHECK_EQUAL(data + 2, proxies[2].get());
+ BOOST_CHECK_EQUAL(data + 3, proxies[3].get());
+ BOOST_CHECK(unused.empty());
+ BOOST_REQUIRE_EQUAL(4, index.size());
+ BOOST_CHECK_EQUAL(0, index[0]);
+ BOOST_CHECK_EQUAL(1, index[1]);
+ BOOST_CHECK_EQUAL(2, index[2]);
+ BOOST_CHECK_EQUAL(3, index[3]);
+ // Remove 1, 3 moves to [1]
+ proxies.erase(proxies.begin() + 1);
+ BOOST_REQUIRE_EQUAL(4, index.size());
+ BOOST_REQUIRE_EQUAL(1, unused.size());
+ BOOST_CHECK_EQUAL(1, unused[0]);
+ BOOST_CHECK_EQUAL(data + 0, proxies[0].get());
+ BOOST_CHECK_EQUAL(data + 2, proxies[1].get());
+ BOOST_CHECK_EQUAL(data + 1, proxies[2].get());
+ // Remove 1, 2 moves to [1]
+ proxies.erase(proxies.begin() + 1);
+ BOOST_REQUIRE_EQUAL(4, index.size());
+ BOOST_REQUIRE_EQUAL(2, unused.size());
+ BOOST_CHECK_EQUAL(1, unused[0]);
+ BOOST_CHECK_EQUAL(2, unused[1]);
+ BOOST_CHECK_EQUAL(data + 0, proxies[0].get());
+ BOOST_CHECK_EQUAL(data + 1, proxies[1].get());
+ // Add new, takes 2 at [2]
+ BOOST_CHECK_EQUAL(4, proxies.emplace_back(acquire(4)));
+ BOOST_REQUIRE_EQUAL(4, index.size());
+ BOOST_REQUIRE_EQUAL(1, unused.size());
+ BOOST_CHECK_EQUAL(1, unused[0]);
+ BOOST_CHECK_EQUAL(data + 0, proxies[0].get());
+ BOOST_CHECK_EQUAL(data + 1, proxies[1].get());
+ BOOST_CHECK_EQUAL(data + 2, proxies[2].get());
+}
+
+BOOST_DATA_TEST_CASE(shuffle_random, boost::unit_test::data::xrange(0, 10), x)
+{
+ std::ignore = x;
+ std::mt19937 gen(std::random_device {}());
+ std::map<int, InstanceVertices<int>::InstanceProxy> proxies;
+ const std::string_view actions = "aaaaaaaarararrraarrrararararaarrrarararararararararraarrrraaaarararaararar";
+ int n {};
+ for (const auto action : actions) {
+ switch (action) {
+ case 'a':
+ BOOST_REQUIRE_EQUAL(n, proxies.emplace(n, acquire(n)).first->second);
+ n++;
+ break;
+ case 'r':
+ BOOST_REQUIRE(!proxies.empty());
+ auto e = std::next(proxies.begin(),
+ std::uniform_int_distribution<> {0, static_cast<int>(proxies.size() - 1)}(gen));
+ proxies.erase(e);
+ break;
+ }
+
+ BOOST_REQUIRE_EQUAL(next, proxies.size());
+ for (const auto & [n, p] : proxies) {
+ BOOST_REQUIRE_EQUAL(n, p);
+ }
+ std::set<size_t> iused;
+ for (size_t i {}; i < index.size(); i++) {
+ if (std::find(unused.begin(), unused.end(), i) == unused.end()) {
+ iused.emplace(index[i]);
+ }
+ }
+ BOOST_TEST_CONTEXT(index) {
+ BOOST_REQUIRE_EQUAL(iused.size(), next);
+ if (!iused.empty()) {
+ BOOST_REQUIRE_EQUAL(*iused.begin(), 0);
+ BOOST_REQUIRE_EQUAL(*iused.rbegin(), next - 1);
+ }
+ }
+ }
+}
+
+BOOST_AUTO_TEST_SUITE_END()
diff --git a/test/test-render.cpp b/test/test-render.cpp
index 45acab5..1643068 100644
--- a/test/test-render.cpp
+++ b/test/test-render.cpp
@@ -86,7 +86,6 @@ BOOST_AUTO_TEST_CASE(basic)
ss.camera.setView({-10, -10, 60}, glm::normalize(glm::vec3 {1, 1, -0.5F}));
const TestScene scene;
ss.render(scene);
- glDisable(GL_DEBUG_OUTPUT);
Texture::save(outImage, "/tmp/basic.tga");
}
@@ -114,7 +113,6 @@ BOOST_AUTO_TEST_CASE(pointlight)
};
const PointLightScene scene;
ss.render(scene);
- glDisable(GL_DEBUG_OUTPUT);
Texture::save(outImage, "/tmp/pointlight.tga");
}
@@ -141,7 +139,6 @@ BOOST_AUTO_TEST_CASE(spotlight)
};
const PointLightScene scene;
ss.render(scene);
- glDisable(GL_DEBUG_OUTPUT);
Texture::save(outImage, "/tmp/spotlight.tga");
}
diff --git a/test/testMainWindow.cpp b/test/testMainWindow.cpp
index 49e18f1..57e3473 100644
--- a/test/testMainWindow.cpp
+++ b/test/testMainWindow.cpp
@@ -12,11 +12,12 @@ TestMainWindow::TestMainWindow() : Window {1, 1, __FILE__, SDL_WINDOW_OPENGL | S
(type == GL_DEBUG_TYPE_ERROR ? "** GL ERROR **" : ""), type, severity, message);
switch (type) {
case GL_DEBUG_TYPE_ERROR:
- case GL_DEBUG_TYPE_PERFORMANCE:
case GL_DEBUG_TYPE_PORTABILITY:
case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR:
case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR:
BOOST_TEST_ERROR(msg.get());
+ case GL_DEBUG_TYPE_PERFORMANCE:
+ BOOST_TEST_WARN(msg.get());
default:
BOOST_TEST_MESSAGE(msg.get());
}
diff --git a/ui/gameMainWindow.cpp b/ui/gameMainWindow.cpp
index 0b30cad..3f85a4f 100644
--- a/ui/gameMainWindow.cpp
+++ b/ui/gameMainWindow.cpp
@@ -51,6 +51,11 @@ GameMainWindow::render() const
void
GameMainWindow::content(const SceneShader & shader) const
{
+ for (const auto & [id, asset] : gameState->assets) {
+ if (const auto r = std::dynamic_pointer_cast<const Renderable>(asset)) {
+ r->render(shader);
+ }
+ }
gameState->world.apply<Renderable>(&Renderable::render, shader);
uiComponents.apply<WorldOverlay>(&WorldOverlay::render, shader);
}
@@ -68,5 +73,10 @@ GameMainWindow::lights(const SceneShader & shader) const
void
GameMainWindow::shadows(const ShadowMapper & shadowMapper) const
{
+ for (const auto & [id, asset] : gameState->assets) {
+ if (const auto r = std::dynamic_pointer_cast<const Renderable>(asset)) {
+ r->shadows(shadowMapper);
+ }
+ }
gameState->world.apply<Renderable>(&Renderable::shadows, shadowMapper);
}