17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-11-04 01:52:05 +01:00

Migration cleanup and renderer documentation

This commit is contained in:
2026-10-15 19:05:35 +02:00
parent c77151979a
commit f47172f34d
18 changed files with 310 additions and 90 deletions

View File

@@ -81,6 +81,15 @@ LONG WINAPI CrashHandler(EXCEPTION_POINTERS *ExceptionInfo)
#endif #endif
// lets the log service write what is still queued and stops it; does nothing once Application.exit() has done that
static void stop_log_service()
{
Global.applicationQuitOrder = true;
auto const it = Global.threads.find("LogService");
if (it != Global.threads.end() && it->second.joinable())
it->second.join();
}
int main(int argc, char *argv[]) int main(int argc, char *argv[])
{ {
#ifdef WITHDUMPGEN #ifdef WITHDUMPGEN
@@ -91,6 +100,8 @@ int main(int argc, char *argv[])
// init start timestamp // init start timestamp
Global.startTimestamp = std::chrono::steady_clock::now(); Global.startTimestamp = std::chrono::steady_clock::now();
int exitcode{0};
// quick short-circuit for standalone e3d export // quick short-circuit for standalone e3d export
if (argc == 6 && std::string(argv[1]) == "-e3d") if (argc == 6 && std::string(argv[1]) == "-e3d")
{ {
@@ -140,6 +151,10 @@ int main(int argc, char *argv[])
result = Application.run(); result = Application.run();
Application.exit(); Application.exit();
} }
else
{
exitcode = result;
}
} }
catch (std::bad_alloc const &Error) catch (std::bad_alloc const &Error)
{ {
@@ -154,9 +169,10 @@ int main(int argc, char *argv[])
} }
#endif #endif
} }
stop_log_service();
#ifndef _WIN32 #ifndef _WIN32
fflush(stdout); fflush(stdout);
fflush(stderr); fflush(stderr);
#endif #endif
std::_Exit(0); // skip destructors, there are ordering errors which causes segfaults std::_Exit(exitcode); // skip destructors, there are ordering errors which causes segfaults
} }

View File

@@ -8,7 +8,7 @@
![Discord](https://img.shields.io/discord/1019306167803056158?style=flat-square&logo=discord&label=Discord&labelColor=white) ![Github Actions](https://img.shields.io/github/actions/workflow/status/MaSzyna-EU07/maszyna/build_bin.yml?style=flat-square ) ![Quality gate](https://img.shields.io/sonar/quality_gate/MaSzyna-EU07_maszyna?server=https%3A%2F%2Fsonarcloud.io&style=flat-square ![Discord](https://img.shields.io/discord/1019306167803056158?style=flat-square&logo=discord&label=Discord&labelColor=white) ![Github Actions](https://img.shields.io/github/actions/workflow/status/MaSzyna-EU07/maszyna/build_bin.yml?style=flat-square ) ![Quality gate](https://img.shields.io/sonar/quality_gate/MaSzyna-EU07_maszyna?server=https%3A%2F%2Fsonarcloud.io&style=flat-square
) ![Sonar cloud violations](https://img.shields.io/sonar/violations/MaSzyna-EU07_maszyna?server=https%3A%2F%2Fsonarcloud.io&format=long&style=flat-square ) ![Sonar cloud violations](https://img.shields.io/sonar/violations/MaSzyna-EU07_maszyna?server=https%3A%2F%2Fsonarcloud.io&format=long&style=flat-square
) [![License: MPL 2.0](https://img.shields.io/badge/License-MPL%202.0-brightgreen.svg?style=flat-square)](LICENSE) [![Last commit](https://img.shields.io/github/last-commit/MaSzyna-EU07/maszyna?style=flat-square&label=Last%20commit)](https://github.com/MaSzyna-EU07/maszyna/commits/master) [![Open issues](https://img.shields.io/github/issues/MaSzyna-EU07/maszyna?style=flat-square&label=Issues)](https://github.com/MaSzyna-EU07/maszyna/issues) [![Pull requests](https://img.shields.io/github/issues-pr/MaSzyna-EU07/maszyna?style=flat-square&label=Pull%20requests)](https://github.com/MaSzyna-EU07/maszyna/pulls) [![GitHub stars](https://img.shields.io/github/stars/MaSzyna-EU07/maszyna?style=flat-square&logo=github&label=Stars)](https://github.com/MaSzyna-EU07/maszyna/stargazers) [![GitHub forks](https://img.shields.io/github/forks/MaSzyna-EU07/maszyna?style=flat-square&logo=github&label=Forks)](https://github.com/MaSzyna-EU07/maszyna/forks) ![C++](https://img.shields.io/badge/C%2B%2B-17-00599C?style=flat-square&logo=cplusplus) ![Windows](https://img.shields.io/badge/platform-Windows-0078D6?style=flat-square&logo=windows) ![Linux](https://img.shields.io/badge/platform-Linux-FCC624?style=flat-square&logo=linux&logoColor=black) ![OpenGL](https://img.shields.io/badge/OpenGL-3.3%2B-5586A4?style=flat-square&logo=opengl) ) [![License: MPL 2.0](https://img.shields.io/badge/License-MPL%202.0-brightgreen.svg?style=flat-square)](LICENSE) [![Last commit](https://img.shields.io/github/last-commit/MaSzyna-EU07/maszyna?style=flat-square&label=Last%20commit)](https://github.com/MaSzyna-EU07/maszyna/commits/master) [![Open issues](https://img.shields.io/github/issues/MaSzyna-EU07/maszyna?style=flat-square&label=Issues)](https://github.com/MaSzyna-EU07/maszyna/issues) [![Pull requests](https://img.shields.io/github/issues-pr/MaSzyna-EU07/maszyna?style=flat-square&label=Pull%20requests)](https://github.com/MaSzyna-EU07/maszyna/pulls) [![GitHub stars](https://img.shields.io/github/stars/MaSzyna-EU07/maszyna?style=flat-square&logo=github&label=Stars)](https://github.com/MaSzyna-EU07/maszyna/stargazers) [![GitHub forks](https://img.shields.io/github/forks/MaSzyna-EU07/maszyna?style=flat-square&logo=github&label=Forks)](https://github.com/MaSzyna-EU07/maszyna/forks) ![C++](https://img.shields.io/badge/C%2B%2B-17-00599C?style=flat-square&logo=cplusplus) ![Windows](https://img.shields.io/badge/platform-Windows-0078D6?style=flat-square&logo=windows) ![Linux](https://img.shields.io/badge/platform-Linux-FCC624?style=flat-square&logo=linux&logoColor=black) ![OpenGL](https://img.shields.io/badge/OpenGL-4.6-5586A4?style=flat-square&logo=opengl)
MaSzyna executable source code is licensed under the [MPL 2.0](https://www.mozilla.org/en-US/MPL/2.0/) and comes with a large pack of free assets under a [custom license](https://eu07.pl/docs/inne/readme_pliki/en-licence.html). MaSzyna executable source code is licensed under the [MPL 2.0](https://www.mozilla.org/en-US/MPL/2.0/) and comes with a large pack of free assets under a [custom license](https://eu07.pl/docs/inne/readme_pliki/en-licence.html).
@@ -89,7 +89,7 @@ MaSzyna compiles and runs natively on **Linux** and **Windows**. Other platforms
**3) Graphics API** **3) Graphics API**
- **OpenGL 3.0** or newer - **OpenGL 4.6** (core profile), required by the default renderer; see [docs/opengl46-renderer.md](docs/opengl46-renderer.md)
- **DirectX 12** via NVRHI (optional, Windows only, for Better Renderer) - **DirectX 12** via NVRHI (optional, Windows only, for Better Renderer)
@@ -105,6 +105,8 @@ MaSzyna compiles and runs natively on **Linux** and **Windows**. Other platforms
> **Linux note:** `WITH_BETTER_RENDERER` uses DirectX 12 through NVRHI and is **not supported on Linux**. > **Linux note:** `WITH_BETTER_RENDERER` uses DirectX 12 through NVRHI and is **not supported on Linux**.
> **macOS note:** macOS provides OpenGL only up to 4.1, so the OpenGL 4.6 renderer does not run there.
@@ -232,7 +234,7 @@ If detection still fails, padding the file (with comments) up to ~1 MB may hel
- OS: **Windows 10 64-bit** - OS: **Windows 10 64-bit**
- Processor: **2 core at least 2 GHz (e.g. Intel Core i3-2100 / AMD Athlon II X2)** - Processor: **2 core at least 2 GHz (e.g. Intel Core i3-2100 / AMD Athlon II X2)**
- RAM: **6 GB RAM** - RAM: **6 GB RAM**
- Graphics Card: **supporting OpenGL 3.3 with at least 1GB VRAM** - Graphics Card: **supporting OpenGL 4.6 with at least 1GB VRAM**
- **150 GB of free disk space** - **150 GB of free disk space**
- Sound card supporting OpenAL with stereo - Sound card supporting OpenAL with stereo
- Optional COM ports for custom control panel setups. - Optional COM ports for custom control panel setups.

View File

@@ -1230,12 +1230,6 @@ static std::string describe_gl_driver()
int eu07_application::init_glfw() int eu07_application::init_glfw()
{ {
{
int glfw_major, glfw_minor, glfw_rev;
glfwGetVersion(&glfw_major, &glfw_minor, &glfw_rev);
m_glfwversion = glfw_major * 10000 + glfw_minor * 100 + glfw_minor;
}
if (glfwInit() == GLFW_FALSE) if (glfwInit() == GLFW_FALSE)
{ {
ErrorLog("Bad init: failed to initialize glfw"); ErrorLog("Bad init: failed to initialize glfw");

View File

@@ -123,7 +123,6 @@ private:
mode_stack m_modestack; // current behaviour mode mode_stack m_modestack; // current behaviour mode
python_taskqueue m_taskqueue; python_taskqueue m_taskqueue;
std::vector<GLFWwindow *> m_windows; std::vector<GLFWwindow *> m_windows;
int m_glfwversion;
std::optional<network::manager> m_network; std::optional<network::manager> m_network;
std::optional<headtrack> m_headtrack; std::optional<headtrack> m_headtrack;

View File

@@ -1447,8 +1447,6 @@ debug_panel::update_section_renderer( std::vector<text_line> &Output ) const {
textline += "NVRHI on "; textline += "NVRHI on ";
} }
textline += ", VBO";
textline += " "; textline += " ";
if( false == Global.LastGLError.empty() ) { if( false == Global.LastGLError.empty() ) {

View File

@@ -11,7 +11,7 @@
| Console/ | Hardware I/O | | Console/ | Hardware I/O |
| application/ | Core simulation loop, time, environment, state serializer, sounds | | application/ | Core simulation loop, time, environment, state serializer, sounds |
| scene/ | Scene graph, scene nodes, scene editor, utilities | | scene/ | Scene graph, scene nodes, scene editor, utilities |
| rendering/ | All renderers (OpenGL legacy and full), geometry banks, particles, lights, frustum | | rendering/ | Renderers (the OpenGL 4.6 one is in rendering/opengl46/), geometry banks, particles, lights, frustum |
| audio/ | Sound, audio, and audio renderer | | audio/ | Sound, audio, and audio renderer |
| environment/ | Sky, sun, moon, stars, skydome | | environment/ | Sky, sun, moon, stars, skydome |
| application/ | Application modes, UI layers/panels, driver/editor/scenario modes | | application/ | Application modes, UI layers/panels, driver/editor/scenario modes |

130
docs/clustered-lighting.md Normal file
View File

@@ -0,0 +1,130 @@
# Clustered lighting
How the OpenGL 4.6 renderer (`rendering/opengl46/`) gives the shaders the lights of vehicle headlights.
For the settings and what changed for players see [opengl46-renderer.md](opengl46-renderer.md).
## Idea
The view frustum of the main pass is cut into a grid of **clusters**: 16 x 9 screen tiles and 24 depth slices,
3456 in all. A compute shader works out which lights reach each cluster, once per frame. The fragment shader
finds the cluster of its fragment and loops over the lights of that cluster only, so the cost of shading
depends on the lights that are near a fragment and not on the number of lights in the scene. The sun is not
a part of this: it is a single directional light, handled in the shader as before, with the cascaded shadow map.
All of the sizes are constants in `gl/ubo.h`; they reach the shaders as `#define`s written by
`gl/glsl_common.cpp` from the same constants.
## Buffers
| Binding | Kind | Name (GLSL) | Content | Written by |
|---|---|---|---|---|
| SSBO 0 | std430 | `light_ssbo` | header (`ambient`, `fog_color`, `lights_total`, `lights_count`, `lights_clustered`) and `lights[]` (slot 0 is the sun) | CPU, `Update_Lights()` |
| SSBO 1 | std430 | `cluster_aabb_ssbo` | bounding box of every cluster in view space | `cluster_build.comp` |
| SSBO 2 | std430 | `cluster_range_ssbo` | `(offset, count)` of every cluster into the index list | `cluster_cull.comp` |
| SSBO 3 | std430 | `cluster_index_ssbo` | shared list of indices into `lights[]` | `cluster_cull.comp` |
| SSBO 4 | std430 | `cluster_counter_ssbo` | `index_count` (allocator of the list) and `overflow_count` | zeroed by the CPU, `cluster_cull.comp` |
| UBO 2 | std140 | `cluster_ubo` | grid parameters of the pass: projection, near / far, slice scale and bias, target size, `valid` | CPU, `cluster_grid::update()` |
The UBO numbers go together with the scene (0), model (1) and instance (3) blocks; the SSBOs have a binding
space of their own. Layouts of the structures are checked with `static_assert`s on sizes and offsets in
`gl/ubo.h`, and have to stay equal to the blocks in `gl/glsl_common.cpp`.
Sizes: `MAX_LIGHTS` = 256 (the sun and 255 headlights), `MAX_LIGHTS_PER_CLUSTER` = 64. The buffers have a
fixed size, so changing the resolution, the field of view or the draw range only makes the grid parameters
and boxes be recomputed.
`gl::light_data.lights_total` is the number of lights written to `lights[]`, which the culling reads.
`lights_count` is the length of the plain loop over the lights (the sun and the first `dynamiclights` headlights) used
when the lights are not clustered. `lights_clustered` is set per pass and tells the shading which one to use.
## Frame order
In `Render_pass()` for the color pass of a view (`opengl46_renderer.cpp`):
1. `Update_Clusters(vp)`: `cluster_grid::update()` compares the projection and near / far plane with those the
boxes were built for. When they differ, `cluster_build.comp` recomputes the boxes (one invocation per
cluster, 54 work groups of 64). This is the "clusters" timer.
2. `Update_Lights(...)`: sorts the lights of the scene (`simulation::Lights`) by distance squared over
intensity, skips the lights that are off or farther than 1 km, writes each lit headlight to `lights[]`
in view space, and uploads the header and the part of the array in use.
3. `Cull_Lights(vp)`: the CPU zeroes the counters, then `cluster_cull.comp` runs (one invocation per cluster, 54
work groups of 64): it tests every light against the box of its cluster and writes the indices of the ones
that reach it. Then `glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT | GL_BUFFER_UPDATE_BARRIER_BIT)`. This is the
"cull" timer.
4. The scene is drawn. Opaque, transparent and the cab all take the lights from the cluster lists.
5. With `gfx.lights.clusterheatmap`, `Render_cluster_heatmap()` draws the number of lights per cluster over
the finished image.
Steps 1 and 3 are skipped when neither `gfx.lights.clustered` nor the heat map is on.
The grid and its buffers belong to a **viewport** (`viewport_config::clusters`): the main view, the extra
viewports and the eyes of a VR headset each have their own, because each has its own projection. The
indexed binding points are context state, so `cluster_grid::bind()` is called at the start of every pass that
uses the grid.
Passes without a grid: the orthographic view of the editor (`valid` = 0), the environment map (it gets its own
`Update_Lights(..., false)`: the sun and ambient light, `lights_total` = 1) and the model previews. They set
`lights_clustered` = 0 and use the plain loop.
## The grid
Depth is the clip space `w` of a point, which equals `1 / gl_FragCoord.w` of its fragment. Unlike the view
space `z` it is right for tilted screens (VR, extra viewports), where the screen is not perpendicular to the view axis. The projection is
reverse-Z with the far plane at infinity, so the `znear` / `zfar` of the pass bound the slices.
- Slice: `floor(log(w) * slice_scale + slice_bias)`, with `slice_scale = 24 / ln(zfar / znear)` and
`slice_bias = -ln(znear) * slice_scale`; exponential spacing from `znear` to `zfar`.
- Tile: `gl_FragCoord.xy` scaled to the 16 x 9 grid; both the tile and the slice are clamped to the grid
(a coordinate just under the edge rounds up to the next tile in float).
- Index: `x + 16 * (y + 9 * z)`, with `x` and `y` counted from the lower left corner, as `gl_FragCoord`.
- Box: `x`, `y` and `w` of the clip space are affine in the view-space position, so the eight corners of a
cluster (`ndc.xy` times `w` at the near and far side of the slice) map back to view space through the
inverse of the corresponding 3x3 part of the projection. The box is the min and max of the corners.
## Culling
`cluster_cull.comp` handles one cluster per invocation and goes through `lights[1 .. lights_total)`.
- A light's reach is the distance where its brightest possible contribution falls under `MIN_CONTRIBUTION`
(1/1024) of the display range, at most `MAX_RANGE` (1000 m, as the 1 km filter on the CPU). Headlights are tested against
that sphere and against the frustum of their projection (four side planes and the near plane, with 5 cm of margin);
point lights against a sphere; spot lights against a sphere and a cone; directional lights reach everything.
- A cluster takes up to 64 lights. The ones that come first in `lights[]` win, which is the order from
`Update_Lights()`. The pairs that did not fit are counted in `overflow_count`.
- The list is one shared array: each cluster reserves its place with one `atomicAdd` on `index_count`.
- The counters are copied to a ring of four small buffers with fences and read some frames later, so the
statistics never stall the GPU.
The headlight projection has no far plane (infinite, reverse-Z), so the reach of a headlight comes from
its falloff and the sphere.
## Shading
`shaders/light_common.glsl`:
- `fragment_cluster()` gives the cluster index of the fragment as described above.
- `add_headlights()` is the single loop over headlights, used by `apply_lights()` and by the normal-map
materials without sun: if `lights_clustered` is set it goes through
`cluster_light_indices[offset .. offset + count)`, otherwise through `1 .. lights_count - 1` of `lights[]`.
- `calc_headlights()` samples the beam texture only inside the 0..1 window of the projection, with
`textureGrad` and derivatives taken from the position before the loop: fragments of one quad may have
different lists at a cluster edge, and derivatives inside divergent control flow are undefined.
- The sun and `calc_shadow()` (cascaded shadow map with `textureGather`) are outside the loop.
`light_ssbo` and the cluster blocks are declared only for the fragment and compute stages; GL 4.6 does not
guarantee any storage blocks in the vertex and geometry stages.
## Changing it
- Grid size, per-cluster capacity, `MAX_LIGHTS`, binding numbers: `gl/ubo.h`, then `gl/glsl_common.cpp` takes them over.
The work group size in the shaders and the number of groups dispatched both come from `CLUSTER_*_GROUP_SIZE`.
- Check the shaders with `ci_shadervalidator` (see [opengl46-renderer.md](opengl46-renderer.md)); CI does it for
changes in `shaders/` and `gl/`.
- Only vehicle headlights feed `lights[]` today. The culling already knows `SPOT`, `POINT` and `DIR`, but
`add_headlights()` runs `calc_headlights()` for every listed light, so a new kind of light also needs
its own branch there.
## Not covered
- The environment map pass has no clusters of its own; it has the sun only.
- A grid for extra viewports and VR is built, but those paths have not been verified on hardware.

99
docs/opengl46-renderer.md Normal file
View File

@@ -0,0 +1,99 @@
# OpenGL 4.6 renderer
The default renderer (`gfxrenderer default`, the `modern` backend, `rendering/opengl46/`) runs on an
**OpenGL 4.6 core profile** context. There is no fallback to an older version, and the OpenGL 2.1 (legacy)
renderer and the OpenGL ES / ANGLE path no longer exist.
For how the lights are handled, see [clustered-lighting.md](clustered-lighting.md).
## Requirements
- A graphics card and driver that provide **OpenGL 4.6** (core profile). What the driver offers is written to
the log at start: `Renderer:`, `Vendor:` and `GL version:`.
- A CPU with AVX2, as before.
- Windows and Linux. macOS provides OpenGL only up to 4.1, so the renderer does not run there.
- The experimental Better Renderer (`gfxrenderer experimental`) is a separate renderer and is not affected.
On Linux, `glxinfo -B` should report an `OpenGL core profile version string` of 4.6 or newer.
### If the simulator does not start
When the context cannot be created, the log (`log.txt`) contains a line like
```
Bad init: failed to create OpenGL 4.6 core profile context (<GLFW error>); the graphics card/driver provides: OpenGL X.Y ..., <renderer> (<vendor>)
```
On Windows the same text is shown in a message box. Updating the graphics driver is the usual fix; a card
which does not support OpenGL 4.6 cannot run this renderer. The process ends with a non-zero exit code.
## eu07.ini
### Removed options
Old `eu07.ini` files still load. These entries are read and ignored, with a line in the log:
| Entry | What happens now |
|---|---|
| `gfxrenderer legacy`, `simple` or any other value except `default`, `full` and `experimental` | the default renderer is used (`eu07.ini: gfxrenderer "X" is no longer supported, using "default"`) |
| `usevbo` | ignored (`eu07.ini: "usevbo" is no longer supported and was ignored`) |
| `gfx.usegles` | ignored, same message |
| `gfx.angleplatform` | ignored, same message |
`gfxrenderer full` is still taken for `default`.
### New options
| Entry | Default | Meaning |
|---|---|---|
| `gfx.lights.clustered yes/no` | `yes` | `yes`: every lit vehicle headlight within 1 km lights the surroundings (see below). `no`: the shading goes through the first `dynamiclights` headlights only, as the old renderer did; useful to compare images or to work around a problem |
| `gfx.lights.clusterheatmap yes/no` | `no` | debug view: the number of lights in each cluster, drawn over the image |
### Changed option
`dynamiclights` (0..7) used to limit the number of headlight sets that light the surroundings. It is still
read, but it only takes effect with `gfx.lights.clustered no`.
## What looks different from the previous renderer
- **No limit of 7 lit headlights.** All lit headlights within 1 km take part in the lighting (each lit end
of a vehicle counts separately), up to 255 in the buffer and 64 reaching any one part of the view. Beyond
these capacities the farthest and dimmest lights are dropped, and the statistics show how many.
- **Outside the beam texture a headlight adds nothing.** Before, the edge of the texture was stretched
beyond the beam, which could leave a faint glow around vehicles. This also applies with
`gfx.lights.clustered no`.
- **Reflections (environment map)** are lit with the sun as seen from the reflection itself, and vehicle
headlights no longer light the scene in them.
- **Motion blur and SSAO** read the real depth of the scene. Before, the depth texture was read in a way the
specification leaves undefined, and the result depended on the driver.
- **Lines are 1 pixel wide**, as they always were on this kind of context. The widths the code asked for
(catenary wires, map tracks) never took effect.
The editor (F11, orthographic view), the reflection pass and the model previews do not use clusters. The
editor goes through the first `dynamiclights` headlights, the reflections through the sun only.
## Statistics and debugging
- **Shift+F12**, Gfx Renderer: the `lights:` row gives the number of lit headlight sets, how many did not fit,
and `clustered` or `first N`. With clusters on, `clusters:` (rebuilding of the grid, which happens when the
projection or the draw range change) and `cull:` (assigning lights to clusters, every frame) show the GPU
time, and `cluster lists:` the size of the lists and the number of dropped (cluster, light) pairs
(`overflow`, with its peak). A non-zero overflow means some cluster held more than 64 lights.
- **Light clusters** window (in debug mode, menu *Windows*, item *Headlight config* opens the renderer debug
windows): the switch for clustered lights, the heat map, the grid state and the same counters.
- `gfx.gldebug yes` turns on the OpenGL debug output (`KHR_debug`); messages go to the log.
## Building
`-DWITH_OPENGL_MODERN=ON` (default) builds this renderer; `WITH_OPENGL_LEGACY` and the GLES / ANGLE options
are gone. Linux builds still use `-DWITH_BETTER_RENDERER=OFF`.
All shaders in `shaders/` can be checked without a graphics card against GLSL 4.60, compute stage included:
```
ci_shadervalidator/build.sh
cd ci_shadervalidator && ./validateshaders
```
It needs `g++` with C++20, `glslangValidator` (Debian/Ubuntu: `glslang-tools`) and the `ref/glm` submodule.
The same check runs in CI for changes to shaders and the `gl/` wrappers.

View File

@@ -9,6 +9,13 @@ namespace gl
thread_local static bindable<T>* active; thread_local static bindable<T>* active;
public: public:
~bindable()
{
// deleting an object leaves its address free for the next one, which must not be taken for the bound one
if (active == this)
active = nullptr;
}
void bind() void bind()
{ {
if (active == this) if (active == this)

View File

@@ -2,7 +2,6 @@
#include <fstream> #include <fstream>
#include <sstream> #include <sstream>
#include <cstring>
#include "shader.h" #include "shader.h"
#include "glsl_common.h" #include "glsl_common.h"
#include "utilities/Logs.h" #include "utilities/Logs.h"

View File

@@ -1198,7 +1198,7 @@ opengl_texture::set_filtering() const
if (Global.AnisotropicFiltering >= 0) { if (Global.AnisotropicFiltering >= 0) {
// anisotropic filtering // anisotropic filtering
::glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, Global.AnisotropicFiltering ); ::glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY, Global.AnisotropicFiltering );
} }
} }

View File

@@ -24,8 +24,6 @@ inline std::vector<glm::vec4> const ndcfrustumshapepoints //
{-1, 1, 1, 1}, // z-far {-1, 1, 1, 1}, // z-far
}; };
inline std::vector<std::size_t> const frustumshapepoinstorder { 0, 1, 1, 2, 2, 3, 3, 0, 4, 5, 5, 6, 6, 7, 7, 4, 0, 4, 1, 5, 2, 6, 3, 7 };
// generic frustum class. used to determine if objects are inside current view area // generic frustum class. used to determine if objects are inside current view area
class cFrustum { class cFrustum {

View File

@@ -108,7 +108,6 @@ bool opengl46_renderer::Init(GLFWwindow *Window)
return false; return false;
WriteLog("preparing renderer.."); WriteLog("preparing renderer..");
OpenGLMatrices.upload() = false; // set matrix stack in virtual mode
m_window = Window; m_window = Window;
@@ -495,7 +494,7 @@ bool opengl46_renderer::init_viewport(viewport_config &vp)
vp.main_texv = std::make_unique<opengl_texture>(); vp.main_texv = std::make_unique<opengl_texture>();
vp.main_texv->alloc_rendertarget(Global.gfx_postfx_motionblur_format, GL_RG, vp.width, vp.height); vp.main_texv->alloc_rendertarget(Global.gfx_postfx_motionblur_format, GL_RG, vp.width, vp.height);
vp.main_fb->attach(*vp.main_texv, GL_COLOR_ATTACHMENT1); vp.main_fb->attach(*vp.main_texv, GL_COLOR_ATTACHMENT1);
} else }
if( !vp.main_fb->is_complete() ) { if( !vp.main_fb->is_complete() ) {
ErrorLog( "main framebuffer setup failed" ); ErrorLog( "main framebuffer setup failed" );

View File

@@ -34,7 +34,7 @@ void bind_for_upload(opengl_texture const &Texture)
void set_anisotropy(GLenum const Target) void set_anisotropy(GLenum const Target)
{ {
if (Global.AnisotropicFiltering >= 0) if (Global.AnisotropicFiltering >= 0)
::glTexParameterf(Target, GL_TEXTURE_MAX_ANISOTROPY_EXT, Global.AnisotropicFiltering); ::glTexParameterf(Target, GL_TEXTURE_MAX_ANISOTROPY, Global.AnisotropicFiltering);
} }
} // namespace } // namespace

View File

@@ -47,53 +47,38 @@ public:
glm::mat4 const top { m_stack.back() }; glm::mat4 const top { m_stack.back() };
m_stack.emplace_back( top ); } m_stack.emplace_back( top ); }
void void
pop_matrix( bool const Upload = true ) { pop_matrix() {
if( m_stack.size() > 1 ) { if( m_stack.size() > 1 ) {
m_stack.pop_back(); m_stack.pop_back(); } }
if( Upload ) { upload(); } } }
void void
load_identity( bool const Upload = true ) { load_identity() {
m_stack.back() = glm::mat4( 1.f ); m_stack.back() = glm::mat4( 1.f ); }
if( Upload ) { upload(); } }
void void
load_matrix( glm::mat4 const &Matrix, bool const Upload = true ) { load_matrix( glm::mat4 const &Matrix ) {
m_stack.back() = Matrix; m_stack.back() = Matrix; }
if( Upload ) { upload(); } }
void void
rotate( float const Angle, glm::vec3 const &Axis, bool const Upload = true ) { rotate( float const Angle, glm::vec3 const &Axis ) {
m_stack.back() = glm::rotate( m_stack.back(), Angle, Axis ); m_stack.back() = glm::rotate( m_stack.back(), Angle, Axis ); }
if( Upload ) { upload(); } }
void void
translate( glm::vec3 const &Translation, bool const Upload = true ) { translate( glm::vec3 const &Translation ) {
m_stack.back() = glm::translate( m_stack.back(), Translation ); m_stack.back() = glm::translate( m_stack.back(), Translation ); }
if( Upload ) { upload(); } }
void void
scale( glm::vec3 const &Scale, bool const Upload = true ) { scale( glm::vec3 const &Scale ) {
m_stack.back() = glm::scale( m_stack.back(), Scale ); m_stack.back() = glm::scale( m_stack.back(), Scale ); }
if( Upload ) { upload(); } }
void void
multiply( glm::mat4 const &Matrix, bool const Upload = true ) { multiply( glm::mat4 const &Matrix ) {
m_stack.back() *= Matrix; m_stack.back() *= Matrix; }
if( Upload ) { upload(); } }
void void
ortho( float const Left, float const Right, float const Bottom, float const Top, float const Znear, float const Zfar, bool const Upload = true ) { ortho( float const Left, float const Right, float const Bottom, float const Top, float const Znear, float const Zfar ) {
m_stack.back() *= glm::ortho( Left, Right, Bottom, Top, Znear, Zfar ); m_stack.back() *= glm::ortho( Left, Right, Bottom, Top, Znear, Zfar ); }
if( Upload ) { upload(); } }
void void
perspective( float const Fovy, float const Aspect, float const Znear, float const Zfar, bool const Upload = true ) { perspective( float const Fovy, float const Aspect, float const Znear, float const Zfar ) {
m_stack.back() *= glm::perspective( Fovy, Aspect, Znear, Zfar ); m_stack.back() *= glm::perspective( Fovy, Aspect, Znear, Zfar ); }
if( Upload ) { upload(); } }
void void
look_at( glm::vec3 const &Eye, glm::vec3 const &Center, glm::vec3 const &Up, bool const Upload = true ) { look_at( glm::vec3 const &Eye, glm::vec3 const &Center, glm::vec3 const &Up ) {
m_stack.back() *= glm::lookAt( Eye, Center, Up ); m_stack.back() *= glm::lookAt( Eye, Center, Up ); }
if( Upload ) { upload(); } }
private: private:
// methods:
// the core profile has no fixed pipeline matrices to upload to; the renderer passes the matrices to its shaders itself
void
upload() {}
// members: // members:
std::vector<glm::mat4> m_stack; std::vector<glm::mat4> m_stack;
}; };
@@ -110,9 +95,6 @@ public:
} }
// methods: // methods:
bool &
upload() {
return m_upload; }
void void
mode( GLuint const Mode ) { mode( GLuint const Mode ) {
switch( Mode ) { switch( Mode ) {
@@ -133,11 +115,11 @@ public:
void void
push_matrix() { m_stacks[ m_mode ].push_matrix(); } push_matrix() { m_stacks[ m_mode ].push_matrix(); }
void void
pop_matrix() { m_stacks[ m_mode ].pop_matrix( m_upload ); } pop_matrix() { m_stacks[ m_mode ].pop_matrix(); }
void void
load_identity() { m_stacks[ m_mode ].load_identity( m_upload ); } load_identity() { m_stacks[ m_mode ].load_identity(); }
void void
load_matrix( glm::mat4 const &Matrix ) { m_stacks[ m_mode ].load_matrix( Matrix, m_upload ); } load_matrix( glm::mat4 const &Matrix ) { m_stacks[ m_mode ].load_matrix( Matrix ); }
template <typename Type_> template <typename Type_>
void void
load_matrix( Type_ const *Matrix ) { load_matrix( glm::make_mat4( Matrix ) ); } load_matrix( Type_ const *Matrix ) { load_matrix( glm::make_mat4( Matrix ) ); }
@@ -149,8 +131,7 @@ public:
glm::vec3( glm::vec3(
static_cast<float>( X ), static_cast<float>( X ),
static_cast<float>( Y ), static_cast<float>( Y ),
static_cast<float>( Z ) ), static_cast<float>( Z ) ) ); }
m_upload ); }
template <typename Type_> template <typename Type_>
void void
translate( Type_ const X, Type_ const Y, Type_ const Z ) { translate( Type_ const X, Type_ const Y, Type_ const Z ) {
@@ -158,8 +139,7 @@ public:
glm::vec3( glm::vec3(
static_cast<float>( X ), static_cast<float>( X ),
static_cast<float>( Y ), static_cast<float>( Y ),
static_cast<float>( Z ) ), static_cast<float>( Z ) ) ); }
m_upload ); }
template <typename Type_> template <typename Type_>
void void
scale( Type_ const X, Type_ const Y, Type_ const Z ) { scale( Type_ const X, Type_ const Y, Type_ const Z ) {
@@ -167,14 +147,12 @@ public:
glm::vec3( glm::vec3(
static_cast<float>( X ), static_cast<float>( X ),
static_cast<float>( Y ), static_cast<float>( Y ),
static_cast<float>( Z ) ), static_cast<float>( Z ) ) ); }
m_upload ); }
template <typename Type_> template <typename Type_>
void void
multiply( Type_ const *Matrix ) { multiply( Type_ const *Matrix ) {
m_stacks[ m_mode ].multiply( m_stacks[ m_mode ].multiply(
glm::make_mat4( Matrix ), glm::make_mat4( Matrix ) ); }
m_upload ); }
template <typename Type_> template <typename Type_>
void void
ortho( Type_ const Left, Type_ const Right, Type_ const Bottom, Type_ const Top, Type_ const Znear, Type_ const Zfar ) { ortho( Type_ const Left, Type_ const Right, Type_ const Bottom, Type_ const Top, Type_ const Znear, Type_ const Zfar ) {
@@ -184,8 +162,7 @@ public:
static_cast<float>( Bottom ), static_cast<float>( Bottom ),
static_cast<float>( Top ), static_cast<float>( Top ),
static_cast<float>( Znear ), static_cast<float>( Znear ),
static_cast<float>( Zfar ), static_cast<float>( Zfar ) ); }
m_upload ); }
template <typename Type_> template <typename Type_>
void void
perspective( Type_ const Fovy, Type_ const Aspect, Type_ const Znear, Type_ const Zfar ) { perspective( Type_ const Fovy, Type_ const Aspect, Type_ const Znear, Type_ const Zfar ) {
@@ -193,8 +170,7 @@ public:
static_cast<float>( glm::radians( Fovy ) ), static_cast<float>( glm::radians( Fovy ) ),
static_cast<float>( Aspect ), static_cast<float>( Aspect ),
static_cast<float>( Znear ), static_cast<float>( Znear ),
static_cast<float>( Zfar ), static_cast<float>( Zfar ) ); }
m_upload ); }
template <typename Type_> template <typename Type_>
void void
look_at( Type_ const Eyex, Type_ const Eyey, Type_ const Eyez, Type_ const Centerx, Type_ const Centery, Type_ const Centerz, Type_ const Upx, Type_ const Upy, Type_ const Upz ) { look_at( Type_ const Eyex, Type_ const Eyey, Type_ const Eyez, Type_ const Centerx, Type_ const Centery, Type_ const Centerz, Type_ const Upx, Type_ const Upy, Type_ const Upz ) {
@@ -210,14 +186,12 @@ public:
glm::vec3( glm::vec3(
static_cast<float>( Upx ), static_cast<float>( Upx ),
static_cast<float>( Upy ), static_cast<float>( Upy ),
static_cast<float>( Upz ) ), static_cast<float>( Upz ) ) ); }
m_upload ); }
private: private:
// members: // members:
stack_mode m_mode{ stack_mode::gl_projection }; stack_mode m_mode{ stack_mode::gl_projection };
openglstack_array m_stacks; openglstack_array m_stacks;
bool m_upload { true };
}; };
extern opengl_matrices OpenGLMatrices; extern opengl_matrices OpenGLMatrices;

View File

@@ -42,7 +42,8 @@ void screenshot_manager::screenshot_save_thread( char *img, int w, int h )
void screenshot_manager::make_screenshot() void screenshot_manager::make_screenshot()
{ {
auto img = new char[Global.fb_size.x * Global.fb_size.y * 4]; // rows are tightly packed RGB: the renderer sets GL_PACK_ALIGNMENT to 1
auto img = new char[Global.fb_size.x * Global.fb_size.y * 3];
glReadPixels(0, 0, Global.fb_size.x, Global.fb_size.y, GL_RGB, GL_UNSIGNED_BYTE, (GLvoid*)img); glReadPixels(0, 0, Global.fb_size.x, Global.fb_size.y, GL_RGB, GL_UNSIGNED_BYTE, (GLvoid*)img);
//m7t: use pbo //m7t: use pbo

View File

@@ -77,8 +77,11 @@ std::mutex logMutex;
void LogService() void LogService()
{ {
while (!Global.applicationQuitOrder) bool quitting{false};
while (!quitting)
{ {
// read before the stacks are drained, so everything logged before the quit order still gets written
quitting = Global.applicationQuitOrder;
{ {
// --- Obsługa InfoStack --- // --- Obsługa InfoStack ---
while (!InfoStack.empty()) while (!InfoStack.empty())
@@ -144,7 +147,8 @@ void LogService()
} }
} }
std::this_thread::sleep_for(std::chrono::milliseconds(50)); if (!quitting)
std::this_thread::sleep_for(std::chrono::milliseconds(50));
} }
} }

View File

@@ -16,8 +16,8 @@ ui::map_panel::map_panel() : ui_panel(STR_C("Map"), false)
window_flags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse; window_flags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse;
#ifdef WITH_OPENGL_MODERN #ifdef WITH_OPENGL_MODERN
opengl46_renderer *gl33 = dynamic_cast<opengl46_renderer*>(GfxRenderer.get()); opengl46_renderer *renderer = dynamic_cast<opengl46_renderer*>(GfxRenderer.get());
if (!gl33) { if (!renderer) {
ErrorLog("map not supported on old renderer"); ErrorLog("map not supported on old renderer");
return; return;
} }
@@ -137,27 +137,27 @@ void ui::map_panel::render_map_texture(glm::mat4 transform, glm::vec2 surface_si
scene_ubo->bind_uniform(); scene_ubo->bind_uniform();
#ifdef WITH_OPENGL_MODERN #ifdef WITH_OPENGL_MODERN
opengl46_renderer *gl33 = dynamic_cast<opengl46_renderer*>(GfxRenderer.get()); opengl46_renderer *renderer = dynamic_cast<opengl46_renderer*>(GfxRenderer.get());
gl33->Draw_Geometry(m_section_handles.begin(), m_section_handles.end()); renderer->Draw_Geometry(m_section_handles.begin(), m_section_handles.end());
scene_ubs.cascade_end = glm::vec4(0.7f, 0.7f, 0.0f, 0.0f); scene_ubs.cascade_end = glm::vec4(0.7f, 0.7f, 0.0f, 0.0f);
scene_ubo->update(scene_ubs); scene_ubo->update(scene_ubs);
gl33->Draw_Geometry(m_colored_paths.future.begin(), m_colored_paths.future.end()); renderer->Draw_Geometry(m_colored_paths.future.begin(), m_colored_paths.future.end());
scene_ubs.cascade_end = glm::vec4(0.0f, 1.0f, 0.0f, 0.0f); scene_ubs.cascade_end = glm::vec4(0.0f, 1.0f, 0.0f, 0.0f);
scene_ubo->update(scene_ubs); scene_ubo->update(scene_ubs);
gl33->Draw_Geometry(m_colored_paths.switches.begin(), m_colored_paths.switches.end()); renderer->Draw_Geometry(m_colored_paths.switches.begin(), m_colored_paths.switches.end());
scene_ubs.cascade_end = glm::vec4(1.0f, 0.0f, 0.0f, 0.0f); scene_ubs.cascade_end = glm::vec4(1.0f, 0.0f, 0.0f, 0.0f);
scene_ubo->update(scene_ubs); scene_ubo->update(scene_ubs);
gl33->Draw_Geometry(m_colored_paths.occupied.begin(), m_colored_paths.occupied.end()); renderer->Draw_Geometry(m_colored_paths.occupied.begin(), m_colored_paths.occupied.end());
scene_ubs.cascade_end = glm::vec4(0.3f, 0.3f, 1.0f, 0.0f); scene_ubs.cascade_end = glm::vec4(0.3f, 0.3f, 1.0f, 0.0f);
scene_ubo->update(scene_ubs); scene_ubo->update(scene_ubs);
gl33->Draw_Geometry(m_colored_paths.highlighted.begin(), m_colored_paths.highlighted.end()); renderer->Draw_Geometry(m_colored_paths.highlighted.begin(), m_colored_paths.highlighted.end());
gl33->Bind_Texture(0, m_icon_atlas); renderer->Bind_Texture(0, m_icon_atlas);
m_poi_shader->bind(); m_poi_shader->bind();
scene_ubs.cascade_end = glm::vec4(1.0f / (surface_size / 200.0f), 1.0f, 0.0f); scene_ubs.cascade_end = glm::vec4(1.0f / (surface_size / 200.0f), 1.0f, 0.0f);
@@ -167,7 +167,7 @@ void ui::map_panel::render_map_texture(glm::mat4 transform, glm::vec2 surface_si
} }
scene_ubo->update(scene_ubs); scene_ubo->update(scene_ubs);
gl33->Draw_Geometry(simulation::Region->get_map_poi_geometry()); renderer->Draw_Geometry(simulation::Region->get_map_poi_geometry());
#endif #endif
if (Global.iMultisampling) if (Global.iMultisampling)