diff --git a/shaders/passthrough_frag.glsl b/shaders/passthrough_frag.glsl new file mode 100644 index 000000000..c8ce87bfc --- /dev/null +++ b/shaders/passthrough_frag.glsl @@ -0,0 +1,8 @@ +varying vec2 texCoord; + +uniform sampler2D tex; + +void main(void) +{ + gl_FragColor = texture2D(tex, texCoord); +} diff --git a/shaders/passthrough_vert.glsl b/shaders/passthrough_vert.glsl new file mode 100644 index 000000000..8ff80b37f --- /dev/null +++ b/shaders/passthrough_vert.glsl @@ -0,0 +1,10 @@ +attribute vec2 in_Position; +attribute vec2 in_TexCoord0; + +varying vec2 texCoord; + +void main(void) +{ + gl_Position = vec4(in_Position.xy, 0.0, 1.0); + texCoord = in_TexCoord0.st; +} diff --git a/shaders/warpmesh_frag.glsl b/shaders/warpmesh_frag.glsl new file mode 100644 index 000000000..d73a47675 --- /dev/null +++ b/shaders/warpmesh_frag.glsl @@ -0,0 +1,9 @@ +varying vec2 texCoord; +varying float intensity; + +uniform sampler2D tex; + +void main(void) +{ + gl_FragColor = vec4(texture2D(tex, texCoord).rgb * intensity, 1.0); +} diff --git a/shaders/warpmesh_vert.glsl b/shaders/warpmesh_vert.glsl new file mode 100644 index 000000000..dcc1174cc --- /dev/null +++ b/shaders/warpmesh_vert.glsl @@ -0,0 +1,16 @@ +attribute vec2 in_Position; +attribute vec2 in_TexCoord0; +attribute float in_Intensity; + +varying vec2 texCoord; +varying float intensity; + +uniform float screenRatio; + +void main(void) +{ + float offset = 0.5 - screenRatio * 0.5; + gl_Position = vec4(in_Position.x * screenRatio, in_Position.y, 0.0, 1.0); + texCoord = vec2(in_TexCoord0.x * screenRatio + offset, in_TexCoord0.y); + intensity = in_Intensity; +} diff --git a/src/celengine/CMakeLists.txt b/src/celengine/CMakeLists.txt index abeca84e1..0f4730ace 100644 --- a/src/celengine/CMakeLists.txt +++ b/src/celengine/CMakeLists.txt @@ -69,6 +69,8 @@ set(CELENGINE_SOURCES location.h lodspheremesh.cpp lodspheremesh.h + mapmanager.cpp + mapmanager.h marker.cpp marker.h meshmanager.cpp @@ -168,6 +170,8 @@ set(CELENGINE_SOURCES vecgl.h vertexobject.cpp vertexobject.h + viewporteffect.h + viewporteffect.cpp virtualtex.cpp virtualtex.h visibleregion.cpp diff --git a/src/celengine/framebuffer.cpp b/src/celengine/framebuffer.cpp index c2e1e545d..5a542b978 100644 --- a/src/celengine/framebuffer.cpp +++ b/src/celengine/framebuffer.cpp @@ -138,6 +138,8 @@ FramebufferObject::generateFbo(unsigned int attachments) { // Create the FBO glGenFramebuffers(1, &m_fboId); + GLint oldFboId; + glGetIntegerv(GL_FRAMEBUFFER_BINDING, &oldFboId); glBindFramebuffer(GL_FRAMEBUFFER, m_fboId); #ifndef GL_ES @@ -151,7 +153,7 @@ FramebufferObject::generateFbo(unsigned int attachments) m_status = glCheckFramebufferStatus(GL_FRAMEBUFFER); if (m_status != GL_FRAMEBUFFER_COMPLETE) { - glBindFramebuffer(GL_FRAMEBUFFER, 0); + glBindFramebuffer(GL_FRAMEBUFFER, oldFboId); cleanup(); return; } @@ -171,7 +173,7 @@ FramebufferObject::generateFbo(unsigned int attachments) m_status = glCheckFramebufferStatus(GL_FRAMEBUFFER); if (m_status != GL_FRAMEBUFFER_COMPLETE) { - glBindFramebuffer(GL_FRAMEBUFFER, 0); + glBindFramebuffer(GL_FRAMEBUFFER, oldFboId); cleanup(); return; } @@ -182,7 +184,7 @@ FramebufferObject::generateFbo(unsigned int attachments) } // Restore default frame buffer - glBindFramebuffer(GL_FRAMEBUFFER, 0); + glBindFramebuffer(GL_FRAMEBUFFER, oldFboId); } // Delete all GL objects associated with this framebuffer object @@ -218,9 +220,8 @@ FramebufferObject::bind() } bool -FramebufferObject::unbind() +FramebufferObject::unbind(GLint oldfboId) { - // Restore default frame buffer - glBindFramebuffer(GL_FRAMEBUFFER, 0); + glBindFramebuffer(GL_FRAMEBUFFER, oldfboId); return true; } diff --git a/src/celengine/framebuffer.h b/src/celengine/framebuffer.h index ba0a7b338..704356d5b 100644 --- a/src/celengine/framebuffer.h +++ b/src/celengine/framebuffer.h @@ -44,7 +44,7 @@ class FramebufferObject GLuint depthTexture() const; bool bind(); - bool unbind(); + bool unbind(GLint oldfboId); private: void generateColorTexture(); diff --git a/src/celengine/mapmanager.cpp b/src/celengine/mapmanager.cpp new file mode 100644 index 000000000..c06734d59 --- /dev/null +++ b/src/celengine/mapmanager.cpp @@ -0,0 +1,196 @@ +// +// mapmanager.cpp +// +// Copyright © 2020 Celestia Development Team. All rights reserved. +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. + +#include +#include +#include "mapmanager.h" + +using namespace std; + +WarpMesh::WarpMesh(int nx, int ny, float *data) : + nx(nx), + ny(ny), + data(data) +{ +} + +WarpMesh::~WarpMesh() +{ + delete data; +} + +void WarpMesh::scopedDataForRendering(const function& f) const +{ + int step = 5 * 6; + int size = (nx - 1) * (ny - 1) * step * sizeof(float); + float *renderingData = new float[size]; + for (int y = 0; y < ny - 1; y += 1) + { + for (int x = 0; x < nx - 1; x += 1) + { + float *destination = &renderingData[(y * (nx - 1) + x) * step]; + float *source = &data[(y * nx + x) * 5]; + // Top left triangle + memcpy(destination, source + 5 * nx, sizeof(float) * 5); + memcpy(destination + 5, source, sizeof(float) * 5); + memcpy(destination + 10, source + 5, sizeof(float) * 5); + // Bottom right triangle + memcpy(destination + 15, source + 5 * nx, sizeof(float) * 5); + memcpy(destination + 20, source + 5, sizeof(float) * 5); + memcpy(destination + 25, source + 5 * nx + 5, sizeof(float) * 5); + } + } + f(renderingData, size); + delete[] renderingData; +} + +int WarpMesh::count() const +{ + return 6 * (nx - 1) * (ny - 1); +} + +bool WarpMesh::mapVertex(float x, float y, float* u, float* v) const +{ + float minX = data[0]; + float minY = data[1]; + float maxX = data[(nx * ny - 1) * 5]; + float maxY = data[(nx * ny - 1) * 5 + 1]; + + float stepX = (maxX - minX) / (nx - 1); + float stepY = (maxY - minY) / (ny - 1); + + float locX = (x - minX) / stepX; + float locY = (y - minY) / stepY; + int floX = floorf(locX); + int floY = floorf(locY); + locX -= floX; + locY -= floY; + + if (floX < 0 || floX >= nx - 1 || floY < 0 || floY >= ny - 1) + return false; + + float p1x = data[(floY * nx + floX) * 5 + 2]; + float p1y = data[(floY * nx + floX) * 5 + 3]; + float p2x = data[(floY * nx + floX + 1) * 5 + 2]; + float p2y = data[(floY * nx + floX + 1) * 5 + 3]; + float p3x = data[(floY * nx + floX + nx) * 5 + 2]; + float p3y = data[(floY * nx + floX + nx) * 5 + 3]; + float p4x = data[(floY * nx + floX + nx + 1) * 5 + 2]; + float p4y = data[(floY * nx + floX + nx + 1) * 5 + 3]; + + if (locX + locY <= 1) + { + // the top left part triangle + *u = p1x + locX * (p2x - p1x) + locY * (p3x - p1x); + *v = p1y + locX * (p2y - p1y) + locY * (p3y - p1y); + } + else + { + // the bottom right triangle + locX -= 1; + locY -= 1; + *u = p4x + locX * (p4x - p3x) + locY * (p4x - p2x); + *v = p4y + locX * (p4y - p3y) + locY * (p4y - p2y); + } + // Texture coordinate is [0, 1], normalize to [-1, 1] + *u = (*u) * 2 - 1; + *v = (*v) * 2 - 1; + return true; +} + +WarpMeshManager* GetWarpMeshManager() +{ + static WarpMeshManager* warpMeshManager = nullptr; + if (warpMeshManager == nullptr) + warpMeshManager = new WarpMeshManager("warp"); + return warpMeshManager; +} + +static string resolveWildcard(const string& filename) +{ + string base(filename, 0, filename.length() - 1); + + string mapfile = base + "map"; + ifstream in(mapfile); + if (in.good()) + return mapfile; + + return {}; +} + +fs::path WarpMeshInfo::resolve(const fs::path& baseDir) +{ + bool wildcard = false; + if (!source.empty() && source.at(source.length() - 1) == '*') + wildcard = true; + + fs::path filename = baseDir / source; + if (wildcard) + { + string matched = resolveWildcard(filename.string()); + if (matched.empty()) + return filename; // . . . for lack of any better way to handle it. + else + return matched; + } + + return filename; +} + + +WarpMesh* WarpMeshInfo::load(const fs::path& name) +{ +#define MESHTYPE_RECT 2 + ifstream f(name.string()); + if (!f.good()) + return nullptr; + + int type, nx, ny; + if (!(f >> type)) + { + DPRINTF(LOG_LEVEL_ERROR, "Failed to read mesh header\n"); + return nullptr; + } + + if (type != MESHTYPE_RECT) + { + DPRINTF(LOG_LEVEL_ERROR, "Unsupported mesh type found: %d\n", type); + return nullptr; + } + + if (!(f >> nx >> ny)) + { + DPRINTF(LOG_LEVEL_ERROR, "Failed to read mesh header\n"); + return nullptr; + } + + if (nx < 2 || ny < 2) + { + DPRINTF(LOG_LEVEL_ERROR, "Row and column numbers should be larger than 2\n"); + return nullptr; + } + + float *data = new float[nx * ny * 5]; + for (int y = 0; y < ny; y += 1) + { + for (int x = 0; x < nx; x += 1) + { + float *base = &data[(y * nx + x) * 5]; + if (!(f >> base[0] >> base[1] >> base[2] >> base[3] >> base[4])) + { + DPRINTF(LOG_LEVEL_ERROR, "Failed to read mesh data\n"); + delete[] data; + return nullptr; + } + } + } + DPRINTF(LOG_LEVEL_INFO, "Read a mesh of %d * %d\n", nx, ny); + return new WarpMesh(nx, ny, data); +} diff --git a/src/celengine/mapmanager.h b/src/celengine/mapmanager.h new file mode 100644 index 000000000..cd6109374 --- /dev/null +++ b/src/celengine/mapmanager.h @@ -0,0 +1,57 @@ +// +// mapmanager.h +// +// Copyright © 2020 Celestia Development Team. All rights reserved. +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. + +#pragma once + +#include +#include +#include +#include + +// File format for data used to warp an image, for +// detail, see http://paulbourke.net/dataformats/meshwarp/ +class WarpMesh +{ + public: + WarpMesh(int nx, int ny, float* data); + ~WarpMesh(); + + // Map data to triangle vertices used for drawing + void scopedDataForRendering(const std::function&) const; + int count() const; // Number of vertices + + // Convert a vertex coordinate to texture coordinate + bool mapVertex(float x, float y, float* u, float* v) const; + + private: + int nx; + int ny; + float* data; +}; + +class WarpMeshInfo : public ResourceInfo +{ + public: + std::string source; + + WarpMeshInfo(const std::string& source) : source(source) {}; + + fs::path resolve(const fs::path&) override; + WarpMesh* load(const fs::path&) override; +}; + +inline bool operator<(const WarpMeshInfo& wi0, const WarpMeshInfo& wi1) +{ + return wi0.source < wi1.source; +} + +typedef ResourceManager WarpMeshManager; + +WarpMeshManager* GetWarpMeshManager(); diff --git a/src/celengine/renderglsl.cpp b/src/celengine/renderglsl.cpp index 72e926f1a..b6e5fbaed 100644 --- a/src/celengine/renderglsl.cpp +++ b/src/celengine/renderglsl.cpp @@ -910,6 +910,8 @@ void renderGeometryShadow_GLSL(Geometry* geometry, if (prog == nullptr) return; + GLint oldFboId; + glGetIntegerv(GL_FRAMEBUFFER_BINDING, &oldFboId); shadowFbo->bind(); glViewport(0, 0, shadowFbo->width(), shadowFbo->height()); @@ -939,5 +941,5 @@ void renderGeometryShadow_GLSL(Geometry* geometry, // Re-enable the color buffer glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); glCullFace(GL_BACK); - shadowFbo->unbind(); + shadowFbo->unbind(oldFboId); } diff --git a/src/celengine/shadermanager.cpp b/src/celengine/shadermanager.cpp index d6d3087bc..258a96f6e 100644 --- a/src/celengine/shadermanager.cpp +++ b/src/celengine/shadermanager.cpp @@ -3213,6 +3213,10 @@ ShaderManager::buildProgram(const ShaderProperties& props) CelestiaGLProgram::ColorAttributeIndex, "in_Color"); + glBindAttribLocation(prog->getID(), + CelestiaGLProgram::IntensityAttributeIndex, + "in_Intensity"); + if (props.texUsage & ShaderProperties::NormalTexture) { glBindAttribLocation(prog->getID(), @@ -3313,6 +3317,10 @@ ShaderManager::buildProgram(const std::string& vs, const std::string& fs) CelestiaGLProgram::PointSizeAttributeIndex, "in_PointSize"); + glBindAttribLocation(prog->getID(), + CelestiaGLProgram::IntensityAttributeIndex, + "in_Intensity"); + status = prog->link(); } diff --git a/src/celengine/shadermanager.h b/src/celengine/shadermanager.h index cdddd64a4..6a1fcef1c 100644 --- a/src/celengine/shadermanager.h +++ b/src/celengine/shadermanager.h @@ -183,6 +183,7 @@ class CelestiaGLProgram TangentAttributeIndex = 6, PointSizeAttributeIndex = 7, ColorAttributeIndex = 8, + IntensityAttributeIndex = 9, }; public: diff --git a/src/celengine/viewporteffect.cpp b/src/celengine/viewporteffect.cpp new file mode 100644 index 000000000..536ba81e1 --- /dev/null +++ b/src/celengine/viewporteffect.cpp @@ -0,0 +1,161 @@ +// +// viewporteffect.cpp +// +// Copyright © 2020 Celestia Development Team. All rights reserved. +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. + +#include "viewporteffect.h" +#include "framebuffer.h" +#include "render.h" +#include "shadermanager.h" +#include "mapmanager.h" + +bool ViewportEffect::preprocess(Renderer* renderer, FramebufferObject* fbo) +{ + glGetIntegerv(GL_FRAMEBUFFER_BINDING, &oldFboId); + return fbo->bind(); +} + +bool ViewportEffect::prerender(Renderer* renderer, FramebufferObject* fbo) +{ + if (!fbo->unbind(oldFboId)) + return false; + + glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + return true; +} + +bool ViewportEffect::distortXY(float &x, float &y) +{ + return true; +} + +PassthroughViewportEffect::PassthroughViewportEffect() : + ViewportEffect(), + vo(GL_ARRAY_BUFFER, 0, GL_STATIC_DRAW) +{ +} + +bool PassthroughViewportEffect::prerender(Renderer* renderer, FramebufferObject* fbo) +{ + if (!ViewportEffect::prerender(renderer, fbo)) + return false; + + renderer->disableDepthTest(); + return true; +} + +bool PassthroughViewportEffect::render(Renderer* renderer, FramebufferObject* fbo, int width, int height) +{ + CelestiaGLProgram *prog = renderer->getShaderManager().getShader("passthrough"); + if (prog == nullptr) + return false; + + vo.bind(); + if (!vo.initialized()) + initializeVO(vo); + + prog->use(); + prog->samplerParam("tex") = 0; + glBindTexture(GL_TEXTURE_2D, fbo->colorTexture()); + draw(vo); + glBindTexture(GL_TEXTURE_2D, 0); + vo.unbind(); + return true; +} + +void PassthroughViewportEffect::initializeVO(celgl::VertexObject& vo) +{ + static float quadVertices[] = { + // positions // texCoords + -1.0f, 1.0f, 0.0f, 1.0f, + -1.0f, -1.0f, 0.0f, 0.0f, + 1.0f, -1.0f, 1.0f, 0.0f, + + -1.0f, 1.0f, 0.0f, 1.0f, + 1.0f, -1.0f, 1.0f, 0.0f, + 1.0f, 1.0f, 1.0f, 1.0f + }; + vo.allocate(sizeof(quadVertices), quadVertices); + vo.setVertices(2, GL_FLOAT, false, 4 * sizeof(float), 0); + vo.setTextureCoords(2, GL_FLOAT, false, 4 * sizeof(float), 2 * sizeof(float)); +} + +void PassthroughViewportEffect::draw(celgl::VertexObject& vo) +{ + vo.draw(GL_TRIANGLES, 6); +} + +WarpMeshViewportEffect::WarpMeshViewportEffect(WarpMesh *mesh) : + ViewportEffect(), + vo(GL_ARRAY_BUFFER, 0, GL_STATIC_DRAW), + mesh(mesh) +{ +} + +bool WarpMeshViewportEffect::prerender(Renderer* renderer, FramebufferObject* fbo) +{ + if (mesh == nullptr) + return false; + + if (!ViewportEffect::prerender(renderer, fbo)) + return false; + + renderer->disableDepthTest(); + return true; +} + +bool WarpMeshViewportEffect::render(Renderer* renderer, FramebufferObject* fbo, int width, int height) +{ + CelestiaGLProgram *prog = renderer->getShaderManager().getShader("warpmesh"); + if (prog == nullptr) + return false; + + vo.bind(); + if (!vo.initialized()) + initializeVO(vo); + + prog->use(); + prog->samplerParam("tex") = 0; + prog->floatParam("screenRatio") = (float)height / width; + glBindTexture(GL_TEXTURE_2D, fbo->colorTexture()); + draw(vo); + glBindTexture(GL_TEXTURE_2D, 0); + vo.unbind(); + return true; +} + +void WarpMeshViewportEffect::initializeVO(celgl::VertexObject& vo) +{ + mesh->scopedDataForRendering([&vo](float *data, int size){ + vo.allocate(size, data); + vo.setVertices(2, GL_FLOAT, false, 5 * sizeof(float), 0); + vo.setTextureCoords(2, GL_FLOAT, false, 5 * sizeof(float), 2 * sizeof(float)); + vo.setVertexAttribArray(CelestiaGLProgram::IntensityAttributeIndex, 1, GL_FLOAT, false, 5 * sizeof(float), 4 * sizeof(float)); + }); +} + +void WarpMeshViewportEffect::draw(celgl::VertexObject& vo) +{ + vo.draw(GL_TRIANGLES, mesh->count()); +} + +bool WarpMeshViewportEffect::distortXY(float &x, float &y) +{ + if (mesh == nullptr) + return false; + + float u; + float v; + if (!mesh->mapVertex(x * 2, y * 2, &u, &v)) + return false; + + x = u / 2; + y = v / 2; + return true; +} diff --git a/src/celengine/viewporteffect.h b/src/celengine/viewporteffect.h new file mode 100644 index 000000000..2876fcede --- /dev/null +++ b/src/celengine/viewporteffect.h @@ -0,0 +1,68 @@ +// +// viewporteffect.h +// +// Copyright © 2020 Celestia Development Team. All rights reserved. +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. + +#pragma once + +#include +#include +#include + +class FramebufferObject; +class Renderer; +class CelestiaGLProgram; +class WarpMesh; + +class ViewportEffect +{ + public: + virtual ~ViewportEffect() = default; + + virtual bool preprocess(Renderer*, FramebufferObject*); + virtual bool prerender(Renderer*, FramebufferObject*); + virtual bool render(Renderer*, FramebufferObject*, int width, int height) = 0; + virtual bool distortXY(float& x, float& y); + + private: + GLint oldFboId; +}; + +class PassthroughViewportEffect : public ViewportEffect +{ + public: + PassthroughViewportEffect(); + ~PassthroughViewportEffect() override = default; + + bool prerender(Renderer*, FramebufferObject* fbo) override; + bool render(Renderer*, FramebufferObject*, int width, int height) override; + + private: + celgl::VertexObject vo; + + void initializeVO(celgl::VertexObject&); + void draw(celgl::VertexObject&); +}; + +class WarpMeshViewportEffect : public ViewportEffect +{ + public: + WarpMeshViewportEffect(WarpMesh *mesh); + ~WarpMeshViewportEffect() override = default; + + bool prerender(Renderer*, FramebufferObject* fbo) override; + bool render(Renderer*, FramebufferObject*, int width, int height) override; + bool distortXY(float& x, float& y) override; + + private: + celgl::VertexObject vo; + WarpMesh *mesh; + + void initializeVO(celgl::VertexObject&); + void draw(celgl::VertexObject&); +}; diff --git a/src/celestia/celestiacore.cpp b/src/celestia/celestiacore.cpp index 7e8822e26..fc9c904fc 100644 --- a/src/celestia/celestiacore.cpp +++ b/src/celestia/celestiacore.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -50,6 +51,7 @@ #include #include #include +#include #ifdef CELX #include @@ -424,8 +426,11 @@ void CelestiaCore::mouseButtonUp(float x, float y, int button) (*activeView)->mapWindowToView((float) x / (float) width, (float) y / (float) height, pickX, pickY); - Vector3f pickRay = - sim->getActiveObserver()->getPickRay(pickX * aspectRatio, pickY); + pickX *= aspectRatio; + if (isViewportEffectUsed) + viewportEffect->distortXY(pickX, pickY); + + Vector3f pickRay = sim->getActiveObserver()->getPickRay(pickX, pickY); Selection oldSel = sim->getSelection(); Selection newSel = sim->pickObject(pickRay, renderer->getRenderFlags(), pickTolerance); @@ -441,8 +446,11 @@ void CelestiaCore::mouseButtonUp(float x, float y, int button) (*activeView)->mapWindowToView((float) x / (float) width, (float) y / (float) height, pickX, pickY); - Vector3f pickRay = - sim->getActiveObserver()->getPickRay(pickX * aspectRatio, pickY); + pickX *= aspectRatio; + if (isViewportEffectUsed) + viewportEffect->distortXY(pickX, pickY); + + Vector3f pickRay = sim->getActiveObserver()->getPickRay(pickX, pickY); Selection sel = sim->pickObject(pickRay, renderer->getRenderFlags(), pickTolerance); if (!sel.empty()) @@ -2057,30 +2065,13 @@ void CelestiaCore::draw() return; viewChanged = false; - if (views.size() == 1) - { - // I'm not certain that a special case for one view is required; but, - // it's possible that there exists some broken hardware out there - // that has to fall back to software rendering if the scissor test - // is enabled. To keep performance on this hypothetical hardware - // reasonable in the typical single view case, we'll use this - // scissorless special case. I'm only paranoid because I've been - // burned by crap hardware so many times. cjl + // Render each view + for (const auto view : views) + draw(view); + + // Reset to render to the main window + if (views.size() > 1) renderer->setRenderRegion(0, 0, width, height, false); - sim->render(*renderer); - } - else - { - for (const auto view : views) - { - if (view->type == View::ViewWindow) - { - view->switchTo(width, height); - sim->render(*renderer, *view->observer); - } - } - renderer->setRenderRegion(0, 0, width, height, false); - } bool toggleAA = renderer->isMSAAEnabled(); if (toggleAA && (renderer->getRenderFlags() & Renderer::ShowCloudMaps)) @@ -2143,6 +2134,47 @@ void CelestiaCore::resize(GLsizei w, GLsizei h) return; } +void CelestiaCore::draw(View* view) +{ + if (view->type != View::ViewWindow) return; + + bool viewportEffectUsed = false; + + FramebufferObject *fbo = nullptr; + if (viewportEffect != nullptr) + { + // create/update FBO for viewport effect + view->updateFBO(width, height); + fbo = view->getFBO(); + } + bool process = fbo != nullptr && viewportEffect->preprocess(renderer, fbo); + + int x = view->x * width; + int y = view->y * height; + int viewWidth = view->width * width; + int viewHeight = view->height * height; + // If we need to process, we draw to the FBO which starts at point zero + renderer->setRenderRegion(process ? 0 : x, process ? 0 : y, viewWidth, viewHeight, !view->isRootView()); + + if (view->isRootView()) + sim->render(*renderer); + else + sim->render(*renderer, *view->observer); + + // Viewport need to be reset to start from (x,y) instead of point zero + if (process && (x != 0 || y != 0)) + renderer->setRenderRegion(x, y, viewWidth, viewHeight); + + if (process && viewportEffect->prerender(renderer, fbo)) + { + if (viewportEffect->render(renderer, fbo, viewWidth, viewHeight)) + viewportEffectUsed = true; + else + DPRINTF(LOG_LEVEL_ERROR, "Unable to render viewport effect.\n"); + } + isViewportEffectUsed = viewportEffectUsed; +} + void CelestiaCore::setSafeAreaInsets(int left, int top, int right, int bottom) { @@ -3781,6 +3813,30 @@ bool CelestiaCore::initSimulation(const fs::path& configFileName, } } + if (!config->viewportEffect.empty() && config->viewportEffect != "none") + { + if (config->viewportEffect == "passthrough") + viewportEffect = unique_ptr(new PassthroughViewportEffect); + else if (config->viewportEffect == "warpmesh") + { + if (config->warpMeshFile.empty()) + { + DPRINTF(LOG_LEVEL_WARNING, "No warp mesh file specified for this effect\n"); + } + else + { + WarpMeshManager *manager = GetWarpMeshManager(); + WarpMesh *mesh = manager->find(manager->getHandle(WarpMeshInfo(config->warpMeshFile))); + if (mesh != nullptr) + viewportEffect = unique_ptr(new WarpMeshViewportEffect(mesh)); + else + DPRINTF(LOG_LEVEL_WARNING, "Failed to read warp mesh file %s\n", config->warpMeshFile); + } + } + else + DPRINTF(LOG_LEVEL_WARNING, "Unknown viewport effect %s\n", config->viewportEffect); + } + sim = new Simulation(universe); if ((renderer->getRenderFlags() & Renderer::ShowAutoMag) == 0) { diff --git a/src/celestia/celestiacore.h b/src/celestia/celestiacore.h index 7321bd628..7736d7fe1 100644 --- a/src/celestia/celestiacore.h +++ b/src/celestia/celestiacore.h @@ -21,6 +21,7 @@ #include #include #include +#include #include "configfile.h" #include "favorites.h" #include "destination.h" @@ -210,6 +211,7 @@ class CelestiaCore // : public Watchable void joystickButton(int button, bool down); void resize(GLsizei w, GLsizei h); void draw(); + void draw(View*); void tick(); Simulation* getSimulation() const; @@ -465,6 +467,9 @@ class CelestiaCore // : public Watchable int screenDpi{ 96 }; int distanceToScreen{ 400 }; + unique_ptr viewportEffect { nullptr }; + bool isViewportEffectUsed { false }; + struct EdgeInsets { int left; diff --git a/src/celestia/configfile.cpp b/src/celestia/configfile.cpp index e68f73319..24f1a6542 100644 --- a/src/celestia/configfile.cpp +++ b/src/celestia/configfile.cpp @@ -93,6 +93,8 @@ CelestiaConfig* ReadCelestiaConfig(const fs::path& filename, CelestiaConfig *con configParams->getString("TitleFont", config->titleFont); configParams->getPath("LogoTexture", config->logoTextureFile); configParams->getString("Cursor", config->cursor); + configParams->getString("ViewportEffect", config->viewportEffect); + configParams->getString("WarpMeshFile", config->warpMeshFile); float maxDist = 1.0; configParams->getNumber("SolarSystemMaxDistance", maxDist); diff --git a/src/celestia/configfile.h b/src/celestia/configfile.h index d94ea036f..762f0bf65 100644 --- a/src/celestia/configfile.h +++ b/src/celestia/configfile.h @@ -78,6 +78,9 @@ public: float SolarSystemMaxDistance; unsigned ShadowMapSize; + + std::string viewportEffect; + std::string warpMeshFile; }; CelestiaConfig* ReadCelestiaConfig(const fs::path& filename, CelestiaConfig* config = nullptr); diff --git a/src/celestia/view.cpp b/src/celestia/view.cpp index a345ddba2..7e8f7286e 100644 --- a/src/celestia/view.cpp +++ b/src/celestia/view.cpp @@ -9,6 +9,7 @@ #include #include +#include #include #include "view.h" @@ -150,14 +151,6 @@ Observer* View::getObserver() const return observer; } -void View::switchTo(int gWidth, int gHeight) -{ - renderer->setRenderRegion(int(x * gWidth), - int(y * gHeight), - int(width * gWidth), - int(height * gHeight)); -} - bool View::isSplittable(Type type) const { // If active view is too small, don't split it. @@ -254,6 +247,7 @@ void View::reset() parent = nullptr; child1 = nullptr; child2 = nullptr; + fbo = nullptr; } void View::drawBorder(int gWidth, int gHeight, const Color &color, float linewidth) @@ -264,3 +258,25 @@ void View::drawBorder(int gWidth, int gHeight, const Color &color, float linewid r.setLineWidth(linewidth); renderer->drawRectangle(r, renderer->getOrthoProjectionMatrix()); } + +void View::updateFBO(int gWidth, int gHeight) +{ + int newWidth = width * gWidth; + int newHeight = height * gHeight; + if (fbo && fbo.get()->width() == newWidth && fbo.get()->height() == newHeight) + return; + + // recreate FBO when FBO not exisits or on size change + fbo = unique_ptr(new FramebufferObject(newWidth, newHeight, + FramebufferObject::ColorAttachment | FramebufferObject::DepthAttachment)); + if (!fbo->isValid()) + { + DPRINTF(LOG_LEVEL_ERROR, "Error creating view FBO.\n"); + fbo = nullptr; + } +} + +FramebufferObject *View::getFBO() const +{ + return fbo.get(); +} diff --git a/src/celestia/view.h b/src/celestia/view.h index 65a31be93..fc931f0b5 100644 --- a/src/celestia/view.h +++ b/src/celestia/view.h @@ -38,7 +38,6 @@ class View void walkTreeResize(View*, int); bool walkTreeResizeDelta(View*, float, bool); - void switchTo(int gWidth, int gHeight); Observer* getObserver() const; bool isRootView() const; bool isSplittable(Type type) const; @@ -46,6 +45,8 @@ class View void reset(); static View* remove(View*); void drawBorder(int gWidth, int gHeight, const Color &color, float linewidth = 1.0f); + void updateFBO(int gWidth, int gHeight); + FramebufferObject *getFBO() const; public: Type type; @@ -63,6 +64,9 @@ class View int labelMode { 0 }; float zoom { 1.0f }; float alternateZoom { 1.0f }; + +private: + std::unique_ptr fbo; }; //} diff --git a/src/celutil/filetype.cpp b/src/celutil/filetype.cpp index 891e83c02..378209901 100644 --- a/src/celutil/filetype.cpp +++ b/src/celutil/filetype.cpp @@ -37,6 +37,7 @@ static const char CelestiaModelExt[] = ".cmod"; static const char CelestiaParticleSystemExt[] = ".cpart"; static const char CelestiaXYZTrajectoryExt[] = ".xyz"; static const char CelestiaXYZVTrajectoryExt[] = ".xyzv"; +static const char Content_WarpMeshExt[] = ".map"; ContentType DetermineFileType(const fs::path& filename) { @@ -83,6 +84,8 @@ ContentType DetermineFileType(const fs::path& filename) return Content_CelestiaXYZTrajectory; if (compareIgnoringCase(CelestiaXYZVTrajectoryExt, ext) == 0) return Content_CelestiaXYZVTrajectory; + if (compareIgnoringCase(Content_WarpMeshExt, ext) == 0) + return Content_WarpMesh; else return Content_Unknown; } diff --git a/src/celutil/filetype.h b/src/celutil/filetype.h index 012754625..bbc72dc3b 100644 --- a/src/celutil/filetype.h +++ b/src/celutil/filetype.h @@ -35,6 +35,7 @@ enum ContentType Content_CelestiaXYZTrajectory = 18, Content_CelestiaXYZVTrajectory = 19, Content_CelestiaParticleSystem = 20, + Content_WarpMesh = 21, Content_Unknown = -1, };