[Scummvm-git-logs] scummvm master -> c66e0f4fa3da891ee9615565077bb6890276a8be
neuromancer
noreply at scummvm.org
Thu Apr 30 16:56:09 UTC 2026
This automated email contains information about 8 new commits which have been
pushed to the 'scummvm' repo located at https://api.github.com/repos/scummvm/scummvm .
Summary:
8368df463a COLONY: initial code for shaders renderer
a6056fe555 COLONY: added basic shaders support for 2d primitives
e0b482a360 COLONY: added basic shaders support for unoptimized 3d primitives
47442da883 COLONY: optimize shader 3d primitives code
22276d5926 COLONY: support for stipples in shader renderer
4f4b855c9d COLONY: add shader files into distribution files
ca3f853772 COLONY: deleted scroll interface
c66e0f4fa3 COLONY: enable SupportsArbitraryResolutions
Commit: 8368df463a0d13bd5ac6d750d799beaa3ea1e3fe
https://github.com/scummvm/scummvm/commit/8368df463a0d13bd5ac6d750d799beaa3ea1e3fe
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-04-30T18:55:53+02:00
Commit Message:
COLONY: initial code for shaders renderer
Changed paths:
A engines/colony/renderer_opengl_shaders.cpp
A engines/colony/renderer_opengl_shaders.h
engines/colony/gfx.cpp
engines/colony/module.mk
diff --git a/engines/colony/gfx.cpp b/engines/colony/gfx.cpp
index 52627e3da9c..829d456b2ca 100644
--- a/engines/colony/gfx.cpp
+++ b/engines/colony/gfx.cpp
@@ -27,19 +27,81 @@
#include "common/config-manager.h"
#include "common/system.h"
+#include "common/textconsole.h"
#include "engines/util.h"
+#include "graphics/renderer.h"
#include "colony/renderer.h"
+#include "colony/renderer_opengl_shaders.h"
namespace Colony {
-// Forward declaration for the OpenGL renderer factory
+// Forward declaration for the fixed-function OpenGL renderer factory.
Renderer *createOpenGLRenderer(OSystem *system, int width, int height);
+// Pick the renderer type. Honors --renderer=<code> on the command line /
+// ConfMan key, restricted to what was compiled in.
+//
+// Phase 1 policy: default users get fixed-function OpenGL. The shader path
+// is opt-in (--renderer=opengl_shaders) until later phases bring its
+// primitive coverage up to parity. Note that the generic preference order
+// in graphics/renderer.cpp:122 picks shaders for the Default case, so we
+// must override it here.
+static Graphics::RendererType pickRendererType() {
+ const Common::String configured = ConfMan.get("renderer");
+ const Graphics::RendererType desired = Graphics::Renderer::parseTypeCode(configured);
+
+ if (desired == Graphics::kRendererTypeDefault) {
+#ifdef USE_OPENGL_GAME
+ return Graphics::kRendererTypeOpenGL;
+#elif defined(USE_OPENGL_SHADERS)
+ return Graphics::kRendererTypeOpenGLShaders;
+#else
+ return Graphics::kRendererTypeDefault;
+#endif
+ }
+
+ const uint32 supported =
+#ifdef USE_OPENGL_GAME
+ Graphics::kRendererTypeOpenGL |
+#endif
+#ifdef USE_OPENGL_SHADERS
+ Graphics::kRendererTypeOpenGLShaders |
+#endif
+ 0;
+ const Graphics::RendererType matching =
+ Graphics::Renderer::getBestMatchingAvailableType(desired, supported);
+
+ if (matching != desired)
+ warning("Colony: requested renderer '%s' is unavailable, falling back",
+ configured.c_str());
+ return matching;
+}
+
Renderer *createRenderer(OSystem *system, int width, int height) {
- // Always use OpenGL (following Freescape's pattern for accelerated renderers)
+ const Graphics::RendererType type = pickRendererType();
initGraphics3d(width, height);
- return createOpenGLRenderer(system, width, height);
+
+#if defined(USE_OPENGL_SHADERS)
+ if (type == Graphics::kRendererTypeOpenGLShaders) {
+ Renderer *r = createOpenGLShaderRenderer(system, width, height);
+ if (r)
+ return r;
+ warning("Colony: shader renderer factory returned null, falling back to fixed-function");
+ }
+#endif
+
+#if defined(USE_OPENGL_GAME)
+ if (type == Graphics::kRendererTypeOpenGL || type == Graphics::kRendererTypeDefault)
+ return createOpenGLRenderer(system, width, height);
+#endif
+
+ // Last resort: try fixed-function unconditionally so the engine still
+ // runs in builds where neither flag was caught above.
+ Renderer *r = createOpenGLRenderer(system, width, height);
+ if (!r)
+ error("Colony: no renderer available (built without OpenGL support?)");
+ return r;
}
} // End of namespace Colony
diff --git a/engines/colony/module.mk b/engines/colony/module.mk
index f2d1a05b782..ef172a65071 100644
--- a/engines/colony/module.mk
+++ b/engines/colony/module.mk
@@ -15,6 +15,7 @@ MODULE_OBJS := \
render_features.o \
render_objects.o \
renderer_opengl.o \
+ renderer_opengl_shaders.o \
savegame.o \
sound.o \
think.o \
diff --git a/engines/colony/renderer_opengl_shaders.cpp b/engines/colony/renderer_opengl_shaders.cpp
new file mode 100644
index 00000000000..d7911f51997
--- /dev/null
+++ b/engines/colony/renderer_opengl_shaders.cpp
@@ -0,0 +1,117 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Based on the original sources
+ * https://github.com/Croquetx/thecolony
+ * Copyright (C) 1988, David A. Smith
+ *
+ * Distributed under Apache Version 2.0 License
+ *
+ */
+
+#include "common/scummsys.h"
+#include "common/system.h"
+#include "common/textconsole.h"
+
+#include "colony/renderer.h"
+#include "colony/renderer_opengl_shaders.h"
+
+#ifdef USE_OPENGL_SHADERS
+
+#include "graphics/opengl/system_headers.h"
+
+namespace Colony {
+
+// Phase 1: skeleton only. Every Renderer override is a stub. Callers can
+// build with USE_OPENGL_SHADERS, instantiate the renderer (e.g. via
+// `--renderer=opengl_shaders`), but the screen will be empty until later
+// phases fill in primitives, 3D draws, and the surface blit path.
+//
+// The fixed-function path (`renderer_opengl.cpp`) remains the default and
+// is unaffected by this file.
+class OpenGLShaderRenderer : public Renderer {
+public:
+ OpenGLShaderRenderer(OSystem *system, int width, int height)
+ : _system(system), _width(width), _height(height) {
+ (void)_width; // populated for Phase 2+ primitive implementations
+ (void)_height;
+ warning("Colony: OpenGL shader renderer is a Phase 1 skeleton â "
+ "primitives are stubbed; use --renderer=opengl for the working renderer");
+ }
+
+ ~OpenGLShaderRenderer() override {}
+
+ // 2D primitives â stubbed.
+ void clear(uint32 color) override {}
+ void drawLine(int x1, int y1, int x2, int y2, uint32 color) override {}
+ void drawRect(const Common::Rect &rect, uint32 color) override {}
+ void fillRect(const Common::Rect &rect, uint32 color) override {}
+ void drawString(const Graphics::Font *font, const Common::String &str, int x, int y,
+ uint32 color, Graphics::TextAlign align) override {}
+ void scroll(int dx, int dy, uint32 background) override {}
+ void drawEllipse(int x, int y, int rx, int ry, uint32 color) override {}
+ void fillEllipse(int x, int y, int rx, int ry, uint32 color) override {}
+ void fillDitherRect(const Common::Rect &rect, uint32 c1, uint32 c2) override {}
+ void setPixel(int x, int y, uint32 color) override {}
+ void drawQuad(int x1, int y1, int x2, int y2, int x3, int y3, int x4, int y4, uint32 color) override {}
+ void drawPolygon(const int *x, const int *y, int count, uint32 color) override {}
+
+ void setPalette(const byte *palette, uint start, uint count) override {}
+
+ // 3D scene rendering â stubbed.
+ void begin3D(int camX, int camY, int camZ, int angle, int angleY, const Common::Rect &viewport) override {}
+ void draw3DWall(int x1, int y1, int x2, int y2, uint32 color) override {}
+ void draw3DQuad(float x1, float y1, float z1, float x2, float y2, float z2,
+ float x3, float y3, float z3, float x4, float y4, float z4, uint32 color) override {}
+ void draw3DPolygon(const float *x, const float *y, const float *z, int count, uint32 color) override {}
+ void draw3DLine(float x1, float y1, float z1, float x2, float y2, float z2, uint32 color) override {}
+ void end3D() override {}
+
+ // Buffer / state management.
+ void copyToScreen() override { _system->updateScreen(); }
+ void setWireframe(bool enable, int64_t fillColor) override {}
+ void setXorMode(bool enable) override {}
+ void setStippleData(const byte *data) override {}
+ void setMacColors(uint32 fg, uint32 bg) override {}
+ void setDepthState(bool testEnabled, bool writeEnabled) override {}
+ void setDepthRange(float nearVal, float farVal) override {}
+ void computeScreenViewport() override {}
+
+ void drawSurface(const Graphics::Surface *surf, int x, int y) override {}
+ Graphics::Surface *getScreenshot() override { return nullptr; }
+
+private:
+ OSystem *_system = nullptr;
+ int _width = 0;
+ int _height = 0;
+};
+
+Renderer *createOpenGLShaderRenderer(OSystem *system, int width, int height) {
+ return new OpenGLShaderRenderer(system, width, height);
+}
+
+} // End of namespace Colony
+
+#else
+
+namespace Colony {
+Renderer *createOpenGLShaderRenderer(OSystem *system, int width, int height) { return nullptr; }
+}
+
+#endif
diff --git a/engines/colony/renderer_opengl_shaders.h b/engines/colony/renderer_opengl_shaders.h
new file mode 100644
index 00000000000..5e45c4a1b27
--- /dev/null
+++ b/engines/colony/renderer_opengl_shaders.h
@@ -0,0 +1,44 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Based on the original sources
+ * https://github.com/Croquetx/thecolony
+ * Copyright (C) 1988, David A. Smith
+ *
+ * Distributed under Apache Version 2.0 License
+ *
+ */
+
+#ifndef COLONY_RENDERER_OPENGL_SHADERS_H
+#define COLONY_RENDERER_OPENGL_SHADERS_H
+
+#include "common/scummsys.h"
+
+namespace Colony {
+
+class Renderer;
+
+// Factory for the programmable-pipeline OpenGL renderer. Returns nullptr
+// when USE_OPENGL_SHADERS is not defined at build time, so callers can
+// fall back to the fixed-function renderer.
+Renderer *createOpenGLShaderRenderer(OSystem *system, int width, int height);
+
+} // End of namespace Colony
+
+#endif
Commit: a6056fe555cc91aa342c02b1289a3a7a00b0bd6a
https://github.com/scummvm/scummvm/commit/a6056fe555cc91aa342c02b1289a3a7a00b0bd6a
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-04-30T18:55:53+02:00
Commit Message:
COLONY: added basic shaders support for 2d primitives
Changed paths:
A engines/colony/shaders/colony_bitmap.fragment
A engines/colony/shaders/colony_bitmap.vertex
A engines/colony/shaders/colony_solid.fragment
A engines/colony/shaders/colony_solid.vertex
engines/colony/renderer_opengl.cpp
engines/colony/renderer_opengl_shaders.cpp
graphics/opengl/shader.cpp
diff --git a/engines/colony/renderer_opengl.cpp b/engines/colony/renderer_opengl.cpp
index e8a4d56b60c..88fbd364285 100644
--- a/engines/colony/renderer_opengl.cpp
+++ b/engines/colony/renderer_opengl.cpp
@@ -115,6 +115,7 @@ private:
};
OpenGLRenderer::OpenGLRenderer(OSystem *system, int width, int height) : _system(system), _width(width), _height(height) {
+ debug(1, "Colony: using OpenGL fixed-function renderer");
_wireframe = true;
_wireframeFillColor = 0;
_stippleData = nullptr;
diff --git a/engines/colony/renderer_opengl_shaders.cpp b/engines/colony/renderer_opengl_shaders.cpp
index d7911f51997..378c8383cf2 100644
--- a/engines/colony/renderer_opengl_shaders.cpp
+++ b/engines/colony/renderer_opengl_shaders.cpp
@@ -26,55 +26,49 @@
*/
#include "common/scummsys.h"
+#include "common/config-manager.h"
+#include "common/debug.h"
#include "common/system.h"
#include "common/textconsole.h"
+#include "graphics/font.h"
+#include "graphics/surface.h"
+#include "math/matrix4.h"
#include "colony/renderer.h"
#include "colony/renderer_opengl_shaders.h"
#ifdef USE_OPENGL_SHADERS
+#include "graphics/opengl/shader.h"
#include "graphics/opengl/system_headers.h"
namespace Colony {
-// Phase 1: skeleton only. Every Renderer override is a stub. Callers can
-// build with USE_OPENGL_SHADERS, instantiate the renderer (e.g. via
-// `--renderer=opengl_shaders`), but the screen will be empty until later
-// phases fill in primitives, 3D draws, and the surface blit path.
-//
-// The fixed-function path (`renderer_opengl.cpp`) remains the default and
-// is unaffected by this file.
+// Phase 2: programmable-pipeline 2D primitives. The 3D path (begin3D and
+// the corridor draws) and the deprecated state setters (XOR, polygon
+// stipple, wireframe) remain stubs and are filled in by later phases.
class OpenGLShaderRenderer : public Renderer {
public:
- OpenGLShaderRenderer(OSystem *system, int width, int height)
- : _system(system), _width(width), _height(height) {
- (void)_width; // populated for Phase 2+ primitive implementations
- (void)_height;
- warning("Colony: OpenGL shader renderer is a Phase 1 skeleton â "
- "primitives are stubbed; use --renderer=opengl for the working renderer");
- }
-
- ~OpenGLShaderRenderer() override {}
+ OpenGLShaderRenderer(OSystem *system, int width, int height);
+ ~OpenGLShaderRenderer() override;
- // 2D primitives â stubbed.
- void clear(uint32 color) override {}
- void drawLine(int x1, int y1, int x2, int y2, uint32 color) override {}
- void drawRect(const Common::Rect &rect, uint32 color) override {}
- void fillRect(const Common::Rect &rect, uint32 color) override {}
+ void clear(uint32 color) override;
+ void drawLine(int x1, int y1, int x2, int y2, uint32 color) override;
+ void drawRect(const Common::Rect &rect, uint32 color) override;
+ void fillRect(const Common::Rect &rect, uint32 color) override;
void drawString(const Graphics::Font *font, const Common::String &str, int x, int y,
- uint32 color, Graphics::TextAlign align) override {}
+ uint32 color, Graphics::TextAlign align) override;
void scroll(int dx, int dy, uint32 background) override {}
- void drawEllipse(int x, int y, int rx, int ry, uint32 color) override {}
- void fillEllipse(int x, int y, int rx, int ry, uint32 color) override {}
- void fillDitherRect(const Common::Rect &rect, uint32 c1, uint32 c2) override {}
- void setPixel(int x, int y, uint32 color) override {}
- void drawQuad(int x1, int y1, int x2, int y2, int x3, int y3, int x4, int y4, uint32 color) override {}
- void drawPolygon(const int *x, const int *y, int count, uint32 color) override {}
+ void drawEllipse(int x, int y, int rx, int ry, uint32 color) override;
+ void fillEllipse(int x, int y, int rx, int ry, uint32 color) override;
+ void fillDitherRect(const Common::Rect &rect, uint32 c1, uint32 c2) override;
+ void setPixel(int x, int y, uint32 color) override;
+ void drawQuad(int x1, int y1, int x2, int y2, int x3, int y3, int x4, int y4, uint32 color) override;
+ void drawPolygon(const int *x, const int *y, int count, uint32 color) override;
- void setPalette(const byte *palette, uint start, uint count) override {}
+ void setPalette(const byte *palette, uint start, uint count) override;
- // 3D scene rendering â stubbed.
+ // 3D path â Phase 3.
void begin3D(int camX, int camY, int camZ, int angle, int angleY, const Common::Rect &viewport) override {}
void draw3DWall(int x1, int y1, int x2, int y2, uint32 color) override {}
void draw3DQuad(float x1, float y1, float z1, float x2, float y2, float z2,
@@ -83,25 +77,458 @@ public:
void draw3DLine(float x1, float y1, float z1, float x2, float y2, float z2, uint32 color) override {}
void end3D() override {}
- // Buffer / state management.
- void copyToScreen() override { _system->updateScreen(); }
+ void copyToScreen() override;
void setWireframe(bool enable, int64_t fillColor) override {}
void setXorMode(bool enable) override {}
void setStippleData(const byte *data) override {}
void setMacColors(uint32 fg, uint32 bg) override {}
void setDepthState(bool testEnabled, bool writeEnabled) override {}
void setDepthRange(float nearVal, float farVal) override {}
- void computeScreenViewport() override {}
+ void computeScreenViewport() override;
- void drawSurface(const Graphics::Surface *surf, int x, int y) override {}
- Graphics::Surface *getScreenshot() override { return nullptr; }
+ void drawSurface(const Graphics::Surface *surf, int x, int y) override;
+ Graphics::Surface *getScreenshot() override;
private:
+ void resolveColor(uint32 color, float rgba[4]) const;
+ void rebuildProjection();
+ void uploadSolid(const float *positions, int vertCount);
+ void drawSolid(GLenum mode, const float *positions, int vertCount, const float rgba[4]);
+ void drawTexturedQuad(int x, int y, int w, int h);
+
OSystem *_system = nullptr;
int _width = 0;
int _height = 0;
+ byte _palette[256 * 3] = {};
+ Common::Rect _screenViewport;
+
+ OpenGL::Shader *_solidShader = nullptr;
+ OpenGL::Shader *_bitmapShader = nullptr;
+ GLuint _solidVBO = 0;
+ GLuint _bitmapVBO = 0;
+ GLuint _bitmapTexture = 0;
+
+ Math::Matrix4 _projection;
+
+ // Solid VBO holds vec2 position only. Sized for the worst-case 2D
+ // primitive â the dither overlay can stream up to width*height/2 dots,
+ // so leave room for typical screens (â170k floats for an 800Ã600 split).
+ enum { kSolidVertexCapacity = 320 * 1024 };
};
+// ---------------------------------------------------------------------------
+// Construction / teardown
+// ---------------------------------------------------------------------------
+
+OpenGLShaderRenderer::OpenGLShaderRenderer(OSystem *system, int width, int height)
+ : _system(system), _width(width), _height(height) {
+ debug(1, "Colony: using OpenGL shader renderer (Phase 2: 2D primitives "
+ "functional; corridor 3D view is stubbed until Phase 3)");
+ for (int i = 0; i < 256 * 3; i++)
+ _palette[i] = 255;
+
+ static const char *solidAttribs[] = { "position", nullptr };
+ _solidShader = OpenGL::Shader::fromFiles("colony_solid", solidAttribs);
+ _solidVBO = OpenGL::Shader::createBuffer(GL_ARRAY_BUFFER,
+ sizeof(float) * 2 * kSolidVertexCapacity, nullptr, GL_DYNAMIC_DRAW);
+ _solidShader->enableVertexAttribute("position", _solidVBO, 2, GL_FLOAT, GL_FALSE,
+ 2 * sizeof(float), 0);
+
+ static const char *bitmapAttribs[] = { "position", "texcoord", nullptr };
+ _bitmapShader = OpenGL::Shader::fromFiles("colony_bitmap", bitmapAttribs);
+ // Per-draw vec2 position + vec2 texcoord, 4 vertices for a quad.
+ _bitmapVBO = OpenGL::Shader::createBuffer(GL_ARRAY_BUFFER,
+ sizeof(float) * 16, nullptr, GL_DYNAMIC_DRAW);
+ _bitmapShader->enableVertexAttribute("position", _bitmapVBO, 2, GL_FLOAT, GL_FALSE,
+ 4 * sizeof(float), 0);
+ _bitmapShader->enableVertexAttribute("texcoord", _bitmapVBO, 2, GL_FLOAT, GL_FALSE,
+ 4 * sizeof(float), 2 * sizeof(float));
+
+ glGenTextures(1, &_bitmapTexture);
+
+ glDisable(GL_DEPTH_TEST);
+ glDisable(GL_CULL_FACE);
+ glDisable(GL_BLEND);
+
+ computeScreenViewport();
+ rebuildProjection();
+
+ glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
+ glClear(GL_COLOR_BUFFER_BIT);
+}
+
+OpenGLShaderRenderer::~OpenGLShaderRenderer() {
+ OpenGL::Shader::freeBuffer(_solidVBO);
+ OpenGL::Shader::freeBuffer(_bitmapVBO);
+ delete _solidShader;
+ delete _bitmapShader;
+ if (_bitmapTexture)
+ glDeleteTextures(1, &_bitmapTexture);
+}
+
+// ---------------------------------------------------------------------------
+// Color resolution and matrix setup
+// ---------------------------------------------------------------------------
+
+void OpenGLShaderRenderer::resolveColor(uint32 color, float rgba[4]) const {
+ // Same convention as the fixed-function renderer: high byte 0xFF â
+ // direct ARGB, otherwise palette index (low byte).
+ if (color & 0xFF000000) {
+ rgba[0] = ((color >> 16) & 0xFF) / 255.0f;
+ rgba[1] = ((color >> 8) & 0xFF) / 255.0f;
+ rgba[2] = (color & 0xFF) / 255.0f;
+ } else {
+ const uint32 idx = color & 0xFF;
+ rgba[0] = _palette[idx * 3] / 255.0f;
+ rgba[1] = _palette[idx * 3 + 1] / 255.0f;
+ rgba[2] = _palette[idx * 3 + 2] / 255.0f;
+ }
+ rgba[3] = 1.0f;
+}
+
+void OpenGLShaderRenderer::rebuildProjection() {
+ // Build glOrtho(0, _width, _height, 0, -1, 1) row-major in Math::Matrix4,
+ // then transpose so glUniformMatrix4fv (which reads column-major) sees
+ // the intended matrix. Y is flipped because our engine puts y=0 at top.
+ Math::Matrix4 m;
+ for (int r = 0; r < 4; r++)
+ for (int c = 0; c < 4; c++)
+ m(r, c) = 0.0f;
+ m(0, 0) = 2.0f / (float)_width;
+ m(0, 3) = -1.0f;
+ m(1, 1) = -2.0f / (float)_height;
+ m(1, 3) = 1.0f;
+ m(2, 2) = -1.0f;
+ m(3, 3) = 1.0f;
+ m.transpose();
+ _projection = m;
+}
+
+// ---------------------------------------------------------------------------
+// Solid-color primitives
+// ---------------------------------------------------------------------------
+
+void OpenGLShaderRenderer::uploadSolid(const float *positions, int vertCount) {
+ glBindBuffer(GL_ARRAY_BUFFER, _solidVBO);
+ glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(float) * 2 * vertCount, positions);
+}
+
+void OpenGLShaderRenderer::drawSolid(GLenum mode, const float *positions, int vertCount,
+ const float rgba[4]) {
+ if (vertCount <= 0)
+ return;
+ uploadSolid(positions, vertCount);
+ _solidShader->use();
+ _solidShader->setUniform("projection", _projection);
+ _solidShader->setUniform("color", Math::Vector4d(rgba[0], rgba[1], rgba[2], rgba[3]));
+ glDrawArrays(mode, 0, vertCount);
+ _solidShader->unbind();
+}
+
+void OpenGLShaderRenderer::clear(uint32 color) {
+ float rgba[4];
+ resolveColor(color, rgba);
+ glClearColor(rgba[0], rgba[1], rgba[2], 1.0f);
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+}
+
+void OpenGLShaderRenderer::drawLine(int x1, int y1, int x2, int y2, uint32 color) {
+ float rgba[4];
+ resolveColor(color, rgba);
+ const float verts[] = { (float)x1, (float)y1, (float)x2, (float)y2 };
+ drawSolid(GL_LINES, verts, 2, rgba);
+}
+
+void OpenGLShaderRenderer::drawRect(const Common::Rect &rect, uint32 color) {
+ float rgba[4];
+ resolveColor(color, rgba);
+ const float verts[] = {
+ (float)rect.left, (float)rect.top,
+ (float)rect.right, (float)rect.top,
+ (float)rect.right, (float)rect.bottom,
+ (float)rect.left, (float)rect.bottom
+ };
+ drawSolid(GL_LINE_LOOP, verts, 4, rgba);
+}
+
+void OpenGLShaderRenderer::fillRect(const Common::Rect &rect, uint32 color) {
+ float rgba[4];
+ resolveColor(color, rgba);
+ // TRIANGLE_STRIP order: top-left, top-right, bottom-left, bottom-right
+ const float verts[] = {
+ (float)rect.left, (float)rect.top,
+ (float)rect.right, (float)rect.top,
+ (float)rect.left, (float)rect.bottom,
+ (float)rect.right, (float)rect.bottom
+ };
+ drawSolid(GL_TRIANGLE_STRIP, verts, 4, rgba);
+}
+
+void OpenGLShaderRenderer::setPixel(int x, int y, uint32 color) {
+ // Same as a 1Ã1 fillRect â this is rarely called now that animation/PICT
+ // blits go through drawSurface.
+ fillRect(Common::Rect(x, y, x + 1, y + 1), color);
+}
+
+void OpenGLShaderRenderer::drawQuad(int x1, int y1, int x2, int y2,
+ int x3, int y3, int x4, int y4, uint32 color) {
+ float rgba[4];
+ resolveColor(color, rgba);
+ // Filled body (TRIANGLE_FAN handles convex quads correctly).
+ const float fanVerts[] = {
+ (float)x1, (float)y1,
+ (float)x2, (float)y2,
+ (float)x3, (float)y3,
+ (float)x4, (float)y4
+ };
+ drawSolid(GL_TRIANGLE_FAN, fanVerts, 4, rgba);
+
+ // Match the fixed-function renderer's white outline overlay.
+ const float white[4] = { 1.0f, 1.0f, 1.0f, 1.0f };
+ drawSolid(GL_LINE_LOOP, fanVerts, 4, white);
+}
+
+void OpenGLShaderRenderer::drawPolygon(const int *x, const int *y, int count, uint32 color) {
+ if (count < 3)
+ return;
+ if (count > 1024)
+ count = 1024;
+
+ float rgba[4];
+ resolveColor(color, rgba);
+
+ float verts[2 * 1024];
+ for (int i = 0; i < count; i++) {
+ verts[i * 2 + 0] = (float)x[i];
+ verts[i * 2 + 1] = (float)y[i];
+ }
+ drawSolid(GL_TRIANGLE_FAN, verts, count, rgba);
+
+ // Same white outline as drawQuad.
+ const float white[4] = { 1.0f, 1.0f, 1.0f, 1.0f };
+ drawSolid(GL_LINE_LOOP, verts, count, white);
+}
+
+void OpenGLShaderRenderer::drawEllipse(int x, int y, int rx, int ry, uint32 color) {
+ float rgba[4];
+ resolveColor(color, rgba);
+ const int kSegments = 36; // 10° steps, matching the fixed-function path
+ float verts[kSegments * 2];
+ for (int i = 0; i < kSegments; i++) {
+ const float rad = i * 2.0f * (float)M_PI / kSegments;
+ verts[i * 2 + 0] = x + cosf(rad) * (float)rx;
+ verts[i * 2 + 1] = y + sinf(rad) * (float)ry;
+ }
+ drawSolid(GL_LINE_LOOP, verts, kSegments, rgba);
+}
+
+void OpenGLShaderRenderer::fillEllipse(int x, int y, int rx, int ry, uint32 color) {
+ float rgba[4];
+ resolveColor(color, rgba);
+ const int kSegments = 36;
+ float verts[kSegments * 2];
+ for (int i = 0; i < kSegments; i++) {
+ const float rad = i * 2.0f * (float)M_PI / kSegments;
+ verts[i * 2 + 0] = x + cosf(rad) * (float)rx;
+ verts[i * 2 + 1] = y + sinf(rad) * (float)ry;
+ }
+ drawSolid(GL_TRIANGLE_FAN, verts, kSegments, rgba);
+}
+
+void OpenGLShaderRenderer::fillDitherRect(const Common::Rect &rect, uint32 c1, uint32 c2) {
+ fillRect(rect, c1);
+ float rgba[4];
+ resolveColor(c2, rgba);
+
+ const int w = rect.width();
+ const int h = rect.height();
+ if (w <= 0 || h <= 0)
+ return;
+
+ // 50% checkerboard: place a dot on every other pixel, alternating per row.
+ // Capacity guard â fall back to solid c2 if the rect is larger than our
+ // streaming buffer (very rare: only the dashboard background hits this
+ // path, and it is much smaller than kSolidVertexCapacity).
+ const int maxDots = kSolidVertexCapacity;
+ int dots = 0;
+ float *verts = new float[maxDots * 2];
+ for (int yi = 0; yi < h && dots < maxDots; yi++) {
+ const int yy = rect.top + yi;
+ for (int xi = (yi & 1); xi < w && dots < maxDots; xi += 2) {
+ verts[dots * 2 + 0] = (float)(rect.left + xi);
+ verts[dots * 2 + 1] = (float)yy;
+ dots++;
+ }
+ }
+ if (dots > 0)
+ drawSolid(GL_POINTS, verts, dots, rgba);
+ delete[] verts;
+}
+
+// ---------------------------------------------------------------------------
+// Text rendering
+// ---------------------------------------------------------------------------
+
+void OpenGLShaderRenderer::drawString(const Graphics::Font *font, const Common::String &str,
+ int x, int y, uint32 color, Graphics::TextAlign align) {
+ if (!font)
+ return;
+ const int w = font->getStringWidth(str);
+ const int h = font->getFontHeight();
+ if (w <= 0 || h <= 0)
+ return;
+
+ if (align == Graphics::kTextAlignCenter)
+ x -= w / 2;
+ else if (align == Graphics::kTextAlignRight)
+ x -= w;
+
+ float rgba[4];
+ resolveColor(color, rgba);
+
+ // Render glyphs to a 1-byte-per-pixel mask, then build an RGBA image
+ // where set pixels carry the requested color (alpha 1) and unset
+ // pixels are transparent. Upload as a texture and draw a single quad.
+ Graphics::Surface mask;
+ mask.create(w, h, Graphics::PixelFormat::createFormatCLUT8());
+ memset(mask.getPixels(), 0, w * h);
+ font->drawString(&mask, str, 0, 0, w, 1, Graphics::kTextAlignLeft);
+
+ const byte cr = (byte)(rgba[0] * 255.0f);
+ const byte cg = (byte)(rgba[1] * 255.0f);
+ const byte cb = (byte)(rgba[2] * 255.0f);
+
+ uint32 *rgbaBuf = new uint32[w * h];
+ for (int py = 0; py < h; py++) {
+ const byte *src = (const byte *)mask.getBasePtr(0, py);
+ uint32 *dst = rgbaBuf + py * w;
+ for (int px = 0; px < w; px++) {
+ if (src[px] == 1)
+ dst[px] = ((uint32)cr) | ((uint32)cg << 8) | ((uint32)cb << 16) | (0xFFu << 24);
+ else
+ dst[px] = 0;
+ }
+ }
+ mask.free();
+
+ glBindTexture(GL_TEXTURE_2D, _bitmapTexture);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, rgbaBuf);
+ delete[] rgbaBuf;
+
+ glEnable(GL_BLEND);
+ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+ drawTexturedQuad(x, y, w, h);
+ glDisable(GL_BLEND);
+}
+
+// ---------------------------------------------------------------------------
+// Surface blit
+// ---------------------------------------------------------------------------
+
+void OpenGLShaderRenderer::drawSurface(const Graphics::Surface *surf, int x, int y) {
+ if (!surf || surf->w <= 0 || surf->h <= 0)
+ return;
+ if (surf->format.bytesPerPixel != 4)
+ return;
+
+ glBindTexture(GL_TEXTURE_2D, _bitmapTexture);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
+ // The engine's surface format is PixelFormat(4,8,8,8,8,24,16,8,0) â
+ // R at bit 24, A at bit 0 â so GL_RGBA + GL_UNSIGNED_INT_8_8_8_8 reads
+ // each uint32 with the high byte mapping to R, matching the fixed-
+ // function path's drawSurface upload (renderer_opengl.cpp:725).
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, surf->w, surf->h, 0,
+ GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, surf->getPixels());
+
+ glEnable(GL_BLEND);
+ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+ drawTexturedQuad(x, y, surf->w, surf->h);
+ glDisable(GL_BLEND);
+}
+
+void OpenGLShaderRenderer::drawTexturedQuad(int x, int y, int w, int h) {
+ const float verts[] = {
+ // position texcoord
+ (float)x, (float)y, 0.0f, 0.0f,
+ (float)(x + w), (float)y, 1.0f, 0.0f,
+ (float)x, (float)(y + h), 0.0f, 1.0f,
+ (float)(x + w), (float)(y + h), 1.0f, 1.0f
+ };
+ glBindBuffer(GL_ARRAY_BUFFER, _bitmapVBO);
+ glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(verts), verts);
+
+ _bitmapShader->use();
+ _bitmapShader->setUniform("projection", _projection);
+ _bitmapShader->setUniform("tex", 0);
+ glActiveTexture(GL_TEXTURE0);
+ glBindTexture(GL_TEXTURE_2D, _bitmapTexture);
+
+ glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
+ _bitmapShader->unbind();
+}
+
+// ---------------------------------------------------------------------------
+// Buffer / state management
+// ---------------------------------------------------------------------------
+
+void OpenGLShaderRenderer::setPalette(const byte *palette, uint start, uint count) {
+ if (start + count > 256)
+ count = 256 - start;
+ memcpy(_palette + start * 3, palette, count * 3);
+}
+
+void OpenGLShaderRenderer::computeScreenViewport() {
+ const int32 screenWidth = _system->getWidth();
+ const int32 screenHeight = _system->getHeight();
+ const bool widescreen = ConfMan.getBool("widescreen_mod");
+
+ if (widescreen) {
+ _screenViewport = Common::Rect(screenWidth, screenHeight);
+ } else if (_system->getFeatureState(OSystem::kFeatureAspectRatioCorrection)) {
+ const int32 vpW = MIN<int32>(screenWidth, screenHeight * 4 / 3);
+ const int32 vpH = MIN<int32>(screenHeight, screenWidth * 3 / 4);
+ _screenViewport = Common::Rect(vpW, vpH);
+ _screenViewport.translate((screenWidth - vpW) / 2, (screenHeight - vpH) / 2);
+ } else {
+ _screenViewport = Common::Rect(screenWidth, screenHeight);
+ }
+
+ glViewport(_screenViewport.left, screenHeight - _screenViewport.bottom,
+ _screenViewport.width(), _screenViewport.height());
+ glScissor(_screenViewport.left, screenHeight - _screenViewport.bottom,
+ _screenViewport.width(), _screenViewport.height());
+}
+
+void OpenGLShaderRenderer::copyToScreen() {
+ glFlush();
+ _system->updateScreen();
+}
+
+Graphics::Surface *OpenGLShaderRenderer::getScreenshot() {
+ Graphics::Surface *surface = new Graphics::Surface();
+ surface->create(_screenViewport.width(), _screenViewport.height(),
+ Graphics::PixelFormat::createFormatRGBA32());
+ glPixelStorei(GL_PACK_ALIGNMENT, 4);
+ glReadPixels(_screenViewport.left, _system->getHeight() - _screenViewport.bottom,
+ _screenViewport.width(), _screenViewport.height(),
+ GL_RGBA, GL_UNSIGNED_BYTE, surface->getPixels());
+ surface->flipVertical(Common::Rect(surface->w, surface->h));
+ return surface;
+}
+
+// ---------------------------------------------------------------------------
+// Factory
+// ---------------------------------------------------------------------------
+
Renderer *createOpenGLShaderRenderer(OSystem *system, int width, int height) {
return new OpenGLShaderRenderer(system, width, height);
}
diff --git a/engines/colony/shaders/colony_bitmap.fragment b/engines/colony/shaders/colony_bitmap.fragment
new file mode 100644
index 00000000000..0dad76028db
--- /dev/null
+++ b/engines/colony/shaders/colony_bitmap.fragment
@@ -0,0 +1,9 @@
+in vec2 var_texcoord;
+
+OUTPUT
+
+uniform sampler2D tex;
+
+void main() {
+ outColor = texture(tex, var_texcoord);
+}
diff --git a/engines/colony/shaders/colony_bitmap.vertex b/engines/colony/shaders/colony_bitmap.vertex
new file mode 100644
index 00000000000..01d7ea1792b
--- /dev/null
+++ b/engines/colony/shaders/colony_bitmap.vertex
@@ -0,0 +1,11 @@
+in vec2 position;
+in vec2 texcoord;
+
+uniform mat4 projection;
+
+out vec2 var_texcoord;
+
+void main() {
+ var_texcoord = texcoord;
+ gl_Position = projection * vec4(position, 0.0, 1.0);
+}
diff --git a/engines/colony/shaders/colony_solid.fragment b/engines/colony/shaders/colony_solid.fragment
new file mode 100644
index 00000000000..e51434cb4f0
--- /dev/null
+++ b/engines/colony/shaders/colony_solid.fragment
@@ -0,0 +1,7 @@
+OUTPUT
+
+uniform vec4 color;
+
+void main() {
+ outColor = color;
+}
diff --git a/engines/colony/shaders/colony_solid.vertex b/engines/colony/shaders/colony_solid.vertex
new file mode 100644
index 00000000000..5bdb7e3789c
--- /dev/null
+++ b/engines/colony/shaders/colony_solid.vertex
@@ -0,0 +1,7 @@
+in vec2 position;
+
+uniform mat4 projection;
+
+void main() {
+ gl_Position = projection * vec4(position, 0.0, 1.0);
+}
diff --git a/graphics/opengl/shader.cpp b/graphics/opengl/shader.cpp
index f625d2d4ab9..f9985d1fea6 100644
--- a/graphics/opengl/shader.cpp
+++ b/graphics/opengl/shader.cpp
@@ -94,6 +94,7 @@ static const GLchar *readFile(const Common::String &filename) {
SearchMan.addDirectory("PLAYGROUND3D_SHADERS", "engines/playground3d", 0, 2);
SearchMan.addDirectory("FREESCAPE_SHADERS", "engines/freescape", 0, 2);
SearchMan.addDirectory("HPL1_SHADERS", "engines/hpl1/engine/impl", 0, 2);
+ SearchMan.addDirectory("COLONY_SHADERS", "engines/colony", 0, 2);
#endif
if (ConfMan.hasKey("extrapath")) {
@@ -114,6 +115,7 @@ static const GLchar *readFile(const Common::String &filename) {
SearchMan.remove("PLAYGROUND3D_SHADERS");
SearchMan.remove("FREESCAPE_SHADERS");
SearchMan.remove("HPL1_SHADERS");
+ SearchMan.remove("COLONY_SHADERS");
#endif
SearchMan.remove("EXTRA_PATH");
Commit: e0b482a360dcd0cf875c5d8d3e1495e620ed09c9
https://github.com/scummvm/scummvm/commit/e0b482a360dcd0cf875c5d8d3e1495e620ed09c9
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-04-30T18:55:53+02:00
Commit Message:
COLONY: added basic shaders support for unoptimized 3d primitives
Changed paths:
A engines/colony/shaders/colony_solid_3d.vertex
engines/colony/renderer_opengl_shaders.cpp
diff --git a/engines/colony/renderer_opengl_shaders.cpp b/engines/colony/renderer_opengl_shaders.cpp
index 378c8383cf2..edb247d428a 100644
--- a/engines/colony/renderer_opengl_shaders.cpp
+++ b/engines/colony/renderer_opengl_shaders.cpp
@@ -44,9 +44,9 @@
namespace Colony {
-// Phase 2: programmable-pipeline 2D primitives. The 3D path (begin3D and
-// the corridor draws) and the deprecated state setters (XOR, polygon
-// stipple, wireframe) remain stubs and are filled in by later phases.
+// Phase 3: 2D primitives + the 3D corridor draw path are programmable.
+// XOR mode and polygon stipple are intentionally left stubbed â see the
+// renderer audit for why those are deferred.
class OpenGLShaderRenderer : public Renderer {
public:
OpenGLShaderRenderer(OSystem *system, int width, int height);
@@ -68,22 +68,21 @@ public:
void setPalette(const byte *palette, uint start, uint count) override;
- // 3D path â Phase 3.
- void begin3D(int camX, int camY, int camZ, int angle, int angleY, const Common::Rect &viewport) override {}
- void draw3DWall(int x1, int y1, int x2, int y2, uint32 color) override {}
+ void begin3D(int camX, int camY, int camZ, int angle, int angleY, const Common::Rect &viewport) override;
+ void draw3DWall(int x1, int y1, int x2, int y2, uint32 color) override;
void draw3DQuad(float x1, float y1, float z1, float x2, float y2, float z2,
- float x3, float y3, float z3, float x4, float y4, float z4, uint32 color) override {}
- void draw3DPolygon(const float *x, const float *y, const float *z, int count, uint32 color) override {}
- void draw3DLine(float x1, float y1, float z1, float x2, float y2, float z2, uint32 color) override {}
- void end3D() override {}
+ float x3, float y3, float z3, float x4, float y4, float z4, uint32 color) override;
+ void draw3DPolygon(const float *x, const float *y, const float *z, int count, uint32 color) override;
+ void draw3DLine(float x1, float y1, float z1, float x2, float y2, float z2, uint32 color) override;
+ void end3D() override;
void copyToScreen() override;
- void setWireframe(bool enable, int64_t fillColor) override {}
+ void setWireframe(bool enable, int64_t fillColor) override;
void setXorMode(bool enable) override {}
void setStippleData(const byte *data) override {}
void setMacColors(uint32 fg, uint32 bg) override {}
- void setDepthState(bool testEnabled, bool writeEnabled) override {}
- void setDepthRange(float nearVal, float farVal) override {}
+ void setDepthState(bool testEnabled, bool writeEnabled) override;
+ void setDepthRange(float nearVal, float farVal) override;
void computeScreenViewport() override;
void drawSurface(const Graphics::Surface *surf, int x, int y) override;
@@ -92,10 +91,20 @@ public:
private:
void resolveColor(uint32 color, float rgba[4]) const;
void rebuildProjection();
+ // Push _projection to the 2D shaders. The matrix is constant between
+ // resolution changes, so this only needs to run from the constructor
+ // and computeScreenViewport â not per draw.
+ void uploadProjectionUniform();
void uploadSolid(const float *positions, int vertCount);
void drawSolid(GLenum mode, const float *positions, int vertCount, const float rgba[4]);
void drawTexturedQuad(int x, int y, int w, int h);
+ void uploadSolid3D(const float *positions, int vertCount);
+ void drawSolid3D(GLenum mode, const float *positions, int vertCount, const float rgba[4]);
+ // Renders a filled+wireframe 3D primitive. Honors _wireframe and
+ // _wireframeFillColor exactly like OpenGLRenderer::draw3DWall/Quad/Polygon.
+ void drawWireframeable3D(const float *positions, int vertCount, uint32 color);
+
OSystem *_system = nullptr;
int _width = 0;
int _height = 0;
@@ -104,16 +113,25 @@ private:
OpenGL::Shader *_solidShader = nullptr;
OpenGL::Shader *_bitmapShader = nullptr;
+ OpenGL::Shader *_solid3dShader = nullptr;
GLuint _solidVBO = 0;
GLuint _bitmapVBO = 0;
+ GLuint _solid3dVBO = 0;
GLuint _bitmapTexture = 0;
Math::Matrix4 _projection;
+ Math::Matrix4 _mvpMatrix;
+
+ bool _wireframe = true;
+ int64_t _wireframeFillColor = 0; // -1 = no fill, else color (palette idx or ARGB)
// Solid VBO holds vec2 position only. Sized for the worst-case 2D
// primitive â the dither overlay can stream up to width*height/2 dots,
// so leave room for typical screens (â170k floats for an 800Ã600 split).
enum { kSolidVertexCapacity = 320 * 1024 };
+ // 3D VBO holds vec3 positions. Corridor polygons are typically <16
+ // vertices but a few features (sprite billboards, stairs) push higher.
+ enum { kSolid3DVertexCapacity = 1024 };
};
// ---------------------------------------------------------------------------
@@ -122,8 +140,7 @@ private:
OpenGLShaderRenderer::OpenGLShaderRenderer(OSystem *system, int width, int height)
: _system(system), _width(width), _height(height) {
- debug(1, "Colony: using OpenGL shader renderer (Phase 2: 2D primitives "
- "functional; corridor 3D view is stubbed until Phase 3)");
+ debug(1, "Colony: using OpenGL shader renderer");
for (int i = 0; i < 256 * 3; i++)
_palette[i] = 255;
@@ -144,6 +161,15 @@ OpenGLShaderRenderer::OpenGLShaderRenderer(OSystem *system, int width, int heigh
_bitmapShader->enableVertexAttribute("texcoord", _bitmapVBO, 2, GL_FLOAT, GL_FALSE,
4 * sizeof(float), 2 * sizeof(float));
+ // 3D solid: shares colony_solid.fragment (uniform color â outColor) with
+ // the 2D path, but uses a vec3 vertex shader that consumes mvpMatrix.
+ static const char *solid3dAttribs[] = { "position", nullptr };
+ _solid3dShader = OpenGL::Shader::fromFiles("colony_solid_3d", "colony_solid", solid3dAttribs);
+ _solid3dVBO = OpenGL::Shader::createBuffer(GL_ARRAY_BUFFER,
+ sizeof(float) * 3 * kSolid3DVertexCapacity, nullptr, GL_DYNAMIC_DRAW);
+ _solid3dShader->enableVertexAttribute("position", _solid3dVBO, 3, GL_FLOAT, GL_FALSE,
+ 3 * sizeof(float), 0);
+
glGenTextures(1, &_bitmapTexture);
glDisable(GL_DEPTH_TEST);
@@ -152,6 +178,12 @@ OpenGLShaderRenderer::OpenGLShaderRenderer(OSystem *system, int width, int heigh
computeScreenViewport();
rebuildProjection();
+ uploadProjectionUniform();
+
+ // The bitmap shader's sampler is permanently bound to texture unit 0.
+ // Setting it once here removes a per-draw setUniform("tex", 0).
+ _bitmapShader->use();
+ _bitmapShader->setUniform("tex", 0);
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
@@ -160,8 +192,10 @@ OpenGLShaderRenderer::OpenGLShaderRenderer(OSystem *system, int width, int heigh
OpenGLShaderRenderer::~OpenGLShaderRenderer() {
OpenGL::Shader::freeBuffer(_solidVBO);
OpenGL::Shader::freeBuffer(_bitmapVBO);
+ OpenGL::Shader::freeBuffer(_solid3dVBO);
delete _solidShader;
delete _bitmapShader;
+ delete _solid3dShader;
if (_bitmapTexture)
glDeleteTextures(1, &_bitmapTexture);
}
@@ -204,6 +238,20 @@ void OpenGLShaderRenderer::rebuildProjection() {
_projection = m;
}
+void OpenGLShaderRenderer::uploadProjectionUniform() {
+ // Push the current ortho matrix to both 2D shaders. The values persist
+ // in each program object until the next setUniform, so subsequent draws
+ // don't need to re-upload it. Color stays per-draw because it varies.
+ if (_solidShader) {
+ _solidShader->use();
+ _solidShader->setUniform("projection", _projection);
+ }
+ if (_bitmapShader) {
+ _bitmapShader->use();
+ _bitmapShader->setUniform("projection", _projection);
+ }
+}
+
// ---------------------------------------------------------------------------
// Solid-color primitives
// ---------------------------------------------------------------------------
@@ -218,11 +266,13 @@ void OpenGLShaderRenderer::drawSolid(GLenum mode, const float *positions, int ve
if (vertCount <= 0)
return;
uploadSolid(positions, vertCount);
+ // "projection" is set once by uploadProjectionUniform() â it persists in
+ // the program object across draws. Only "color" varies per call.
+ // Shader stays bound between draws so Shader::use()'s _previousShader
+ // cache (graphics/opengl/shader.cpp:378-380) skips re-binding.
_solidShader->use();
- _solidShader->setUniform("projection", _projection);
_solidShader->setUniform("color", Math::Vector4d(rgba[0], rgba[1], rgba[2], rgba[3]));
glDrawArrays(mode, 0, vertCount);
- _solidShader->unbind();
}
void OpenGLShaderRenderer::clear(uint32 color) {
@@ -466,14 +516,14 @@ void OpenGLShaderRenderer::drawTexturedQuad(int x, int y, int w, int h) {
glBindBuffer(GL_ARRAY_BUFFER, _bitmapVBO);
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(verts), verts);
+ // "projection" and "tex" are set once at init / on resolution change;
+ // they persist in the program object so we don't re-upload here.
_bitmapShader->use();
- _bitmapShader->setUniform("projection", _projection);
- _bitmapShader->setUniform("tex", 0);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, _bitmapTexture);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
- _bitmapShader->unbind();
+ // Shader stays bound â see drawSolid for the rationale.
}
// ---------------------------------------------------------------------------
@@ -525,6 +575,211 @@ Graphics::Surface *OpenGLShaderRenderer::getScreenshot() {
return surface;
}
+// ---------------------------------------------------------------------------
+// 3D corridor / scene rendering
+// ---------------------------------------------------------------------------
+
+void OpenGLShaderRenderer::setWireframe(bool enable, int64_t fillColor) {
+ _wireframe = enable;
+ _wireframeFillColor = fillColor;
+}
+
+void OpenGLShaderRenderer::setDepthState(bool testEnabled, bool writeEnabled) {
+ if (testEnabled)
+ glEnable(GL_DEPTH_TEST);
+ else
+ glDisable(GL_DEPTH_TEST);
+ glDepthMask(writeEnabled ? GL_TRUE : GL_FALSE);
+}
+
+void OpenGLShaderRenderer::setDepthRange(float nearVal, float farVal) {
+ glDepthRange(nearVal, farVal);
+}
+
+void OpenGLShaderRenderer::begin3D(int camX, int camY, int camZ, int angle, int angleY,
+ const Common::Rect &viewport) {
+ glEnable(GL_DEPTH_TEST);
+ glClear(GL_DEPTH_BUFFER_BIT);
+
+ // Map the engine's logical viewport into system pixels (matches
+ // OpenGLRenderer::begin3D in renderer_opengl.cpp:246-257).
+ const float scaleX = (float)_screenViewport.width() / (float)_width;
+ const float scaleY = (float)_screenViewport.height() / (float)_height;
+ const int sysH = _system->getHeight();
+ const int vpX = _screenViewport.left + (int)(viewport.left * scaleX);
+ const int vpY = sysH - (_screenViewport.top + (int)(viewport.bottom * scaleY));
+ const int vpW = (int)(viewport.width() * scaleX);
+ const int vpH = (int)(viewport.height() * scaleY);
+ glViewport(vpX, vpY, vpW, vpH);
+ glScissor(vpX, vpY, vpW, vpH);
+ glEnable(GL_SCISSOR_TEST);
+ glDepthFunc(GL_LEQUAL);
+ glDepthMask(GL_TRUE);
+
+ // Perspective: 75° vertical FOV, near=1, far=10000 â matches the
+ // fixed-function path so geometry lands at the same screen positions.
+ const float aspectRatio = (float)viewport.width() / (float)viewport.height();
+ const float fov = 75.0f;
+ const float nearClip = 1.0f;
+ const float farClip = 10000.0f;
+ const float ymax = nearClip * tanf(fov * (float)M_PI / 360.0f);
+ const float xmax = ymax * aspectRatio;
+
+ // Build perspective frustum directly. Math::makeFrustumMatrix exists in
+ // math/glmath.h, but its sign convention requires a final transpose to
+ // match Math::Matrix4's row-major storage; constructing the matrix
+ // element-by-element here is clearer and easier to audit.
+ Math::Matrix4 proj;
+ for (int r = 0; r < 4; r++)
+ for (int c = 0; c < 4; c++)
+ proj(r, c) = 0.0f;
+ proj(0, 0) = 2.0f * nearClip / (xmax - (-xmax));
+ proj(1, 1) = 2.0f * nearClip / (ymax - (-ymax));
+ proj(0, 2) = (xmax + (-xmax)) / (xmax - (-xmax));
+ proj(1, 2) = (ymax + (-ymax)) / (ymax - (-ymax));
+ proj(2, 2) = -(farClip + nearClip) / (farClip - nearClip);
+ proj(2, 3) = -2.0f * farClip * nearClip / (farClip - nearClip);
+ proj(3, 2) = -1.0f;
+
+ // View transform: replicate the fixed-function chain
+ // Rx(pitch) * Rx(-90) * Rz(yaw) * T(-cam)
+ // applied to column vectors (renderer_opengl.cpp:280-292).
+ Math::Matrix4 pitch;
+ pitch.buildAroundX((float)angleY * 360.0f / 256.0f);
+ Math::Matrix4 minus90;
+ minus90.buildAroundX(-90.0f);
+ Math::Matrix4 yaw;
+ yaw.buildAroundZ(-(float)angle * 360.0f / 256.0f + 90.0f);
+ Math::Matrix4 trans; // identity
+ trans.setPosition(Math::Vector3d((float)-camX, (float)-camY, (float)-camZ));
+
+ const Math::Matrix4 view = pitch * minus90 * yaw * trans;
+ Math::Matrix4 mvp = proj * view;
+ mvp.transpose(); // Math::Matrix4 is row-major; setUniform expects column-major.
+ _mvpMatrix = mvp;
+
+ // Push mvpMatrix to the 3D shader once per frame (here in begin3D),
+ // instead of on every primitive â it doesn't change between draws.
+ _solid3dShader->use();
+ _solid3dShader->setUniform("mvpMatrix", _mvpMatrix);
+}
+
+void OpenGLShaderRenderer::end3D() {
+ glDisable(GL_DEPTH_TEST);
+ glDepthMask(GL_TRUE);
+ glDepthRange(0.0, 1.0);
+ glDisable(GL_SCISSOR_TEST);
+
+ // Restore the 2D viewport so subsequent overlay draws (dashboard, menu,
+ // crosshair, automap) land in the right spot.
+ const int sysW = _system->getWidth();
+ const int sysH = _system->getHeight();
+ glViewport(0, 0, sysW, sysH);
+ glScissor(0, 0, sysW, sysH);
+ computeScreenViewport();
+}
+
+void OpenGLShaderRenderer::uploadSolid3D(const float *positions, int vertCount) {
+ glBindBuffer(GL_ARRAY_BUFFER, _solid3dVBO);
+ glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(float) * 3 * vertCount, positions);
+}
+
+void OpenGLShaderRenderer::drawSolid3D(GLenum mode, const float *positions, int vertCount,
+ const float rgba[4]) {
+ if (vertCount <= 0)
+ return;
+ uploadSolid3D(positions, vertCount);
+ // "mvpMatrix" is set in begin3D and persists in the program object â
+ // only "color" varies per draw. Same shader-bind caching as drawSolid.
+ _solid3dShader->use();
+ _solid3dShader->setUniform("color", Math::Vector4d(rgba[0], rgba[1], rgba[2], rgba[3]));
+ glDrawArrays(mode, 0, vertCount);
+}
+
+void OpenGLShaderRenderer::drawWireframeable3D(const float *positions, int vertCount,
+ uint32 color) {
+ if (vertCount < 3) {
+ // Degenerate â render the line directly so callers don't have to
+ // special-case 2-vertex inputs.
+ float rgba[4];
+ resolveColor(color, rgba);
+ drawSolid3D(GL_LINE_STRIP, positions, vertCount, rgba);
+ return;
+ }
+
+ if (_wireframe) {
+ if (_wireframeFillColor != -1) {
+ glEnable(GL_POLYGON_OFFSET_FILL);
+ glPolygonOffset(1.1f, 4.0f);
+ float fillRgba[4];
+ resolveColor((uint32)_wireframeFillColor, fillRgba);
+ drawSolid3D(GL_TRIANGLE_FAN, positions, vertCount, fillRgba);
+ glDisable(GL_POLYGON_OFFSET_FILL);
+ }
+ float edgeRgba[4];
+ resolveColor(color, edgeRgba);
+ drawSolid3D(GL_LINE_LOOP, positions, vertCount, edgeRgba);
+ } else {
+ glEnable(GL_POLYGON_OFFSET_FILL);
+ glPolygonOffset(1.1f, 4.0f);
+ float rgba[4];
+ resolveColor(color, rgba);
+ drawSolid3D(GL_TRIANGLE_FAN, positions, vertCount, rgba);
+ glDisable(GL_POLYGON_OFFSET_FILL);
+ }
+}
+
+void OpenGLShaderRenderer::draw3DWall(int x1, int y1, int x2, int y2, uint32 color) {
+ // 256à scale and ±160 height match renderer_opengl.cpp:295-298.
+ const float fx1 = x1 * 256.0f, fy1 = y1 * 256.0f;
+ const float fx2 = x2 * 256.0f, fy2 = y2 * 256.0f;
+ const float verts[12] = {
+ fx1, fy1, -160.0f,
+ fx2, fy2, -160.0f,
+ fx2, fy2, 160.0f,
+ fx1, fy1, 160.0f,
+ };
+ drawWireframeable3D(verts, 4, color);
+}
+
+void OpenGLShaderRenderer::draw3DQuad(float x1, float y1, float z1, float x2, float y2, float z2,
+ float x3, float y3, float z3, float x4, float y4, float z4, uint32 color) {
+ const float verts[12] = {
+ x1, y1, z1,
+ x2, y2, z2,
+ x3, y3, z3,
+ x4, y4, z4,
+ };
+ drawWireframeable3D(verts, 4, color);
+}
+
+void OpenGLShaderRenderer::draw3DPolygon(const float *x, const float *y, const float *z,
+ int count, uint32 color) {
+ if (count < 3)
+ return;
+ if (count > kSolid3DVertexCapacity)
+ count = kSolid3DVertexCapacity;
+
+ float stack[3 * 32];
+ float *verts = (count <= 32) ? stack : new float[3 * count];
+ for (int i = 0; i < count; i++) {
+ verts[i * 3 + 0] = x[i];
+ verts[i * 3 + 1] = y[i];
+ verts[i * 3 + 2] = z[i];
+ }
+ drawWireframeable3D(verts, count, color);
+ if (verts != stack)
+ delete[] verts;
+}
+
+void OpenGLShaderRenderer::draw3DLine(float x1, float y1, float z1, float x2, float y2, float z2,
+ uint32 color) {
+ const float verts[6] = { x1, y1, z1, x2, y2, z2 };
+ float rgba[4];
+ resolveColor(color, rgba);
+ drawSolid3D(GL_LINES, verts, 2, rgba);
+}
+
// ---------------------------------------------------------------------------
// Factory
// ---------------------------------------------------------------------------
diff --git a/engines/colony/shaders/colony_solid_3d.vertex b/engines/colony/shaders/colony_solid_3d.vertex
new file mode 100644
index 00000000000..4826ea4c434
--- /dev/null
+++ b/engines/colony/shaders/colony_solid_3d.vertex
@@ -0,0 +1,7 @@
+in vec3 position;
+
+uniform mat4 mvpMatrix;
+
+void main() {
+ gl_Position = mvpMatrix * vec4(position, 1.0);
+}
Commit: 47442da883f1f6b242f4cd26f7f9a0b94a7d3807
https://github.com/scummvm/scummvm/commit/47442da883f1f6b242f4cd26f7f9a0b94a7d3807
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-04-30T18:55:53+02:00
Commit Message:
COLONY: optimize shader 3d primitives code
Changed paths:
engines/colony/renderer_opengl_shaders.cpp
diff --git a/engines/colony/renderer_opengl_shaders.cpp b/engines/colony/renderer_opengl_shaders.cpp
index edb247d428a..dc2696745b2 100644
--- a/engines/colony/renderer_opengl_shaders.cpp
+++ b/engines/colony/renderer_opengl_shaders.cpp
@@ -122,6 +122,14 @@ private:
Math::Matrix4 _projection;
Math::Matrix4 _mvpMatrix;
+ // Cached "color" uniform values per shader. Uniforms persist with the
+ // program object across glUseProgram cycles, so once we've uploaded a
+ // color, subsequent draws can skip glUniform4fv when the color matches.
+ // Adjacent walls / dashboard primitives commonly share colors, so this
+ // elides a lot of uniform writes per frame.
+ float _solidLastColor[4] = { -1.0f, -1.0f, -1.0f, -1.0f };
+ float _solid3dLastColor[4] = { -1.0f, -1.0f, -1.0f, -1.0f };
+
bool _wireframe = true;
int64_t _wireframeFillColor = 0; // -1 = no fill, else color (palette idx or ARGB)
@@ -171,6 +179,15 @@ OpenGLShaderRenderer::OpenGLShaderRenderer(OSystem *system, int width, int heigh
3 * sizeof(float), 0);
glGenTextures(1, &_bitmapTexture);
+ // Texture parameters are per-texture-object state, so they only need to
+ // be set once at creation; they persist across glTexImage2D re-uploads.
+ // All blits (drawSurface, drawString) reuse this single texture handle
+ // and share the same NEAREST filter + CLAMP_TO_EDGE wrap.
+ glBindTexture(GL_TEXTURE_2D, _bitmapTexture);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glDisable(GL_DEPTH_TEST);
glDisable(GL_CULL_FACE);
@@ -257,8 +274,13 @@ void OpenGLShaderRenderer::uploadProjectionUniform() {
// ---------------------------------------------------------------------------
void OpenGLShaderRenderer::uploadSolid(const float *positions, int vertCount) {
+ // glBufferData (orphan) instead of glBufferSubData: tells the driver
+ // the previous contents are dead, so it can hand us fresh storage
+ // without waiting for the GPU to finish reading the old data. This
+ // matches Freescape's per-draw pattern (gfx_opengl_shaders.cpp:695,
+ // 717) and avoids implicit CPU/GPU sync stalls on Mac drivers.
glBindBuffer(GL_ARRAY_BUFFER, _solidVBO);
- glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(float) * 2 * vertCount, positions);
+ glBufferData(GL_ARRAY_BUFFER, sizeof(float) * 2 * vertCount, positions, GL_DYNAMIC_DRAW);
}
void OpenGLShaderRenderer::drawSolid(GLenum mode, const float *positions, int vertCount,
@@ -267,11 +289,16 @@ void OpenGLShaderRenderer::drawSolid(GLenum mode, const float *positions, int ve
return;
uploadSolid(positions, vertCount);
// "projection" is set once by uploadProjectionUniform() â it persists in
- // the program object across draws. Only "color" varies per call.
- // Shader stays bound between draws so Shader::use()'s _previousShader
- // cache (graphics/opengl/shader.cpp:378-380) skips re-binding.
+ // the program object across draws. Shader stays bound between draws so
+ // Shader::use()'s _previousShader cache short-circuits the rebind.
_solidShader->use();
- _solidShader->setUniform("color", Math::Vector4d(rgba[0], rgba[1], rgba[2], rgba[3]));
+ if (rgba[0] != _solidLastColor[0] || rgba[1] != _solidLastColor[1]
+ || rgba[2] != _solidLastColor[2] || rgba[3] != _solidLastColor[3]) {
+ _solidShader->setUniform("color",
+ Math::Vector4d(rgba[0], rgba[1], rgba[2], rgba[3]));
+ _solidLastColor[0] = rgba[0]; _solidLastColor[1] = rgba[1];
+ _solidLastColor[2] = rgba[2]; _solidLastColor[3] = rgba[3];
+ }
glDrawArrays(mode, 0, vertCount);
}
@@ -461,11 +488,9 @@ void OpenGLShaderRenderer::drawString(const Graphics::Font *font, const Common::
}
mask.free();
+ // Texture params are set once at construction. The shared _bitmapTexture
+ // keeps NEAREST filter + CLAMP_TO_EDGE for both drawString and drawSurface.
glBindTexture(GL_TEXTURE_2D, _bitmapTexture);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, rgbaBuf);
delete[] rgbaBuf;
@@ -486,11 +511,8 @@ void OpenGLShaderRenderer::drawSurface(const Graphics::Surface *surf, int x, int
if (surf->format.bytesPerPixel != 4)
return;
+ // Texture params are set once at construction; just bind here.
glBindTexture(GL_TEXTURE_2D, _bitmapTexture);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
// The engine's surface format is PixelFormat(4,8,8,8,8,24,16,8,0) â
// R at bit 24, A at bit 0 â so GL_RGBA + GL_UNSIGNED_INT_8_8_8_8 reads
@@ -513,8 +535,9 @@ void OpenGLShaderRenderer::drawTexturedQuad(int x, int y, int w, int h) {
(float)x, (float)(y + h), 0.0f, 1.0f,
(float)(x + w), (float)(y + h), 1.0f, 1.0f
};
+ // Orphan + write fresh â see uploadSolid.
glBindBuffer(GL_ARRAY_BUFFER, _bitmapVBO);
- glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(verts), verts);
+ glBufferData(GL_ARRAY_BUFFER, sizeof(verts), verts, GL_DYNAMIC_DRAW);
// "projection" and "tex" are set once at init / on resolution change;
// they persist in the program object so we don't re-upload here.
@@ -680,8 +703,9 @@ void OpenGLShaderRenderer::end3D() {
}
void OpenGLShaderRenderer::uploadSolid3D(const float *positions, int vertCount) {
+ // See uploadSolid for the orphan-via-glBufferData rationale.
glBindBuffer(GL_ARRAY_BUFFER, _solid3dVBO);
- glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(float) * 3 * vertCount, positions);
+ glBufferData(GL_ARRAY_BUFFER, sizeof(float) * 3 * vertCount, positions, GL_DYNAMIC_DRAW);
}
void OpenGLShaderRenderer::drawSolid3D(GLenum mode, const float *positions, int vertCount,
@@ -689,10 +713,17 @@ void OpenGLShaderRenderer::drawSolid3D(GLenum mode, const float *positions, int
if (vertCount <= 0)
return;
uploadSolid3D(positions, vertCount);
- // "mvpMatrix" is set in begin3D and persists in the program object â
- // only "color" varies per draw. Same shader-bind caching as drawSolid.
+ // "mvpMatrix" is set in begin3D and persists in the program object.
+ // Skip the color upload when it matches the previous draw â adjacent
+ // corridor walls/quads commonly share a color.
_solid3dShader->use();
- _solid3dShader->setUniform("color", Math::Vector4d(rgba[0], rgba[1], rgba[2], rgba[3]));
+ if (rgba[0] != _solid3dLastColor[0] || rgba[1] != _solid3dLastColor[1]
+ || rgba[2] != _solid3dLastColor[2] || rgba[3] != _solid3dLastColor[3]) {
+ _solid3dShader->setUniform("color",
+ Math::Vector4d(rgba[0], rgba[1], rgba[2], rgba[3]));
+ _solid3dLastColor[0] = rgba[0]; _solid3dLastColor[1] = rgba[1];
+ _solid3dLastColor[2] = rgba[2]; _solid3dLastColor[3] = rgba[3];
+ }
glDrawArrays(mode, 0, vertCount);
}
Commit: 22276d5926b57201928f086f2e80c7dd5c9ebc11
https://github.com/scummvm/scummvm/commit/22276d5926b57201928f086f2e80c7dd5c9ebc11
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-04-30T18:55:53+02:00
Commit Message:
COLONY: support for stipples in shader renderer
Changed paths:
A engines/colony/shaders/colony_solid_3d.fragment
engines/colony/renderer_opengl_shaders.cpp
diff --git a/engines/colony/renderer_opengl_shaders.cpp b/engines/colony/renderer_opengl_shaders.cpp
index dc2696745b2..4db4864e2b5 100644
--- a/engines/colony/renderer_opengl_shaders.cpp
+++ b/engines/colony/renderer_opengl_shaders.cpp
@@ -79,8 +79,8 @@ public:
void copyToScreen() override;
void setWireframe(bool enable, int64_t fillColor) override;
void setXorMode(bool enable) override {}
- void setStippleData(const byte *data) override {}
- void setMacColors(uint32 fg, uint32 bg) override {}
+ void setStippleData(const byte *data) override;
+ void setMacColors(uint32 fg, uint32 bg) override;
void setDepthState(bool testEnabled, bool writeEnabled) override;
void setDepthRange(float nearVal, float farVal) override;
void computeScreenViewport() override;
@@ -100,9 +100,14 @@ private:
void drawTexturedQuad(int x, int y, int w, int h);
void uploadSolid3D(const float *positions, int vertCount);
- void drawSolid3D(GLenum mode, const float *positions, int vertCount, const float rgba[4]);
- // Renders a filled+wireframe 3D primitive. Honors _wireframe and
- // _wireframeFillColor exactly like OpenGLRenderer::draw3DWall/Quad/Polygon.
+ // allowStipple=true on the fill pass picks up _stippleActive; lines
+ // must always render unstippled (matches the fixed-function path,
+ // which only stipples GL_QUADS / GL_POLYGON, never lines).
+ void drawSolid3D(GLenum mode, const float *positions, int vertCount,
+ const float rgba[4], bool allowStipple = false);
+ // Renders a filled+wireframe 3D primitive. Honors _wireframe,
+ // _wireframeFillColor, and _stippleActive â same semantics as
+ // OpenGLRenderer::draw3DWall/Quad/Polygon.
void drawWireframeable3D(const float *positions, int vertCount, uint32 color);
OSystem *_system = nullptr;
@@ -133,6 +138,29 @@ private:
bool _wireframe = true;
int64_t _wireframeFillColor = 0; // -1 = no fill, else color (palette idx or ARGB)
+ // Stipple state. The shader emulates glPolygonStipple via a 128-int
+ // uniform array (Freescape pattern, GLES2-safe). _stippleActive is
+ // true when setStippleData() received a non-null pattern. fg/bg are
+ // the engine's encoded colors (high byte 0xFF = direct ARGB, else
+ // palette index); they are resolved to vec4 before upload.
+ //
+ // Defaults match OpenGLRenderer (fg = palette index 0 = black,
+ // bg = palette index 255 = white). B&W Mac mode never calls
+ // setMacColors and relies on these defaults.
+ int _stippleShaderArray[128] = {};
+ bool _stippleActive = false;
+ uint32 _stippleFg = 0;
+ uint32 _stippleBg = 255;
+ // Per-shader dirty flags. Track what the program object currently has
+ // so we can skip redundant uniform uploads (same idea as the color
+ // cache). _solid3dStippleEnabled = the value of "useStipple" most
+ // recently uploaded to _solid3dShader. _stippleColorsDirty starts true
+ // so the first stipple draw uploads the resolved colors even if the
+ // engine never calls setMacColors (the B&W Mac path).
+ bool _solid3dStippleEnabled = false;
+ bool _stipplePatternDirty = false;
+ bool _stippleColorsDirty = true;
+
// Solid VBO holds vec2 position only. Sized for the worst-case 2D
// primitive â the dither overlay can stream up to width*height/2 dots,
// so leave room for typical screens (â170k floats for an 800Ã600 split).
@@ -169,14 +197,19 @@ OpenGLShaderRenderer::OpenGLShaderRenderer(OSystem *system, int width, int heigh
_bitmapShader->enableVertexAttribute("texcoord", _bitmapVBO, 2, GL_FLOAT, GL_FALSE,
4 * sizeof(float), 2 * sizeof(float));
- // 3D solid: shares colony_solid.fragment (uniform color â outColor) with
- // the 2D path, but uses a vec3 vertex shader that consumes mvpMatrix.
+ // 3D solid: vec3 vertex consuming mvpMatrix; the fragment shader has
+ // its own stipple-emulation branch (Freescape pattern, GLES2 safe),
+ // so we use a dedicated colony_solid_3d.{vertex,fragment} pair.
static const char *solid3dAttribs[] = { "position", nullptr };
- _solid3dShader = OpenGL::Shader::fromFiles("colony_solid_3d", "colony_solid", solid3dAttribs);
+ _solid3dShader = OpenGL::Shader::fromFiles("colony_solid_3d", solid3dAttribs);
_solid3dVBO = OpenGL::Shader::createBuffer(GL_ARRAY_BUFFER,
sizeof(float) * 3 * kSolid3DVertexCapacity, nullptr, GL_DYNAMIC_DRAW);
_solid3dShader->enableVertexAttribute("position", _solid3dVBO, 3, GL_FLOAT, GL_FALSE,
3 * sizeof(float), 0);
+ // Initial stipple state: disabled. The fragment shader takes the
+ // non-stippled path until setStippleData(non-null) marks otherwise.
+ _solid3dShader->use();
+ _solid3dShader->setUniform("useStipple", 0u);
glGenTextures(1, &_bitmapTexture);
// Texture parameters are per-texture-object state, so they only need to
@@ -607,6 +640,26 @@ void OpenGLShaderRenderer::setWireframe(bool enable, int64_t fillColor) {
_wireframeFillColor = fillColor;
}
+void OpenGLShaderRenderer::setStippleData(const byte *data) {
+ const bool nowActive = (data != nullptr);
+ if (nowActive) {
+ // Widen the 128 pattern bytes into ints for the uniform array.
+ // Mark the pattern dirty so the next 3D draw uploads it.
+ for (int i = 0; i < 128; i++)
+ _stippleShaderArray[i] = data[i];
+ _stipplePatternDirty = true;
+ }
+ _stippleActive = nowActive;
+}
+
+void OpenGLShaderRenderer::setMacColors(uint32 fg, uint32 bg) {
+ if (_stippleFg != fg || _stippleBg != bg) {
+ _stippleFg = fg;
+ _stippleBg = bg;
+ _stippleColorsDirty = true;
+ }
+}
+
void OpenGLShaderRenderer::setDepthState(bool testEnabled, bool writeEnabled) {
if (testEnabled)
glEnable(GL_DEPTH_TEST);
@@ -709,20 +762,51 @@ void OpenGLShaderRenderer::uploadSolid3D(const float *positions, int vertCount)
}
void OpenGLShaderRenderer::drawSolid3D(GLenum mode, const float *positions, int vertCount,
- const float rgba[4]) {
+ const float rgba[4], bool allowStipple) {
if (vertCount <= 0)
return;
uploadSolid3D(positions, vertCount);
// "mvpMatrix" is set in begin3D and persists in the program object.
- // Skip the color upload when it matches the previous draw â adjacent
- // corridor walls/quads commonly share a color.
_solid3dShader->use();
- if (rgba[0] != _solid3dLastColor[0] || rgba[1] != _solid3dLastColor[1]
- || rgba[2] != _solid3dLastColor[2] || rgba[3] != _solid3dLastColor[3]) {
- _solid3dShader->setUniform("color",
- Math::Vector4d(rgba[0], rgba[1], rgba[2], rgba[3]));
- _solid3dLastColor[0] = rgba[0]; _solid3dLastColor[1] = rgba[1];
- _solid3dLastColor[2] = rgba[2]; _solid3dLastColor[3] = rgba[3];
+
+ // Toggle the shader's stipple branch only when the effective state
+ // changes. allowStipple=false short-circuits stipple for line passes.
+ const bool wantStipple = allowStipple && _stippleActive;
+ if (wantStipple != _solid3dStippleEnabled) {
+ _solid3dShader->setUniform("useStipple", wantStipple ? 1u : 0u);
+ _solid3dStippleEnabled = wantStipple;
+ }
+
+ if (wantStipple) {
+ // Pattern + fg/bg colors are uploaded only when they change.
+ if (_stipplePatternDirty) {
+ _solid3dShader->setUniform("stipple", 128, _stippleShaderArray);
+ _stipplePatternDirty = false;
+ }
+ if (_stippleColorsDirty) {
+ float fgRgba[4], bgRgba[4];
+ resolveColor(_stippleFg, fgRgba);
+ resolveColor(_stippleBg, bgRgba);
+ _solid3dShader->setUniform("stippleFg",
+ Math::Vector4d(fgRgba[0], fgRgba[1], fgRgba[2], fgRgba[3]));
+ _solid3dShader->setUniform("stippleBg",
+ Math::Vector4d(bgRgba[0], bgRgba[1], bgRgba[2], bgRgba[3]));
+ _stippleColorsDirty = false;
+ }
+ // "color" uniform isn't sampled when useStipple is set â skip the
+ // upload entirely. Invalidate the cache so a later non-stipple
+ // draw with the same rgba still uploads.
+ _solid3dLastColor[0] = -1.0f;
+ } else {
+ // Skip the color upload when it matches the previous draw â
+ // adjacent corridor walls/quads commonly share a color.
+ if (rgba[0] != _solid3dLastColor[0] || rgba[1] != _solid3dLastColor[1]
+ || rgba[2] != _solid3dLastColor[2] || rgba[3] != _solid3dLastColor[3]) {
+ _solid3dShader->setUniform("color",
+ Math::Vector4d(rgba[0], rgba[1], rgba[2], rgba[3]));
+ _solid3dLastColor[0] = rgba[0]; _solid3dLastColor[1] = rgba[1];
+ _solid3dLastColor[2] = rgba[2]; _solid3dLastColor[3] = rgba[3];
+ }
}
glDrawArrays(mode, 0, vertCount);
}
@@ -739,23 +823,27 @@ void OpenGLShaderRenderer::drawWireframeable3D(const float *positions, int vertC
}
if (_wireframe) {
- if (_wireframeFillColor != -1) {
+ // Fill pass: stipple beats wireframeFillColor when active. Pass
+ // allowStipple=true so drawSolid3D picks up _stippleActive and
+ // uses the fg/bg uniforms; the fill color is ignored in that case.
+ if (_stippleActive || _wireframeFillColor != -1) {
glEnable(GL_POLYGON_OFFSET_FILL);
glPolygonOffset(1.1f, 4.0f);
float fillRgba[4];
- resolveColor((uint32)_wireframeFillColor, fillRgba);
- drawSolid3D(GL_TRIANGLE_FAN, positions, vertCount, fillRgba);
+ resolveColor(_stippleActive ? 0u : (uint32)_wireframeFillColor, fillRgba);
+ drawSolid3D(GL_TRIANGLE_FAN, positions, vertCount, fillRgba, true);
glDisable(GL_POLYGON_OFFSET_FILL);
}
+ // Edges: lines are never stippled in the fixed-function path.
float edgeRgba[4];
resolveColor(color, edgeRgba);
- drawSolid3D(GL_LINE_LOOP, positions, vertCount, edgeRgba);
+ drawSolid3D(GL_LINE_LOOP, positions, vertCount, edgeRgba, false);
} else {
glEnable(GL_POLYGON_OFFSET_FILL);
glPolygonOffset(1.1f, 4.0f);
float rgba[4];
resolveColor(color, rgba);
- drawSolid3D(GL_TRIANGLE_FAN, positions, vertCount, rgba);
+ drawSolid3D(GL_TRIANGLE_FAN, positions, vertCount, rgba, true);
glDisable(GL_POLYGON_OFFSET_FILL);
}
}
diff --git a/engines/colony/shaders/colony_solid_3d.fragment b/engines/colony/shaders/colony_solid_3d.fragment
new file mode 100644
index 00000000000..0bedb43516d
--- /dev/null
+++ b/engines/colony/shaders/colony_solid_3d.fragment
@@ -0,0 +1,49 @@
+OUTPUT
+
+uniform vec4 color;
+
+// Stipple state. The pattern is the same 32Ã32 / 128-byte layout that
+// glPolygonStipple expects, encoded as 128 ints. Bit 1 â foreground,
+// bit 0 â background (Mac QuickDraw convention).
+//
+// Bit-extraction logic mirrors Freescape's freescape_triangle.fragment:
+// constant-iteration loops avoid GLES2's restriction on dynamic indexing
+// of uniform arrays.
+uniform UBOOL useStipple;
+uniform int stipple[128];
+uniform vec4 stippleFg;
+uniform vec4 stippleBg;
+
+void main() {
+ if (UBOOL_TEST(useStipple)) {
+ ivec2 coord = ivec2(gl_FragCoord.xy);
+ int x = int(mod(float(coord.x), 32.));
+ int y = int(mod(float(coord.y), 32.));
+
+ // Each row has 4 bytes (4 Ã 8 = 32 columns).
+ int byteIndex = y * 4 + (x / 8);
+ int bitIndex = int(mod(float(x), 8.));
+
+ int patternByte = 0;
+ for (int i = 0; i < 128; i++) {
+ if (i == byteIndex) {
+ patternByte = stipple[i];
+ break;
+ }
+ }
+
+ // Bit 7 (MSB) = leftmost column. Right-shift (7 - bitIndex) times.
+ for (int i = 0; i < 7; i++) {
+ if (i >= 7 - bitIndex)
+ break;
+ patternByte = patternByte / 2;
+ }
+
+ if (int(mod(float(patternByte), 2.)) == 0)
+ outColor = stippleBg;
+ else
+ outColor = stippleFg;
+ } else {
+ outColor = color;
+ }
+}
Commit: 4f4b855c9df3bb89fd3699c791698fb62352b223
https://github.com/scummvm/scummvm/commit/4f4b855c9df3bb89fd3699c791698fb62352b223
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-04-30T18:55:53+02:00
Commit Message:
COLONY: add shader files into distribution files
Changed paths:
Makefile.common
devtools/create_project/xcode.cpp
diff --git a/Makefile.common b/Makefile.common
index 21b7e4392fe..12074f25763 100644
--- a/Makefile.common
+++ b/Makefile.common
@@ -459,6 +459,9 @@ endif
ifdef ENABLE_FREESCAPE
DIST_FILES_SHADERS+=$(wildcard $(srcdir)/engines/freescape/shaders/*)
endif
+ifdef ENABLE_COLONY
+DIST_FILES_SHADERS+=$(wildcard $(srcdir)/engines/colony/shaders/*)
+endif
endif
# Soundfonts
diff --git a/devtools/create_project/xcode.cpp b/devtools/create_project/xcode.cpp
index d4d3b547c36..20779d3ab8d 100644
--- a/devtools/create_project/xcode.cpp
+++ b/devtools/create_project/xcode.cpp
@@ -1141,6 +1141,14 @@ XcodeProvider::ValueList& XcodeProvider::getResourceFiles(const BuildSetup &setu
files.push_back("engines/freescape/shaders/freescape_cubemap.fragment");
files.push_back("engines/freescape/shaders/freescape_cubemap.vertex");
}
+ if (CONTAINS_DEFINE(setup.defines, "ENABLE_COLONY")) {
+ files.push_back("engines/colony/shaders/colony_solid.fragment");
+ files.push_back("engines/colony/shaders/colony_solid.vertex");
+ files.push_back("engines/colony/shaders/colony_solid_3d.fragment");
+ files.push_back("engines/colony/shaders/colony_solid_3d.vertex");
+ files.push_back("engines/colony/shaders/colony_bitmap.fragment");
+ files.push_back("engines/colony/shaders/colony_bitmap.vertex");
+ }
if (CONTAINS_DEFINE(setup.defines, "USE_FLUIDSYNTH")) {
files.push_back("dists/soundfonts/Roland_SC-55.sf2");
files.push_back("dists/soundfonts/COPYRIGHT.Roland_SC-55");
Commit: ca3f853772918ab622b5f741b47dbe25296b1734
https://github.com/scummvm/scummvm/commit/ca3f853772918ab622b5f741b47dbe25296b1734
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-04-30T18:55:53+02:00
Commit Message:
COLONY: deleted scroll interface
Changed paths:
engines/colony/renderer.h
engines/colony/renderer_opengl.cpp
engines/colony/renderer_opengl_shaders.cpp
diff --git a/engines/colony/renderer.h b/engines/colony/renderer.h
index fc9fb696c7a..8145baa0f40 100644
--- a/engines/colony/renderer.h
+++ b/engines/colony/renderer.h
@@ -45,7 +45,6 @@ public:
virtual void drawRect(const Common::Rect &rect, uint32 color) = 0;
virtual void fillRect(const Common::Rect &rect, uint32 color) = 0;
virtual void drawString(const Graphics::Font *font, const Common::String &str, int x, int y, uint32 color, Graphics::TextAlign align = Graphics::kTextAlignLeft) = 0;
- virtual void scroll(int dx, int dy, uint32 background) = 0;
virtual void drawEllipse(int x, int y, int rx, int ry, uint32 color) = 0;
virtual void fillEllipse(int x, int y, int rx, int ry, uint32 color) = 0;
virtual void fillDitherRect(const Common::Rect &rect, uint32 color1, uint32 color2) = 0;
diff --git a/engines/colony/renderer_opengl.cpp b/engines/colony/renderer_opengl.cpp
index 88fbd364285..6c1bf0cfd0b 100644
--- a/engines/colony/renderer_opengl.cpp
+++ b/engines/colony/renderer_opengl.cpp
@@ -48,7 +48,6 @@ public:
void drawRect(const Common::Rect &rect, uint32 color) override;
void fillRect(const Common::Rect &rect, uint32 color) override;
void drawString(const Graphics::Font *font, const Common::String &str, int x, int y, uint32 color, Graphics::TextAlign align) override;
- void scroll(int dx, int dy, uint32 background) override;
void drawEllipse(int x, int y, int rx, int ry, uint32 color) override;
void fillEllipse(int x, int y, int rx, int ry, uint32 color) override;
void fillDitherRect(const Common::Rect &rect, uint32 color1, uint32 color2) override;
@@ -554,9 +553,6 @@ void OpenGLRenderer::computeScreenViewport() {
glScissor(_screenViewport.left, screenHeight - _screenViewport.bottom, _screenViewport.width(), _screenViewport.height());
}
-void OpenGLRenderer::scroll(int dx, int dy, uint32 background) {
-}
-
void OpenGLRenderer::drawEllipse(int x, int y, int rx, int ry, uint32 color) {
GLint savedViewport[4];
GLint savedScissor[4];
diff --git a/engines/colony/renderer_opengl_shaders.cpp b/engines/colony/renderer_opengl_shaders.cpp
index 4db4864e2b5..cf7ed31d02c 100644
--- a/engines/colony/renderer_opengl_shaders.cpp
+++ b/engines/colony/renderer_opengl_shaders.cpp
@@ -58,7 +58,6 @@ public:
void fillRect(const Common::Rect &rect, uint32 color) override;
void drawString(const Graphics::Font *font, const Common::String &str, int x, int y,
uint32 color, Graphics::TextAlign align) override;
- void scroll(int dx, int dy, uint32 background) override {}
void drawEllipse(int x, int y, int rx, int ry, uint32 color) override;
void fillEllipse(int x, int y, int rx, int ry, uint32 color) override;
void fillDitherRect(const Common::Rect &rect, uint32 c1, uint32 c2) override;
Commit: c66e0f4fa3da891ee9615565077bb6890276a8be
https://github.com/scummvm/scummvm/commit/c66e0f4fa3da891ee9615565077bb6890276a8be
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-04-30T18:55:53+02:00
Commit Message:
COLONY: enable SupportsArbitraryResolutions
Changed paths:
engines/colony/animation.cpp
engines/colony/colony.cpp
engines/colony/colony.h
engines/colony/intro.cpp
engines/colony/renderer_opengl.cpp
engines/colony/renderer_opengl_shaders.cpp
engines/colony/savegame.cpp
diff --git a/engines/colony/animation.cpp b/engines/colony/animation.cpp
index 970fe084230..93eeba9e536 100644
--- a/engines/colony/animation.cpp
+++ b/engines/colony/animation.cpp
@@ -397,7 +397,7 @@ void ColonyEngine::playAnimation() {
_animationRunning = true;
_system->lockMouse(false);
_system->showMouse(true);
- _system->warpMouse(_centerX, _centerY);
+ warpMouseLogical(_centerX, _centerY);
const char *cursorName = "default arrow cursor";
if (_renderMode == Common::kRenderMacintosh && _macArrowCursor) {
cursorName = "Mac arrow cursor";
@@ -591,17 +591,19 @@ void ColonyEngine::playAnimation() {
_gfx->computeScreenViewport();
needsDraw = true;
} else if (event.type == Common::EVENT_LBUTTONDOWN) {
- int item = whichSprite(event.mouse);
+ int item = whichSprite(eventMouseToLogical(event.mouse));
if (item > 0) {
handleAnimationClick(item);
needsDraw = true;
}
} else if (event.type == Common::EVENT_RBUTTONDOWN) {
// DOS: right-click exits animation (AnimControl returns FALSE on button-up)
- debugC(1, kColonyDebugAnimation, "Animation: RBUTTONDOWN exit at pos=%d,%d", event.mouse.x, event.mouse.y);
+ const Common::Point logical = eventMouseToLogical(event.mouse);
+ debugC(1, kColonyDebugAnimation, "Animation: RBUTTONDOWN exit at pos=%d,%d", logical.x, logical.y);
_animationRunning = false;
} else if (event.type == Common::EVENT_MOUSEMOVE) {
- debugC(5, kColonyDebugAnimation, "Animation Mouse: %d, %d", event.mouse.x, event.mouse.y);
+ const Common::Point logical = eventMouseToLogical(event.mouse);
+ debugC(5, kColonyDebugAnimation, "Animation Mouse: %d, %d", logical.x, logical.y);
} else if (event.type == Common::EVENT_CUSTOM_ENGINE_ACTION_START) {
if (event.customType == kActionEscape) {
openMainMenuDialog();
@@ -1779,8 +1781,11 @@ void ColonyEngine::moveObject(int index) {
linked.push_back(index);
}
- // Get initial mouse position and animation origin
- Common::Point old = _system->getEventManager()->getMousePos();
+ // Get initial mouse position and animation origin. getMousePos() returns
+ // virtual-screen coords; with kSupportsArbitraryResolutions that's
+ // window pixels, but sprite xloc/yloc and _screenR are in engine-logical
+ // coords. Convert so drag deltas are in the same units as the sprites.
+ Common::Point old = eventMouseToLogical(_system->getEventManager()->getMousePos());
int ox = _screenR.left + (_screenR.width() - 416) / 2;
ox = (ox / 8) * 8;
int oy = _screenR.top + (_screenR.height() - 264) / 2;
@@ -1857,7 +1862,7 @@ void ColonyEngine::moveObject(int index) {
if (!buttonDown)
break;
- Common::Point cur = _system->getEventManager()->getMousePos();
+ Common::Point cur = eventMouseToLogical(_system->getEventManager()->getMousePos());
int dx = cur.x - old.x;
int dy = cur.y - old.y;
diff --git a/engines/colony/colony.cpp b/engines/colony/colony.cpp
index 0996a626682..dd764ff95d5 100644
--- a/engines/colony/colony.cpp
+++ b/engines/colony/colony.cpp
@@ -294,6 +294,26 @@ ColonyEngine::~ColonyEngine() {
delete _wm;
}
+Common::Point ColonyEngine::eventMouseToLogical(const Common::Point &p) const {
+ const int sysW = _system->getWidth();
+ const int sysH = _system->getHeight();
+ if (sysW <= 0 || sysH <= 0 || (sysW == _width && sysH == _height))
+ return p;
+ return Common::Point((int)((int64)p.x * _width / sysW),
+ (int)((int64)p.y * _height / sysH));
+}
+
+void ColonyEngine::warpMouseLogical(int x, int y) {
+ const int sysW = _system->getWidth();
+ const int sysH = _system->getHeight();
+ if (sysW <= 0 || sysH <= 0 || (sysW == _width && sysH == _height)) {
+ _system->warpMouse(x, y);
+ return;
+ }
+ _system->warpMouse((int)((int64)x * sysW / _width),
+ (int)((int64)y * sysH / _height));
+}
+
void ColonyEngine::pauseEngineIntern(bool pause) {
if (pause && _gfx && _level >= 1 && _level <= 7) {
if (_savedScreen) {
@@ -494,7 +514,7 @@ void ColonyEngine::updateMouseCapture(bool recenter) {
if (_mouseLocked && recenter) {
_mousePos = Common::Point(_centerX, _centerY);
- _system->warpMouse(_centerX, _centerY);
+ warpMouseLogical(_centerX, _centerY);
_system->getEventManager()->purgeMouseEvents();
}
}
@@ -880,10 +900,26 @@ Common::Error ColonyEngine::run() {
Common::Event event;
while (_system->getEventManager()->pollEvent(event)) {
- // Let MacWindowManager handle menu events first
+ // Let MacWindowManager handle menu events first. The menu bar is
+ // drawn into _menuSurface (engine logical coords, _widthÃ_height
+ // per colony.cpp:570), so its hit-testing rects are in logical
+ // space. With kSupportsArbitraryResolutions, event.mouse arrives
+ // in window pixels â pass a coord-scaled copy so the WM resolves
+ // menu clicks correctly. The original event is preserved for the
+ // engine's own handlers downstream.
if (_wm) {
bool wasMenuActive = _wm->isMenuActive();
- if (_wm->processEvent(event)) {
+ Common::Event wmEvent = event;
+ if (event.type == Common::EVENT_MOUSEMOVE
+ || event.type == Common::EVENT_LBUTTONDOWN
+ || event.type == Common::EVENT_LBUTTONUP
+ || event.type == Common::EVENT_RBUTTONDOWN
+ || event.type == Common::EVENT_RBUTTONUP
+ || event.type == Common::EVENT_MBUTTONDOWN
+ || event.type == Common::EVENT_MBUTTONUP) {
+ wmEvent.mouse = eventMouseToLogical(event.mouse);
+ }
+ if (_wm->processEvent(wmEvent)) {
// WM consumed the event (menu interaction)
if (!wasMenuActive && _wm->isMenuActive()) {
_system->lockMouse(false);
@@ -1028,8 +1064,10 @@ Common::Error ColonyEngine::run() {
} else if (event.type == Common::EVENT_LBUTTONDOWN && (_mouseLocked || _cursorShoot)) {
cShoot();
} else if (event.type == Common::EVENT_MOUSEMOVE) {
- _mousePos = event.mouse;
+ _mousePos = eventMouseToLogical(event.mouse);
if (_mouseLocked) {
+ // relMouse stays in window-pixel deltas regardless of
+ // resolution mode â keep raw for mouselook feel.
mouseDX += event.relMouse.x;
mouseDY += event.relMouse.y;
mouseMoved = true;
@@ -1049,7 +1087,7 @@ Common::Error ColonyEngine::run() {
}
// Warp back to center and purge remaining mouse events
// to prevent the warp from generating phantom deltas (Freescape pattern)
- _system->warpMouse(_centerX, _centerY);
+ warpMouseLogical(_centerX, _centerY);
_system->getEventManager()->purgeMouseEvents();
mouseMoved = false;
mouseDX = mouseDY = 0;
diff --git a/engines/colony/colony.h b/engines/colony/colony.h
index a7f70bc0876..5e41698308a 100644
--- a/engines/colony/colony.h
+++ b/engines/colony/colony.h
@@ -663,6 +663,21 @@ private:
int occupiedObjectAt(int x, int y, const Locate *pobject);
void interactWithObject(int objNum);
+ // Convert a mouse coord delivered by the event manager into engine
+ // logical coords. With kSupportsArbitraryResolutions declared, the
+ // framework rewrites _currentState.gameWidth to the overlay (window)
+ // pixel size in recalculateDisplayAreas() â so g_system->getWidth()
+ // no longer matches our _width, and mouse events arrive in window
+ // pixels. The engine's hit-test math (whichSprite, _screenR) is in
+ // logical coords, so we have to scale back. Same pattern Freescape
+ // uses in mousePosToCrossairPos (freescape.cpp:593-597).
+ Common::Point eventMouseToLogical(const Common::Point &p) const;
+ // Inverse of eventMouseToLogical: warp the mouse to a position
+ // expressed in engine-logical coords. _system->warpMouse expects
+ // virtual-screen coords, which with kSupportsArbitraryResolutions
+ // is window pixels.
+ void warpMouseLogical(int x, int y);
+
// shoot.c: shooting and power management
void setPower(int p0, int p1, int p2);
void cShoot();
diff --git a/engines/colony/intro.cpp b/engines/colony/intro.cpp
index eee3ebe8dc5..6bbed3222f5 100644
--- a/engines/colony/intro.cpp
+++ b/engines/colony/intro.cpp
@@ -72,20 +72,40 @@ public:
if (processEvent(event))
continue;
+ // MacDialog button rects are in _screen coordinates (engine
+ // logical, e.g. 853Ã480). With kSupportsArbitraryResolutions
+ // the framework rewrites g_system->getWidth/Height to window
+ // pixel size, so event.mouse arrives in window pixels â
+ // convert back to _screen coords before hit-testing.
+ auto toLocal = [this](const Common::Point &p) -> Common::Point {
+ const int sysW = g_system->getWidth();
+ const int sysH = g_system->getHeight();
+ if (sysW <= 0 || sysH <= 0 || (sysW == _screen->w && sysH == _screen->h))
+ return p;
+ return Common::Point((int)((int64)p.x * _screen->w / sysW),
+ (int)((int64)p.y * _screen->h / sysH));
+ };
+
switch (event.type) {
case Common::EVENT_QUIT:
shouldQuitEngine = true;
shouldQuit = true;
break;
- case Common::EVENT_MOUSEMOVE:
- mouseMove(event.mouse.x, event.mouse.y);
+ case Common::EVENT_MOUSEMOVE: {
+ const Common::Point p = toLocal(event.mouse);
+ mouseMove(p.x, p.y);
break;
- case Common::EVENT_LBUTTONDOWN:
- mouseClick(event.mouse.x, event.mouse.y);
+ }
+ case Common::EVENT_LBUTTONDOWN: {
+ const Common::Point p = toLocal(event.mouse);
+ mouseClick(p.x, p.y);
break;
- case Common::EVENT_LBUTTONUP:
- shouldQuit = mouseRaise(event.mouse.x, event.mouse.y);
+ }
+ case Common::EVENT_LBUTTONUP: {
+ const Common::Point p = toLocal(event.mouse);
+ shouldQuit = mouseRaise(p.x, p.y);
break;
+ }
case Common::EVENT_KEYDOWN:
if (event.kbd.keycode == Common::KEYCODE_ESCAPE) {
_pressedButton = -1;
diff --git a/engines/colony/renderer_opengl.cpp b/engines/colony/renderer_opengl.cpp
index 6c1bf0cfd0b..7ced3b47798 100644
--- a/engines/colony/renderer_opengl.cpp
+++ b/engines/colony/renderer_opengl.cpp
@@ -99,6 +99,11 @@ public:
private:
void useColor(uint32 color);
+ // Set glLineWidth scaled to window/logical ratio. Mirrors Freescape
+ // (gfx_opengl_shaders.cpp:692). Cached so back-to-back same-width
+ // line draws don't re-issue the GL call.
+ void applyLineWidthForLines();
+ float _lineWidth = 1.0f;
GLuint _overlayTexId = 0;
OSystem *_system = nullptr;
@@ -166,6 +171,17 @@ void OpenGLRenderer::useColor(uint32 color) {
}
}
+void OpenGLRenderer::applyLineWidthForLines() {
+ const int sysW = _system ? _system->getWidth() : 0;
+ float w = 1.0f;
+ if (sysW > _width)
+ w = MAX(1.0f, (float)sysW / (float)_width);
+ if (w != _lineWidth) {
+ glLineWidth(w);
+ _lineWidth = w;
+ }
+}
+
void OpenGLRenderer::clear(uint32 color) {
float r, g, b;
if (color & 0xFF000000) {
@@ -184,6 +200,7 @@ void OpenGLRenderer::clear(uint32 color) {
void OpenGLRenderer::drawLine(int x1, int y1, int x2, int y2, uint32 color) {
useColor(color);
+ applyLineWidthForLines();
glBegin(GL_LINES);
glVertex2i(x1, y1);
glVertex2i(x2, y2);
@@ -192,6 +209,7 @@ void OpenGLRenderer::drawLine(int x1, int y1, int x2, int y2, uint32 color) {
void OpenGLRenderer::drawRect(const Common::Rect &rect, uint32 color) {
useColor(color);
+ applyLineWidthForLines();
glBegin(GL_LINE_LOOP);
glVertex2i(rect.left, rect.top);
glVertex2i(rect.right, rect.top);
@@ -328,6 +346,7 @@ void OpenGLRenderer::draw3DWall(int x1, int y1, int x2, int y2, uint32 color) {
// Draw colored wireframe edges
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
+ applyLineWidthForLines();
useColor(color);
glBegin(GL_QUADS);
glVertex3f(fx1, fy1, -160.0f);
@@ -572,6 +591,7 @@ void OpenGLRenderer::drawEllipse(int x, int y, int rx, int ry, uint32 color) {
glDisable(GL_DEPTH_TEST);
useColor(color);
+ applyLineWidthForLines();
glBegin(GL_LINE_LOOP);
for (int i = 0; i < 360; i += 10) {
float rad = i * M_PI / 180.0f;
@@ -636,9 +656,18 @@ void OpenGLRenderer::fillDitherRect(const Common::Rect &rect, uint32 color1, uin
}
void OpenGLRenderer::setPixel(int x, int y, uint32 color) {
+ // Draw as a 1Ã1 GL_QUADS instead of GL_POINTS. With kSupportsArbitrary
+ // Resolutions the viewport stretches logical coords to window pixels:
+ // a single GL_POINT renders as 1 framebuffer pixel (leaving gaps on
+ // HiDPI), while a 1Ã1 quad in logical coords gets scaled to cover the
+ // full logical-pixel cell â matching what the shader renderer does
+ // (renderer_opengl_shaders.cpp setPixel implementation).
useColor(color);
- glBegin(GL_POINTS);
+ glBegin(GL_QUADS);
glVertex2i(x, y);
+ glVertex2i(x + 1, y);
+ glVertex2i(x + 1, y + 1);
+ glVertex2i(x, y + 1);
glEnd();
}
@@ -652,6 +681,7 @@ void OpenGLRenderer::drawQuad(int x1, int y1, int x2, int y2, int x3, int y3, in
glEnd();
glColor3ub(255, 255, 255);
+ applyLineWidthForLines();
glBegin(GL_LINE_LOOP);
glVertex2i(x1, y1);
glVertex2i(x2, y2);
@@ -669,8 +699,9 @@ void OpenGLRenderer::drawPolygon(const int *x, const int *y, int count, uint32 c
glVertex2i(x[i], y[i]);
}
glEnd();
-
+
glColor3ub(255, 255, 255);
+ applyLineWidthForLines();
glBegin(GL_LINE_LOOP);
for (int i = 0; i < count; i++) {
glVertex2i(x[i], y[i]);
diff --git a/engines/colony/renderer_opengl_shaders.cpp b/engines/colony/renderer_opengl_shaders.cpp
index cf7ed31d02c..7ef33fb4d5c 100644
--- a/engines/colony/renderer_opengl_shaders.cpp
+++ b/engines/colony/renderer_opengl_shaders.cpp
@@ -97,6 +97,10 @@ private:
void uploadSolid(const float *positions, int vertCount);
void drawSolid(GLenum mode, const float *positions, int vertCount, const float rgba[4]);
void drawTexturedQuad(int x, int y, int w, int h);
+ // Set glLineWidth to scale with the window size (Freescape pattern,
+ // gfx_opengl_shaders.cpp:692). No-op when mode isn't a line primitive.
+ // Cached to avoid redundant state changes across same-width draws.
+ void applyLineWidth(GLenum mode);
void uploadSolid3D(const float *positions, int vertCount);
// allowStipple=true on the fill pass picks up _stippleActive; lines
@@ -134,6 +138,9 @@ private:
float _solidLastColor[4] = { -1.0f, -1.0f, -1.0f, -1.0f };
float _solid3dLastColor[4] = { -1.0f, -1.0f, -1.0f, -1.0f };
+ // Cached glLineWidth so applyLineWidth() can short-circuit repeats.
+ float _lineWidth = 1.0f;
+
bool _wireframe = true;
int64_t _wireframeFillColor = 0; // -1 = no fill, else color (palette idx or ARGB)
@@ -315,6 +322,24 @@ void OpenGLShaderRenderer::uploadSolid(const float *positions, int vertCount) {
glBufferData(GL_ARRAY_BUFFER, sizeof(float) * 2 * vertCount, positions, GL_DYNAMIC_DRAW);
}
+void OpenGLShaderRenderer::applyLineWidth(GLenum mode) {
+ if (mode != GL_LINES && mode != GL_LINE_STRIP && mode != GL_LINE_LOOP)
+ return;
+ // Scale by window pixel width versus engine logical width â mirrors
+ // Freescape (gfx_opengl_shaders.cpp:692), MAX(1, ...) for safety.
+ // With kSupportsArbitraryResolutions, _system->getWidth() returns the
+ // overlay (window) pixel width; without it, getWidth() == _width and
+ // the ratio is 1.
+ const int sysW = _system ? _system->getWidth() : 0;
+ float w = 1.0f;
+ if (sysW > _width)
+ w = MAX(1.0f, (float)sysW / (float)_width);
+ if (w != _lineWidth) {
+ glLineWidth(w);
+ _lineWidth = w;
+ }
+}
+
void OpenGLShaderRenderer::drawSolid(GLenum mode, const float *positions, int vertCount,
const float rgba[4]) {
if (vertCount <= 0)
@@ -331,6 +356,7 @@ void OpenGLShaderRenderer::drawSolid(GLenum mode, const float *positions, int ve
_solidLastColor[0] = rgba[0]; _solidLastColor[1] = rgba[1];
_solidLastColor[2] = rgba[2]; _solidLastColor[3] = rgba[3];
}
+ applyLineWidth(mode);
glDrawArrays(mode, 0, vertCount);
}
@@ -807,6 +833,7 @@ void OpenGLShaderRenderer::drawSolid3D(GLenum mode, const float *positions, int
_solid3dLastColor[2] = rgba[2]; _solid3dLastColor[3] = rgba[3];
}
}
+ applyLineWidth(mode);
glDrawArrays(mode, 0, vertCount);
}
diff --git a/engines/colony/savegame.cpp b/engines/colony/savegame.cpp
index 4dec96aa614..8d7df774b93 100644
--- a/engines/colony/savegame.cpp
+++ b/engines/colony/savegame.cpp
@@ -279,7 +279,12 @@ bool findInvalidActiveObjectSlot(const Common::Array<Thing> &objects, uint32 &in
bool ColonyEngine::hasFeature(EngineFeature f) const {
return f == kSupportsReturnToLauncher ||
f == kSupportsLoadingDuringRuntime ||
- f == kSupportsSavingDuringRuntime;
+ f == kSupportsSavingDuringRuntime ||
+ // Skip the OpenGL backend's FBO compositing pass: render directly
+ // to the default framebuffer (Freescape's pattern, freescape.cpp:
+ // 1170). Side effect: the user-facing screenshot hotkey now reads
+ // the correct buffer because there's no FBO bound between frames.
+ f == kSupportsArbitraryResolutions;
}
bool ColonyEngine::canSaveGameStateCurrently(Common::U32String *msg) {
More information about the Scummvm-git-logs
mailing list