diff --git a/EU07.cpp b/EU07.cpp index e0361829..d3540235 100644 --- a/EU07.cpp +++ b/EU07.cpp @@ -81,6 +81,15 @@ LONG WINAPI CrashHandler(EXCEPTION_POINTERS *ExceptionInfo) #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[]) { #ifdef WITHDUMPGEN @@ -91,6 +100,8 @@ int main(int argc, char *argv[]) // init start timestamp Global.startTimestamp = std::chrono::steady_clock::now(); + int exitcode{0}; + // quick short-circuit for standalone e3d export if (argc == 6 && std::string(argv[1]) == "-e3d") { @@ -140,6 +151,10 @@ int main(int argc, char *argv[]) result = Application.run(); Application.exit(); } + else + { + exitcode = result; + } } catch (std::bad_alloc const &Error) { @@ -154,9 +169,10 @@ int main(int argc, char *argv[]) } #endif } + stop_log_service(); #ifndef _WIN32 fflush(stdout); fflush(stderr); #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 } diff --git a/README.md b/README.md index 47741a51..f2e9540f 100644 --- a/README.md +++ b/README.md @@ -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 ) ![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). @@ -89,7 +89,7 @@ MaSzyna compiles and runs natively on **Linux** and **Windows**. Other platforms **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) @@ -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**. +> **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** - Processor: **2 core at least 2 GHz (e.g. Intel Core i3-2100 / AMD Athlon II X2)** - 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** - Sound card supporting OpenAL with stereo - Optional COM ports for custom control panel setups. diff --git a/application/application.cpp b/application/application.cpp index 4148f78a..64827c41 100644 --- a/application/application.cpp +++ b/application/application.cpp @@ -1230,12 +1230,6 @@ static std::string describe_gl_driver() 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) { ErrorLog("Bad init: failed to initialize glfw"); diff --git a/application/application.h b/application/application.h index 11415603..b65a7f47 100644 --- a/application/application.h +++ b/application/application.h @@ -123,7 +123,6 @@ private: mode_stack m_modestack; // current behaviour mode python_taskqueue m_taskqueue; std::vector m_windows; - int m_glfwversion; std::optional m_network; std::optional m_headtrack; diff --git a/application/driveruipanels.cpp b/application/driveruipanels.cpp index c1da3f5e..1e4049d6 100644 --- a/application/driveruipanels.cpp +++ b/application/driveruipanels.cpp @@ -1447,8 +1447,6 @@ debug_panel::update_section_renderer( std::vector &Output ) const { textline += "NVRHI on "; } - textline += ", VBO"; - textline += " "; if( false == Global.LastGLError.empty() ) { diff --git a/directoryDescription.md b/directoryDescription.md index eb943fb9..6daf6a69 100644 --- a/directoryDescription.md +++ b/directoryDescription.md @@ -11,7 +11,7 @@ | Console/ | Hardware I/O | | application/ | Core simulation loop, time, environment, state serializer, sounds | | 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 | | environment/ | Sky, sun, moon, stars, skydome | | application/ | Application modes, UI layers/panels, driver/editor/scenario modes | diff --git a/docs/clustered-lighting.md b/docs/clustered-lighting.md new file mode 100644 index 00000000..7f7c715d --- /dev/null +++ b/docs/clustered-lighting.md @@ -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. diff --git a/docs/opengl46-renderer.md b/docs/opengl46-renderer.md new file mode 100644 index 00000000..d23c49e2 --- /dev/null +++ b/docs/opengl46-renderer.md @@ -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 (); the graphics card/driver provides: OpenGL X.Y ..., () +``` + +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. diff --git a/gl/bindable.h b/gl/bindable.h index b845dadb..9c9217a1 100644 --- a/gl/bindable.h +++ b/gl/bindable.h @@ -9,6 +9,13 @@ namespace gl thread_local static bindable* active; 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() { if (active == this) diff --git a/gl/shader.cpp b/gl/shader.cpp index a90a4f2f..70cccbff 100644 --- a/gl/shader.cpp +++ b/gl/shader.cpp @@ -2,7 +2,6 @@ #include #include -#include #include "shader.h" #include "glsl_common.h" #include "utilities/Logs.h" diff --git a/model/Texture.cpp b/model/Texture.cpp index 5713c822..70037b85 100644 --- a/model/Texture.cpp +++ b/model/Texture.cpp @@ -1198,7 +1198,7 @@ opengl_texture::set_filtering() const if (Global.AnisotropicFiltering >= 0) { // anisotropic filtering - ::glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, Global.AnisotropicFiltering ); + ::glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY, Global.AnisotropicFiltering ); } } diff --git a/rendering/frustum.h b/rendering/frustum.h index 4afe02f0..b12a7c58 100644 --- a/rendering/frustum.h +++ b/rendering/frustum.h @@ -24,8 +24,6 @@ inline std::vector const ndcfrustumshapepoints // {-1, 1, 1, 1}, // z-far }; -inline std::vector 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 class cFrustum { diff --git a/rendering/opengl46/opengl46renderer.cpp b/rendering/opengl46/opengl46renderer.cpp index 6600af3c..5250f5c7 100644 --- a/rendering/opengl46/opengl46renderer.cpp +++ b/rendering/opengl46/opengl46renderer.cpp @@ -108,7 +108,6 @@ bool opengl46_renderer::Init(GLFWwindow *Window) return false; WriteLog("preparing renderer.."); - OpenGLMatrices.upload() = false; // set matrix stack in virtual mode m_window = Window; @@ -495,7 +494,7 @@ bool opengl46_renderer::init_viewport(viewport_config &vp) vp.main_texv = std::make_unique(); 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); - } else + } if( !vp.main_fb->is_complete() ) { ErrorLog( "main framebuffer setup failed" ); diff --git a/rendering/opengl46/opengl46terrain.cpp b/rendering/opengl46/opengl46terrain.cpp index 7de2a147..dff9fda4 100644 --- a/rendering/opengl46/opengl46terrain.cpp +++ b/rendering/opengl46/opengl46terrain.cpp @@ -34,7 +34,7 @@ void bind_for_upload(opengl_texture const &Texture) void set_anisotropy(GLenum const Target) { if (Global.AnisotropicFiltering >= 0) - ::glTexParameterf(Target, GL_TEXTURE_MAX_ANISOTROPY_EXT, Global.AnisotropicFiltering); + ::glTexParameterf(Target, GL_TEXTURE_MAX_ANISOTROPY, Global.AnisotropicFiltering); } } // namespace diff --git a/rendering/openglmatrixstack.h b/rendering/openglmatrixstack.h index caf84bcb..7d3597ce 100644 --- a/rendering/openglmatrixstack.h +++ b/rendering/openglmatrixstack.h @@ -47,53 +47,38 @@ public: glm::mat4 const top { m_stack.back() }; m_stack.emplace_back( top ); } void - pop_matrix( bool const Upload = true ) { + pop_matrix() { if( m_stack.size() > 1 ) { - m_stack.pop_back(); - if( Upload ) { upload(); } } } + m_stack.pop_back(); } } void - load_identity( bool const Upload = true ) { - m_stack.back() = glm::mat4( 1.f ); - if( Upload ) { upload(); } } + load_identity() { + m_stack.back() = glm::mat4( 1.f ); } void - load_matrix( glm::mat4 const &Matrix, bool const Upload = true ) { - m_stack.back() = Matrix; - if( Upload ) { upload(); } } + load_matrix( glm::mat4 const &Matrix ) { + m_stack.back() = Matrix; } void - rotate( float const Angle, glm::vec3 const &Axis, bool const Upload = true ) { - m_stack.back() = glm::rotate( m_stack.back(), Angle, Axis ); - if( Upload ) { upload(); } } + rotate( float const Angle, glm::vec3 const &Axis ) { + m_stack.back() = glm::rotate( m_stack.back(), Angle, Axis ); } void - translate( glm::vec3 const &Translation, bool const Upload = true ) { - m_stack.back() = glm::translate( m_stack.back(), Translation ); - if( Upload ) { upload(); } } + translate( glm::vec3 const &Translation ) { + m_stack.back() = glm::translate( m_stack.back(), Translation ); } void - scale( glm::vec3 const &Scale, bool const Upload = true ) { - m_stack.back() = glm::scale( m_stack.back(), Scale ); - if( Upload ) { upload(); } } + scale( glm::vec3 const &Scale ) { + m_stack.back() = glm::scale( m_stack.back(), Scale ); } void - multiply( glm::mat4 const &Matrix, bool const Upload = true ) { - m_stack.back() *= Matrix; - if( Upload ) { upload(); } } + multiply( glm::mat4 const &Matrix ) { + m_stack.back() *= Matrix; } void - ortho( float const Left, float const Right, float const Bottom, float const Top, float const Znear, float const Zfar, bool const Upload = true ) { - m_stack.back() *= glm::ortho( Left, Right, Bottom, Top, Znear, Zfar ); - if( Upload ) { upload(); } } + 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 ); } void - perspective( float const Fovy, float const Aspect, float const Znear, float const Zfar, bool const Upload = true ) { - m_stack.back() *= glm::perspective( Fovy, Aspect, Znear, Zfar ); - if( Upload ) { upload(); } } + perspective( float const Fovy, float const Aspect, float const Znear, float const Zfar ) { + m_stack.back() *= glm::perspective( Fovy, Aspect, Znear, Zfar ); } void - look_at( glm::vec3 const &Eye, glm::vec3 const &Center, glm::vec3 const &Up, bool const Upload = true ) { - m_stack.back() *= glm::lookAt( Eye, Center, Up ); - if( Upload ) { upload(); } } + look_at( glm::vec3 const &Eye, glm::vec3 const &Center, glm::vec3 const &Up ) { + m_stack.back() *= glm::lookAt( Eye, Center, Up ); } 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: std::vector m_stack; }; @@ -110,9 +95,6 @@ public: } // methods: - bool & - upload() { - return m_upload; } void mode( GLuint const Mode ) { switch( Mode ) { @@ -133,11 +115,11 @@ public: void push_matrix() { m_stacks[ m_mode ].push_matrix(); } void - pop_matrix() { m_stacks[ m_mode ].pop_matrix( m_upload ); } + pop_matrix() { m_stacks[ m_mode ].pop_matrix(); } void - load_identity() { m_stacks[ m_mode ].load_identity( m_upload ); } + load_identity() { m_stacks[ m_mode ].load_identity(); } 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 void load_matrix( Type_ const *Matrix ) { load_matrix( glm::make_mat4( Matrix ) ); } @@ -149,8 +131,7 @@ public: glm::vec3( static_cast( X ), static_cast( Y ), - static_cast( Z ) ), - m_upload ); } + static_cast( Z ) ) ); } template void translate( Type_ const X, Type_ const Y, Type_ const Z ) { @@ -158,8 +139,7 @@ public: glm::vec3( static_cast( X ), static_cast( Y ), - static_cast( Z ) ), - m_upload ); } + static_cast( Z ) ) ); } template void scale( Type_ const X, Type_ const Y, Type_ const Z ) { @@ -167,14 +147,12 @@ public: glm::vec3( static_cast( X ), static_cast( Y ), - static_cast( Z ) ), - m_upload ); } + static_cast( Z ) ) ); } template void multiply( Type_ const *Matrix ) { m_stacks[ m_mode ].multiply( - glm::make_mat4( Matrix ), - m_upload ); } + glm::make_mat4( Matrix ) ); } template void 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( Bottom ), static_cast( Top ), static_cast( Znear ), - static_cast( Zfar ), - m_upload ); } + static_cast( Zfar ) ); } template void perspective( Type_ const Fovy, Type_ const Aspect, Type_ const Znear, Type_ const Zfar ) { @@ -193,8 +170,7 @@ public: static_cast( glm::radians( Fovy ) ), static_cast( Aspect ), static_cast( Znear ), - static_cast( Zfar ), - m_upload ); } + static_cast( Zfar ) ); } template 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 ) { @@ -210,14 +186,12 @@ public: glm::vec3( static_cast( Upx ), static_cast( Upy ), - static_cast( Upz ) ), - m_upload ); } + static_cast( Upz ) ) ); } private: // members: stack_mode m_mode{ stack_mode::gl_projection }; openglstack_array m_stacks; - bool m_upload { true }; }; extern opengl_matrices OpenGLMatrices; diff --git a/rendering/screenshot.cpp b/rendering/screenshot.cpp index 27ddb08e..de63c7fb 100644 --- a/rendering/screenshot.cpp +++ b/rendering/screenshot.cpp @@ -42,7 +42,8 @@ void screenshot_manager::screenshot_save_thread( char *img, int w, int h ) 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); //m7t: use pbo diff --git a/utilities/Logs.cpp b/utilities/Logs.cpp index 38f8062b..da14373a 100644 --- a/utilities/Logs.cpp +++ b/utilities/Logs.cpp @@ -77,8 +77,11 @@ std::mutex logMutex; 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 --- 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)); } } diff --git a/widgets/map.cpp b/widgets/map.cpp index 8f14eb8c..2340a437 100644 --- a/widgets/map.cpp +++ b/widgets/map.cpp @@ -16,8 +16,8 @@ ui::map_panel::map_panel() : ui_panel(STR_C("Map"), false) window_flags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse; #ifdef WITH_OPENGL_MODERN - opengl46_renderer *gl33 = dynamic_cast(GfxRenderer.get()); - if (!gl33) { + opengl46_renderer *renderer = dynamic_cast(GfxRenderer.get()); + if (!renderer) { ErrorLog("map not supported on old renderer"); return; } @@ -137,27 +137,27 @@ void ui::map_panel::render_map_texture(glm::mat4 transform, glm::vec2 surface_si scene_ubo->bind_uniform(); #ifdef WITH_OPENGL_MODERN - opengl46_renderer *gl33 = dynamic_cast(GfxRenderer.get()); + opengl46_renderer *renderer = dynamic_cast(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_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_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_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_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(); 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); - gl33->Draw_Geometry(simulation::Region->get_map_poi_geometry()); + renderer->Draw_Geometry(simulation::Region->get_map_poi_geometry()); #endif if (Global.iMultisampling)