Render the world in its own framebuffer

This commit is contained in:
Kae 2023-07-06 23:59:18 +10:00
parent fe4cc19618
commit fe99ec6966
15 changed files with 203 additions and 68 deletions

View File

@ -1,9 +0,0 @@
{
"effectParameters" : {},
"effectTextures" : {},
"effectShaders" : {
"vertex" : "default.vert",
"fragment" : "default.frag"
}
}

View File

@ -0,0 +1,11 @@
{
"blitFrameBuffer" : "world",
"effectParameters" : {},
"effectTextures" : {},
"effectShaders" : {
"vertex" : "interface.vert",
"fragment" : "interface.frag"
}
}

View File

@ -1,4 +1,6 @@
{
"frameBuffer" : "world",
"effectParameters" : {
"lightMapEnabled" : {
"type" : "bool",

View File

@ -0,0 +1,5 @@
{
"frameBuffers" : {
"world" : {}
}
}

View File

@ -130,6 +130,8 @@ public:
virtual String rendererId() const = 0;
virtual Vec2U screenSize() const = 0;
virtual void loadConfig(Json const& config) = 0;
// The actual shaders used by this renderer will be in a default no effects
// state when constructed, but can be overridden here. This config will be
// specific to each type of renderer, so it will be necessary to key the

View File

@ -105,6 +105,7 @@ OpenGl20Renderer::~OpenGl20Renderer() {
for (auto& effect : m_effects)
glDeleteProgram(effect.second.program);
m_frameBuffers.clear();
logGlErrorSummary("OpenGL errors during shutdown");
}
@ -116,6 +117,38 @@ Vec2U OpenGl20Renderer::screenSize() const {
return m_screenSize;
}
OpenGl20Renderer::GlFrameBuffer::GlFrameBuffer(Json const& fbConfig) : config(fbConfig) {
texture = createGlTexture(Image(), TextureAddressing::Clamp, TextureFiltering::Nearest);
glBindTexture(GL_TEXTURE_2D, texture->glTextureId());
Vec2U size = jsonToVec2U(config.getArray("size", { 256, 256 }));
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, size[0] , size[1], 0, GL_RGB, GL_UNSIGNED_BYTE, NULL);
glGenFramebuffers(1, &id);
if (!id)
throw RendererException("Failed to create OpenGL framebuffer");
glBindFramebuffer(GL_FRAMEBUFFER, id);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture->glTextureId(), 0);
auto framebufferStatus = glCheckFramebufferStatus(GL_FRAMEBUFFER);
if (framebufferStatus != GL_FRAMEBUFFER_COMPLETE)
throw RendererException("OpenGL framebuffer is not complete!");
}
OpenGl20Renderer::GlFrameBuffer::~GlFrameBuffer() {
glDeleteFramebuffers(1, &id);
texture.reset();
}
void OpenGl20Renderer::loadConfig(Json const& config) {
m_frameBuffers.clear();
for (auto& pair : config.getObject("frameBuffers", {}))
m_frameBuffers[pair.first] = make_ref<GlFrameBuffer>(pair.second);
}
void OpenGl20Renderer::loadEffectConfig(String const& name, Json const& effectConfig, StringMap<String> const& shaders) {
if (m_effects.contains(name)) {
Logger::warn("OpenGL effect {} already exists", name);
@ -303,6 +336,18 @@ bool OpenGl20Renderer::switchEffectConfig(String const& name) {
return false;
Effect& effect = find->second;
if (m_currentEffect == &effect)
return true;
if (auto blitFrameBufferId = effect.config.optString("blitFrameBuffer"))
blitGlFrameBuffer(getGlFrameBuffer(*blitFrameBufferId));
if (auto frameBufferId = effect.config.optString("frameBuffer"))
switchGlFrameBuffer(getGlFrameBuffer(*frameBufferId));
else {
m_currentFrameBuffer.reset();
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
}
glUseProgram(m_program = effect.program);
setupGlUniforms(effect);
@ -387,11 +432,24 @@ void OpenGl20Renderer::setScreenSize(Vec2U screenSize) {
m_screenSize = screenSize;
glViewport(0, 0, m_screenSize[0], m_screenSize[1]);
glUniform2f(m_screenSizeUniform, m_screenSize[0], m_screenSize[1]);
for (auto& frameBuffer : m_frameBuffers) {
glBindTexture(GL_TEXTURE_2D, frameBuffer.second->texture->glTextureId());
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, m_screenSize[0], m_screenSize[1], 0, GL_RGB, GL_UNSIGNED_BYTE, NULL);
}
}
void OpenGl20Renderer::startFrame() {
if (m_scissorRect)
glDisable(GL_SCISSOR_TEST);
for (auto& frameBuffer : m_frameBuffers) {
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, frameBuffer.second->id);
glClear(GL_COLOR_BUFFER_BIT);
frameBuffer.second->blitted = false;
}
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glClear(GL_COLOR_BUFFER_BIT);
@ -417,6 +475,9 @@ void OpenGl20Renderer::finishFrame() {
return false;
});
// Blit if another shader hasn't
glBindFramebuffer(GL_FRAMEBUFFER, 0);
if (DebugEnabled)
logGlErrorSummary("OpenGL errors this frame");
}
@ -456,17 +517,20 @@ void OpenGl20Renderer::GlTextureAtlasSet::copyAtlasPixels(
glBindTexture(GL_TEXTURE_2D, glTexture);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
if (image.pixelFormat() == PixelFormat::RGB24) {
glTexSubImage2D(GL_TEXTURE_2D, 0, bottomLeft[0], bottomLeft[1], image.width(), image.height(), GL_RGB, GL_UNSIGNED_BYTE, image.data());
} else if (image.pixelFormat() == PixelFormat::RGBA32) {
glTexSubImage2D(GL_TEXTURE_2D, 0, bottomLeft[0], bottomLeft[1], image.width(), image.height(), GL_RGBA, GL_UNSIGNED_BYTE, image.data());
} else if (image.pixelFormat() == PixelFormat::BGR24) {
glTexSubImage2D(GL_TEXTURE_2D, 0, bottomLeft[0], bottomLeft[1], image.width(), image.height(), GL_BGR, GL_UNSIGNED_BYTE, image.data());
} else if (image.pixelFormat() == PixelFormat::BGRA32) {
glTexSubImage2D(GL_TEXTURE_2D, 0, bottomLeft[0], bottomLeft[1], image.width(), image.height(), GL_BGRA, GL_UNSIGNED_BYTE, image.data());
} else {
GLenum format;
auto pixelFormat = image.pixelFormat();
if (pixelFormat == PixelFormat::RGB24)
format = GL_RGB;
else if (pixelFormat == PixelFormat::RGBA32)
format = GL_RGBA;
else if (pixelFormat == PixelFormat::BGR24)
format = GL_BGR;
else if (pixelFormat == PixelFormat::BGRA32)
format = GL_BGRA;
else
throw RendererException("Unsupported texture format in OpenGL20Renderer::TextureGroup::copyAtlasPixels");
}
glTexSubImage2D(GL_TEXTURE_2D, 0, bottomLeft[0], bottomLeft[1], image.width(), image.height(), format, GL_UNSIGNED_BYTE, image.data());
}
OpenGl20Renderer::GlTextureGroup::GlTextureGroup(unsigned atlasNumCells)
@ -608,7 +672,7 @@ void OpenGl20Renderer::GlRenderBuffer::set(List<RenderPrimitive>& primitives) {
glBufferData(GL_ARRAY_BUFFER, accumulationBuffer.size(), accumulationBuffer.ptr(), GL_STREAM_DRAW);
}
vertexBuffers.append(vb);
vertexBuffers.emplace_back(move(vb));
currentTextures.clear();
currentTextureSizes.clear();
@ -654,9 +718,10 @@ void OpenGl20Renderer::GlRenderBuffer::set(List<RenderPrimitive>& primitives) {
++currentVertexCount;
};
float textureIndex = 0.0f;
Vec2F textureOffset = {};
Texture* lastTexture = nullptr;
for (auto& primitive : primitives) {
float textureIndex;
Vec2F textureOffset;
if (auto tri = primitive.ptr<RenderTriangle>()) {
tie(textureIndex, textureOffset) = addCurrentTexture(move(tri->texture));
@ -678,6 +743,7 @@ void OpenGl20Renderer::GlRenderBuffer::set(List<RenderPrimitive>& primitives) {
} else if (auto poly = primitive.ptr<RenderPoly>()) {
if (poly->vertexes.size() > 2) {
tie(textureIndex, textureOffset) = addCurrentTexture(move(poly->texture));
for (size_t i = 1; i < poly->vertexes.size() - 1; ++i) {
appendBufferVertex(poly->vertexes[0], textureIndex, textureOffset);
appendBufferVertex(poly->vertexes[i], textureIndex, textureOffset);
@ -723,17 +789,20 @@ bool OpenGl20Renderer::logGlErrorSummary(String prefix) {
void OpenGl20Renderer::uploadTextureImage(PixelFormat pixelFormat, Vec2U size, uint8_t const* data) {
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
if (pixelFormat == PixelFormat::RGB24) {
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, size[0], size[1], 0, GL_RGB, GL_UNSIGNED_BYTE, data);
} else if (pixelFormat == PixelFormat::RGBA32) {
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, size[0], size[1], 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
} else if (pixelFormat == PixelFormat::BGR24) {
glTexImage2D(GL_TEXTURE_2D, 0, GL_BGR, size[0], size[1], 0, GL_BGR, GL_UNSIGNED_BYTE, data);
} else if (pixelFormat == PixelFormat::BGRA32) {
glTexImage2D(GL_TEXTURE_2D, 0, GL_BGRA, size[0], size[1], 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
} else {
starAssert(false);
}
GLenum format;
if (pixelFormat == PixelFormat::RGB24)
format = GL_RGB;
else if (pixelFormat == PixelFormat::RGBA32)
format = GL_RGBA;
else if (pixelFormat == PixelFormat::BGR24)
format = GL_BGR;
else if (pixelFormat == PixelFormat::BGRA32)
format = GL_BGRA;
else
throw RendererException("Unsupported texture format in OpenGL20Renderer::uploadTextureImage");
glTexImage2D(GL_TEXTURE_2D, 0, format, size[0], size[1], 0, format, GL_UNSIGNED_BYTE, data);
}
void OpenGl20Renderer::flushImmediatePrimitives() {
@ -752,9 +821,6 @@ auto OpenGl20Renderer::createGlTexture(Image const& image, TextureAddressing add
glLoneTexture->textureAddressing = addressing;
glLoneTexture->textureSize = image.size();
if (image.empty())
return glLoneTexture;
glGenTextures(1, &glLoneTexture->textureId);
if (glLoneTexture->textureId == 0)
throw RendererException("Could not generate texture in OpenGL20Renderer::createGlTexture");
@ -777,7 +843,9 @@ auto OpenGl20Renderer::createGlTexture(Image const& image, TextureAddressing add
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
}
uploadTextureImage(image.pixelFormat(), image.size(), image.data());
if (!image.empty())
uploadTextureImage(image.pixelFormat(), image.size(), image.data());
return glLoneTexture;
}
@ -852,6 +920,37 @@ void OpenGl20Renderer::setupGlUniforms(Effect& effect) {
glUniform2f(m_screenSizeUniform, m_screenSize[0], m_screenSize[1]);
}
RefPtr<OpenGl20Renderer::GlFrameBuffer> OpenGl20Renderer::getGlFrameBuffer(String const& id) {
if (auto ptr = m_frameBuffers.ptr(id))
return *ptr;
else
throw RendererException::format("Frame buffer '{}' does not exist", id);
}
void OpenGl20Renderer::blitGlFrameBuffer(RefPtr<GlFrameBuffer> const& frameBuffer) {
if (frameBuffer->blitted)
return;
auto& size = m_screenSize;
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
glBindFramebuffer(GL_READ_FRAMEBUFFER, frameBuffer->id);
glBlitFramebuffer(
0, 0, size[0], size[1],
0, 0, size[0], size[1],
GL_COLOR_BUFFER_BIT, GL_NEAREST
);
frameBuffer->blitted = true;
}
void OpenGl20Renderer::switchGlFrameBuffer(RefPtr<GlFrameBuffer> const& frameBuffer) {
if (m_currentFrameBuffer == frameBuffer)
return;
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, frameBuffer->id);
m_currentFrameBuffer = frameBuffer;
}
GLuint OpenGl20Renderer::Effect::getAttribute(String const& name) {
auto find = attributes.find(name);
if (find == attributes.end()) {

View File

@ -10,6 +10,8 @@ namespace Star {
STAR_CLASS(OpenGl20Renderer);
constexpr size_t FrameBufferCount = 1;
// OpenGL 2.0 implementation of Renderer. OpenGL context must be created and
// active during construction, destruction, and all method calls.
class OpenGl20Renderer : public Renderer {
@ -20,6 +22,7 @@ public:
String rendererId() const override;
Vec2U screenSize() const override;
void loadConfig(Json const& config) override;
void loadEffectConfig(String const& name, Json const& effectConfig, StringMap<String> const& shaders) override;
void setEffectParameter(String const& parameterName, RenderEffectParameter const& parameter) override;
@ -145,22 +148,33 @@ private:
struct EffectParameter {
GLint parameterUniform = -1;
VariantTypeIndex parameterType;
VariantTypeIndex parameterType = 0;
Maybe<RenderEffectParameter> parameterValue;
};
struct EffectTexture {
GLint textureUniform = -1;
unsigned textureUnit;
unsigned textureUnit = 0;
TextureAddressing textureAddressing = TextureAddressing::Clamp;
TextureFiltering textureFiltering = TextureFiltering::Linear;
GLint textureSizeUniform = -1;
RefPtr<GlLoneTexture> textureValue;
};
struct GlFrameBuffer : RefCounter {
GLuint id = 0;
RefPtr<GlLoneTexture> texture;
Json config;
bool blitted = false;
GlFrameBuffer(Json const& config);
~GlFrameBuffer();
};
class Effect {
public:
GLuint program;
GLuint program = 0;
Json config;
StringMap<EffectParameter> parameters;
StringMap<EffectTexture> textures;
@ -185,6 +199,10 @@ private:
void setupGlUniforms(Effect& effect);
RefPtr<OpenGl20Renderer::GlFrameBuffer> getGlFrameBuffer(String const& id);
void blitGlFrameBuffer(RefPtr<OpenGl20Renderer::GlFrameBuffer> const& frameBuffer);
void switchGlFrameBuffer(RefPtr<OpenGl20Renderer::GlFrameBuffer> const& frameBuffer);
Vec2U m_screenSize;
GLuint m_program = 0;
@ -203,6 +221,9 @@ private:
StringMap<Effect> m_effects;
Effect* m_currentEffect;
StringMap<RefPtr<GlFrameBuffer>> m_frameBuffers;
RefPtr<GlFrameBuffer> m_currentFrameBuffer;
RefPtr<GlTexture> m_whiteTexture;
Maybe<RectI> m_scissorRect;

View File

@ -211,7 +211,7 @@ void ClientApplication::renderInit(RendererPtr renderer) {
auto assets = m_root->assets();
auto loadEffectConfig = [&](String const& name) {
String path = strf("/rendering/{}.config", name);
String path = strf("/rendering/effects/{}.config", name);
if (assets->assetExists(path)) {
StringMap<String> shaders;
auto config = assets->json(path);
@ -233,6 +233,8 @@ void ClientApplication::renderInit(RendererPtr renderer) {
Logger::warn("No rendering config found for renderer with id '{}'", renderer->rendererId());
};
renderer->loadConfig(assets->json("/rendering/opengl20.config"));
loadEffectConfig("world");
loadEffectConfig("interface");
@ -372,6 +374,9 @@ void ClientApplication::update() {
void ClientApplication::render() {
auto config = m_root->configuration();
auto assets = m_root->assets();
auto& renderer = Application::renderer();
renderer->switchEffectConfig("interface");
if (m_guiContext->windowWidth() >= m_crossoverRes[0] && m_guiContext->windowHeight() >= m_crossoverRes[1])
m_guiContext->setInterfaceScale(m_maxInterfaceScale);
@ -387,28 +392,21 @@ void ClientApplication::render() {
} else if (m_state > MainAppState::Title) {
WorldClientPtr worldClient = m_universeClient->worldClient();
RendererPtr renderer = Application::renderer();
renderer->switchEffectConfig("world");
if (worldClient) {
auto totalStart = Time::monotonicMicroseconds();
auto start = totalStart;
renderer->switchEffectConfig("world");
auto clientStart = totalStart;
worldClient->render(m_renderData, TilePainter::BorderTileSize);
LogMap::set("client_render_world_client", strf(u8"{:05d}\u00b5s", Time::monotonicMicroseconds() - start));
LogMap::set("client_render_world_client", strf(u8"{:05d}\u00b5s", Time::monotonicMicroseconds() - clientStart));
start = Time::monotonicMicroseconds();
auto paintStart = Time::monotonicMicroseconds();
m_worldPainter->render(m_renderData, [&]() { worldClient->waitForLighting(); });
LogMap::set("client_render_world_painter", strf(u8"{:05d}\u00b5s", Time::monotonicMicroseconds() - start));
start = Time::monotonicMicroseconds();
m_mainInterface->renderInWorldElements();
LogMap::set("client_render_world_elements", strf(u8"{:05d}\u00b5s", Time::monotonicMicroseconds() - start));
LogMap::set("client_render_world_painter", strf(u8"{:05d}\u00b5s", Time::monotonicMicroseconds() - paintStart));
LogMap::set("client_render_world_total", strf(u8"{:05d}\u00b5s", Time::monotonicMicroseconds() - totalStart));
}
renderer->switchEffectConfig("interface");
auto start = Time::monotonicMicroseconds();
m_mainInterface->renderInWorldElements();
m_mainInterface->render();
m_cinematicOverlay->render();
LogMap::set("client_render_interface", strf(u8"{:05d}\u00b5s", Time::monotonicMicroseconds() - start));

View File

@ -127,7 +127,6 @@ void JsonStreamer<Json>::toJsonStream(Json const& val, JsonStream& stream, bool
stream.endArray();
} else if (type == Json::Type::Object) {
stream.beginObject();
List<String::Char> chars;
if (sort) {
auto objectPtr = val.objectPtr();
List<JsonObject::const_iterator> iterators;
@ -142,10 +141,8 @@ void JsonStreamer<Json>::toJsonStream(Json const& val, JsonStream& stream, bool
if (!first)
stream.putComma();
first = false;
chars.clear();
for (auto const& c : i->first)
chars.push_back(c);
stream.objectKey(chars.ptr(), chars.size());
auto ws = i->first.wideString();
stream.objectKey(ws.c_str(), ws.length());
stream.putColon();
toJsonStream(i->second, stream, sort);
}

View File

@ -124,25 +124,32 @@ size_t TilePainter::TextureKeyHash::operator()(TextureKey const& key) const {
}
TilePainter::ChunkHash TilePainter::terrainChunkHash(WorldRenderData& renderData, Vec2I chunkIndex) {
XXHash3 hasher;
//XXHash3 hasher;
static ByteArray buffer;
buffer.clear();
RectI tileRange = RectI::withSize(chunkIndex * RenderChunkSize, Vec2I::filled(RenderChunkSize)).padded(MaterialRenderProfileMaxNeighborDistance);
forEachRenderTile(renderData, tileRange, [&](Vec2I const&, RenderTile const& renderTile) {
renderTile.hashPushTerrain(hasher);
});
//renderTile.hashPushTerrain(hasher);
buffer.append((char*)&renderTile, offsetof(RenderTile, liquidId));
});
return hasher.digest();
//return hasher.digest();
return XXH3_64bits(buffer.ptr(), buffer.size());
}
TilePainter::ChunkHash TilePainter::liquidChunkHash(WorldRenderData& renderData, Vec2I chunkIndex) {
XXHash3 hasher;
///XXHash3 hasher;
RectI tileRange = RectI::withSize(chunkIndex * RenderChunkSize, Vec2I::filled(RenderChunkSize)).padded(MaterialRenderProfileMaxNeighborDistance);
static ByteArray buffer;
buffer.clear();
forEachRenderTile(renderData, tileRange, [&](Vec2I const&, RenderTile const& renderTile) {
renderTile.hashPushLiquid(hasher);
});
//renderTile.hashPushLiquid(hasher);
buffer.append((char*)&renderTile.liquidId, sizeof(LiquidId) + sizeof(LiquidLevel));
});
return hasher.digest();
//return hasher.digest();
return XXH3_64bits(buffer.ptr(), buffer.size());
}
TilePainter::QuadZLevel TilePainter::materialZLevel(uint32_t zLevel, MaterialId material, MaterialHue hue, MaterialColorVariant colorVariant) {

View File

@ -69,6 +69,8 @@ void WorldPainter::render(WorldRenderData& renderData, function<void()> lightWai
m_environmentPainter->renderSky(Vec2F(m_camera.screenSize()), renderData.skyRenderData);
m_environmentPainter->renderFrontOrbiters(orbiterAndPlanetRatio, Vec2F(m_camera.screenSize()), renderData.skyRenderData);
m_renderer->flush();
if (lightWaiter) {
auto start = Time::monotonicMicroseconds();
lightWaiter();