mirror of
https://github.com/Nexoniarz/Fry3DX.git
synced 2026-11-03 04:24:26 +00:00
OpenGL ES 3.0 for phones without Vulkan; the old-GPU renderer draws the grid; site fonts
- Android: Vulkan 1.3 if the phone has it, else OpenGL ES 3.0 (the fallback renderer, which now also runs on ES); the APK no longer needs Vulkan to install - The fallback renderer draws sprites and streaks: the grid and axes - The website ships its fonts (Inter, JetBrains Mono): no more falling back to whatever the visitor has - Android: link the log library (GitHub's build) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NMSrd5oe1GpUD7E6d825KP
This commit is contained in:
parent
65705d82f3
commit
5e84ef0e13
13 changed files with 348 additions and 53 deletions
|
|
@ -3,7 +3,9 @@
|
|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="io.github.nexoniarz.fry3dx"
|
||||
android:installLocation="auto">
|
||||
<uses-feature android:name="android.hardware.vulkan.version" android:version="0x403000" android:required="true" />
|
||||
<!-- Vulkan 1.3 if the phone has it, otherwise OpenGL ES 3.0 (a simpler look) -->
|
||||
<uses-feature android:name="android.hardware.vulkan.version" android:version="0x403000" android:required="false" />
|
||||
<uses-feature android:glEsVersion="0x00030000" android:required="true" />
|
||||
<uses-feature android:name="android.hardware.touchscreen" android:required="false" />
|
||||
<application
|
||||
android:label="Fry3DX"
|
||||
|
|
|
|||
|
|
@ -25,13 +25,13 @@ window.FRY = {
|
|||
{ icon: "◫", title: "Real shape editing", text: "Points, edges and faces. Extrude, inset, round edges, loop cut, knife, merge. Shift+click picks a whole loop, Shift+Ctrl+click a ring." },
|
||||
{ icon: "◎", title: "Holes for screws and nuts", text: "Round, rectangle, triangle or hexagon. M2 to M8 screw and nut sizes ready, with a little room added so the print actually fits." },
|
||||
{ icon: "▤", title: "Copies and thickness", text: "Copies in a row or round in a circle. Make a surface thick, or a solid hollow with walls exactly as thick as you need." },
|
||||
{ icon: "🖌", title: "Paint and stickers", text: "Paint on the model or on its flat picture. Paste any picture (Ctrl+V) and it goes on like a sticker that bends over edges and corners." },
|
||||
{ icon: "✐", title: "Paint and stickers", text: "Paint on the model or on its flat picture. Paste any picture (Ctrl+V) and it goes on like a sticker that bends over edges and corners." },
|
||||
{ icon: "▦", title: "Layout tab", text: "Move, turn and size the flat frames of the surface by hand. The paint follows, so the model looks the same." },
|
||||
{ icon: "⎙", title: "Made for 3D printing", text: "Your printer's plate in the view, sizes in millimetres, checks for holes and flipped faces with fix buttons, overhangs shown red, weight and filament." },
|
||||
{ icon: "☀", title: "See it rendered", text: "Solid view for modelling, a real renderer with sky, sun and soft shadows when you want to see it properly. Save a picture with F12." },
|
||||
{ icon: "🗀", title: "Folders and groups", text: "Layers with folders inside folders, drag and drop, show/hide and lock whole groups, and a button on every row that brings the camera to it." },
|
||||
{ icon: "📱", title: "On your phone too", text: "Not a viewer: the whole modeller. Big buttons, one finger turns the view, two move and pinch to zoom, holding still is a right-click. Files through the phone's own picker." },
|
||||
{ icon: "💾", title: "Never lose work", text: "Its own packed .fry file, autosave in the background, recovery after a crash, and a Save / Don't save / Cancel when closing." },
|
||||
{ icon: "▭", title: "Folders and groups", text: "Layers with folders inside folders, drag and drop, show/hide and lock whole groups, and a button on every row that brings the camera to it." },
|
||||
{ icon: "▯", title: "On your phone too", text: "Not a viewer: the whole modeller. Big buttons, one finger turns the view, two move and pinch to zoom, holding still is a right-click. Files through the phone's own picker." },
|
||||
{ icon: "⟲", title: "Never lose work", text: "Its own packed .fry file, autosave in the background, recovery after a crash, and a Save / Don't save / Cancel when closing." },
|
||||
],
|
||||
|
||||
shots: [
|
||||
|
|
|
|||
92
docs/fonts/OFL-Inter.txt
Normal file
92
docs/fonts/OFL-Inter.txt
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
Copyright (c) 2016 The Inter Project Authors (https://github.com/rsms/inter)
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION AND CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
93
docs/fonts/OFL-JetBrainsMono.txt
Normal file
93
docs/fonts/OFL-JetBrainsMono.txt
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
Copyright 2020 The JetBrains Mono Project Authors (https://github.com/JetBrains/JetBrainsMono)
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
https://openfontlicense.org
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
BIN
docs/fonts/inter-latin-wght-normal.woff2
Normal file
BIN
docs/fonts/inter-latin-wght-normal.woff2
Normal file
Binary file not shown.
BIN
docs/fonts/jetbrains-mono-latin-wght-normal.woff2
Normal file
BIN
docs/fonts/jetbrains-mono-latin-wght-normal.woff2
Normal file
Binary file not shown.
|
|
@ -78,7 +78,7 @@
|
|||
|
||||
<footer>
|
||||
<p>Fry3DX, made by <a id="author" href="#"></a>.</p>
|
||||
<p class="muted">Open source under the Apache License 2.0 · Sky pictures from Poly Haven (CC0).</p>
|
||||
<p class="muted">Open source under the Apache License 2.0 · Sky pictures from Poly Haven (CC0) · Fonts: Inter, JetBrains Mono (OFL).</p>
|
||||
</footer>
|
||||
|
||||
<script src="content.js"></script>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
/* The site's own fonts (SIL Open Font License, fonts/OFL-*.txt), so it looks the same everywhere. */
|
||||
@font-face { font-family: "Inter"; src: url("fonts/inter-latin-wght-normal.woff2") format("woff2"); font-weight: 100 900; font-display: swap; }
|
||||
@font-face { font-family: "JetBrains Mono"; src: url("fonts/jetbrains-mono-latin-wght-normal.woff2") format("woff2"); font-weight: 100 800; font-display: swap; }
|
||||
:root {
|
||||
--bg: #161618;
|
||||
--panel: #1f1f23;
|
||||
|
|
@ -16,7 +19,7 @@ body {
|
|||
margin: 0;
|
||||
background: var(--bg);
|
||||
color: var(--ink);
|
||||
font: 16px/1.6 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
|
||||
font: 16px/1.6 "Inter", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
|
||||
}
|
||||
a { color: var(--hot); text-decoration: none; }
|
||||
a:hover { text-decoration: underline; }
|
||||
|
|
@ -80,15 +83,15 @@ section:last-child { border-bottom: 0; }
|
|||
.keys { border-collapse: collapse; width: 100%; }
|
||||
.keys td { padding: 7px 0; border-bottom: 1px solid var(--line); }
|
||||
.keys td:first-child { width: 45%; }
|
||||
kbd { font: .85rem ui-monospace, "DejaVu Sans Mono", monospace; background: var(--panel2); border: 1px solid var(--line); border-bottom-width: 2px; border-radius: 5px; padding: 2px 7px; }
|
||||
kbd { font: .85rem "JetBrains Mono", ui-monospace, Consolas, monospace; background: var(--panel2); border: 1px solid var(--line); border-bottom-width: 2px; border-radius: 5px; padding: 2px 7px; }
|
||||
|
||||
.downloads { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 14px; margin-bottom: 28px; }
|
||||
.download { display: flex; flex-direction: column; gap: 4px; padding: 18px 20px; border-radius: var(--radius); background: var(--panel); border: 1px solid var(--line); color: var(--ink); }
|
||||
.download:hover { text-decoration: none; border-color: var(--hot); }
|
||||
.download strong { font-size: 1.15rem; color: var(--hot); }
|
||||
.download .file { font: .85rem ui-monospace, "DejaVu Sans Mono", monospace; color: #cfcdc7; }
|
||||
.download .file { font: .85rem "JetBrains Mono", ui-monospace, Consolas, monospace; color: #cfcdc7; }
|
||||
.code { position: relative; background: #0f0f11; border: 1px solid var(--line); border-left: 3px solid var(--hot); border-radius: var(--radius); }
|
||||
.code pre { margin: 0; padding: 18px 20px; overflow-x: auto; font: .92rem/1.7 ui-monospace, "DejaVu Sans Mono", monospace; color: #d6d4cd; }
|
||||
.code pre { margin: 0; padding: 18px 20px; overflow-x: auto; font: .92rem/1.7 "JetBrains Mono", ui-monospace, Consolas, monospace; color: #d6d4cd; }
|
||||
.copy { position: absolute; top: 10px; right: 10px; font: inherit; font-size: .85rem; color: var(--ink); background: var(--panel2); border: 1px solid var(--line); border-radius: 6px; padding: 4px 12px; cursor: pointer; }
|
||||
.copy:hover { border-color: var(--hot); }
|
||||
|
||||
|
|
|
|||
|
|
@ -16,6 +16,9 @@ add_custom_target(keel_version COMMAND ${CMAKE_COMMAND} -DSRC=${PROJECT_SOURCE_D
|
|||
add_dependencies(keel keel_version)
|
||||
target_include_directories(keel PRIVATE ${CMAKE_BINARY_DIR}/generated)
|
||||
target_link_libraries(keel PRIVATE stb miniaudio opusfile)
|
||||
if(ANDROID)
|
||||
target_link_libraries(keel PUBLIC log) # logcat
|
||||
endif()
|
||||
target_link_libraries(keel PUBLIC zstd_seekable glm::glm yapf SDL3::SDL3 volk vk-bootstrap::vk-bootstrap GPUOpen::VulkanMemoryAllocator imgui)
|
||||
target_compile_definitions(keel PUBLIC GLM_FORCE_DEPTH_ZERO_TO_ONE GLM_ENABLE_EXPERIMENTAL VK_NO_PROTOTYPES)
|
||||
keel_add_shaders(keel shaders/box.vert shaders/box.frag shaders/mesh.vert shaders/mesh.frag shaders/depth.frag shaders/cull.comp shaders/froxel.comp shaders/froxel_rt.comp shaders/gi.comp shaders/skinned.vert
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ Cvar vid_mode("vid_mode", "0", CVAR_ARCHIVE, "0 = window, 1 = borderless fullscr
|
|||
Cvar vid_width("vid_width", "1600", CVAR_ARCHIVE, "Window / fullscreen width");
|
||||
Cvar vid_height("vid_height", "900", CVAR_ARCHIVE, "Window / fullscreen height");
|
||||
Cvar r_renderer("r_renderer", "vulkan", CVAR_ARCHIVE,
|
||||
"Graphics API: vulkan, opengl (4.6), or the cut-down fallbacks for old GPUs: opengl40, opengl31 (restart needed)");
|
||||
"Graphics API: vulkan, opengl (4.6), or the cut-down fallbacks for old GPUs: opengl40, opengl31, gles (OpenGL ES 3.0) (restart needed)");
|
||||
Cvar hotreload("hotreload", "1", CVAR_ARCHIVE,
|
||||
"Reload textures, models, sounds, scripts and the map when their files change (with tools/watch.py compiling as you save)");
|
||||
Cvar fps_max_menu("fps_max_menu", "120", CVAR_ARCHIVE, "Frame rate limit in menus and while paused (0 = same as fps_max)");
|
||||
|
|
@ -283,12 +283,25 @@ int runClient(Game& game) {
|
|||
const int w = std::max(vid_width.i(), 640), h = std::max(vid_height.i(), 480);
|
||||
// The API asked for, then the others: the modern pair, then the fallbacks for old GPUs.
|
||||
const std::string want = r_renderer.str();
|
||||
const RenderApi first = want == "opengl" ? RenderApi::OpenGL : want == "opengl40" ? RenderApi::OpenGL40 : want == "opengl31" ? RenderApi::OpenGL31 : RenderApi::Vulkan;
|
||||
// (A phone has only Vulkan and OpenGL ES.)
|
||||
RenderApi first = want == "opengl" ? RenderApi::OpenGL : want == "opengl40" ? RenderApi::OpenGL40 : want == "opengl31" ? RenderApi::OpenGL31
|
||||
: want == "gles" ? RenderApi::GLES30 : RenderApi::Vulkan;
|
||||
#if defined(__ANDROID__)
|
||||
if (first != RenderApi::Vulkan) first = RenderApi::GLES30;
|
||||
const std::initializer_list<RenderApi> all{RenderApi::Vulkan, RenderApi::GLES30};
|
||||
#else
|
||||
const std::initializer_list<RenderApi> all{RenderApi::Vulkan, RenderApi::OpenGL, RenderApi::OpenGL40, RenderApi::OpenGL31, RenderApi::GLES30};
|
||||
#endif
|
||||
std::vector<RenderApi> order{first};
|
||||
for (RenderApi a : {RenderApi::Vulkan, RenderApi::OpenGL, RenderApi::OpenGL40, RenderApi::OpenGL31})
|
||||
if (std::find(order.begin(), order.end(), a) == order.end() && (first != RenderApi::OpenGL31 || a == RenderApi::OpenGL31)) order.push_back(a);
|
||||
for (RenderApi a : all)
|
||||
if (std::find(order.begin(), order.end(), a) == order.end() && (first != RenderApi::OpenGL31 || a == RenderApi::OpenGL31 || a == RenderApi::GLES30)) order.push_back(a);
|
||||
bool ok = false;
|
||||
for (RenderApi api : order) {
|
||||
if (api == RenderApi::GLES30) { // (before the window: a phone picks its surface's kind when it's made)
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
|
||||
}
|
||||
ok = window.init(game.title(), w, h, api != RenderApi::Vulkan) && renderer.init(window, r_validation.b(), api);
|
||||
if (ok) {
|
||||
if (api != first)
|
||||
|
|
|
|||
|
|
@ -19,8 +19,8 @@ namespace keel {
|
|||
class Window;
|
||||
using TextureId = uint32_t;
|
||||
|
||||
// OpenGL40/OpenGL31: the cut-down fallback for old GPUs (rhi/gl/gl_legacy.cpp).
|
||||
enum class RenderApi { Vulkan, OpenGL, OpenGL40, OpenGL31 };
|
||||
// OpenGL40/OpenGL31/GLES30: the cut-down fallback for old GPUs and phones (rhi/gl/gl_legacy.cpp).
|
||||
enum class RenderApi { Vulkan, OpenGL, OpenGL40, OpenGL31, GLES30 };
|
||||
|
||||
// Texture slots. 0 and 1 are created by the renderer; 2-4 are the backend's render targets
|
||||
// (sampled through the same table); loaded textures start at kFirstTextureSlot.
|
||||
|
|
@ -235,6 +235,6 @@ public:
|
|||
|
||||
std::unique_ptr<RenderBackend> createVulkanBackend();
|
||||
std::unique_ptr<RenderBackend> createOpenGLBackend();
|
||||
std::unique_ptr<RenderBackend> createLegacyOpenGLBackend(int major, int minor);
|
||||
std::unique_ptr<RenderBackend> createLegacyOpenGLBackend(int major, int minor, bool es = false);
|
||||
|
||||
} // namespace keel
|
||||
|
|
|
|||
|
|
@ -114,6 +114,7 @@ bool Renderer::init(const Window& window, bool validation, RenderApi api) {
|
|||
case RenderApi::OpenGL: backend_ = createOpenGLBackend(); break;
|
||||
case RenderApi::OpenGL40: backend_ = createLegacyOpenGLBackend(4, 0); break;
|
||||
case RenderApi::OpenGL31: backend_ = createLegacyOpenGLBackend(3, 1); break;
|
||||
case RenderApi::GLES30: backend_ = createLegacyOpenGLBackend(3, 0, true); break;
|
||||
default: backend_ = createVulkanBackend(); break;
|
||||
}
|
||||
if (!backend_->init(window, validation)) {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
// The fallback renderer for old GPUs: OpenGL 4.0 or 3.1, GLSL 1.40, nothing newer — bound
|
||||
// The fallback renderer for old GPUs: OpenGL 4.0 or 3.1 (GLSL 1.40), or OpenGL ES 3.0 for phones
|
||||
// without Vulkan (GLSL ES 3.00: no skinning, as it has no texture buffers), nothing newer — bound
|
||||
// textures, vertex attributes, one uniform block for the frame, a texture buffer for skinning.
|
||||
// Two tiers, cut down from the main renderer:
|
||||
// 4.0 lightmaps, sun with cascaded shadows (PCF), HDR sky, fog, normal maps, GGX highlights,
|
||||
|
|
@ -38,13 +39,11 @@ namespace {
|
|||
X(void, Viewport, (GLint, GLint, GLsizei, GLsizei)) \
|
||||
X(void, Clear, (GLbitfield)) \
|
||||
X(void, ClearColor, (GLfloat, GLfloat, GLfloat, GLfloat)) \
|
||||
X(void, ClearDepth, (GLdouble)) \
|
||||
X(void, DepthFunc, (GLenum)) \
|
||||
X(void, DepthMask, (GLboolean)) \
|
||||
X(void, BlendFuncSeparate, (GLenum, GLenum, GLenum, GLenum)) \
|
||||
X(void, PolygonOffset, (GLfloat, GLfloat)) \
|
||||
X(void, PixelStorei, (GLenum, GLint)) \
|
||||
X(void, DrawBuffer, (GLenum)) \
|
||||
X(void, ReadBuffer, (GLenum)) \
|
||||
X(void, ReadPixels, (GLint, GLint, GLsizei, GLsizei, GLenum, GLenum, void*)) \
|
||||
X(void, GenBuffers, (GLsizei, GLuint*)) \
|
||||
|
|
@ -59,7 +58,6 @@ namespace {
|
|||
X(void, ActiveTexture, (GLenum)) \
|
||||
X(void, TexImage2D, (GLenum, GLint, GLint, GLsizei, GLsizei, GLint, GLenum, GLenum, const void*)) \
|
||||
X(void, TexParameteri, (GLenum, GLenum, GLint)) \
|
||||
X(void, TexBuffer, (GLenum, GLenum, GLuint)) \
|
||||
X(void, GenFramebuffers, (GLsizei, GLuint*)) \
|
||||
X(void, DeleteFramebuffers, (GLsizei, const GLuint*)) \
|
||||
X(void, BindFramebuffer, (GLenum, GLuint)) \
|
||||
|
|
@ -84,7 +82,6 @@ namespace {
|
|||
X(GLuint, CreateProgram, (void)) \
|
||||
X(void, AttachShader, (GLuint, GLuint)) \
|
||||
X(void, BindAttribLocation, (GLuint, GLuint, const GLchar*)) \
|
||||
X(void, BindFragDataLocation, (GLuint, GLuint, const GLchar*)) \
|
||||
X(void, LinkProgram, (GLuint)) \
|
||||
X(void, GetProgramiv, (GLuint, GLenum, GLint*)) \
|
||||
X(void, GetProgramInfoLog, (GLuint, GLsizei, GLsizei*, GLchar*)) \
|
||||
|
|
@ -96,10 +93,19 @@ namespace {
|
|||
X(void, Uniform1i, (GLint, GLint)) \
|
||||
X(void, Uniform4fv, (GLint, GLsizei, const GLfloat*)) \
|
||||
X(void, UniformMatrix4fv, (GLint, GLsizei, GLboolean, const GLfloat*))
|
||||
// Desktop GL only, or ES only: whichever there is.
|
||||
#define KEEL_GLX_OPTIONAL(X) \
|
||||
X(void, ClearDepth, (GLdouble)) \
|
||||
X(void, ClearDepthf, (GLfloat)) \
|
||||
X(void, DrawBuffer, (GLenum)) \
|
||||
X(void, DrawBuffers, (GLsizei, const GLenum*)) \
|
||||
X(void, TexBuffer, (GLenum, GLenum, GLuint)) \
|
||||
X(void, BindFragDataLocation, (GLuint, GLuint, const GLchar*))
|
||||
|
||||
namespace glx {
|
||||
#define KEEL_GLX_DECLARE(ret, name, params) using name##_t = ret(APIENTRY*) params; name##_t name = nullptr;
|
||||
KEEL_GLX_FUNCTIONS(KEEL_GLX_DECLARE)
|
||||
KEEL_GLX_OPTIONAL(KEEL_GLX_DECLARE)
|
||||
#undef KEEL_GLX_DECLARE
|
||||
bool load() {
|
||||
std::string missing;
|
||||
|
|
@ -108,12 +114,15 @@ bool load() {
|
|||
if (!name) missing += " gl" #name;
|
||||
KEEL_GLX_FUNCTIONS(KEEL_GLX_LOAD)
|
||||
#undef KEEL_GLX_LOAD
|
||||
#define KEEL_GLX_LOAD_OPTIONAL(ret, name, params) name = reinterpret_cast<name##_t>(SDL_GL_GetProcAddress("gl" #name));
|
||||
KEEL_GLX_OPTIONAL(KEEL_GLX_LOAD_OPTIONAL)
|
||||
#undef KEEL_GLX_LOAD_OPTIONAL
|
||||
if (!missing.empty()) log::warn("OpenGL (legacy): missing functions:{}", missing);
|
||||
return missing.empty();
|
||||
}
|
||||
} // namespace glx
|
||||
|
||||
// ── Shaders (GLSL 1.40; TIER is 1 for GL 3.1, 2 for 4.0) ──
|
||||
// ── Shaders (GLSL 1.40 or ES 3.00; TIER is 1 for GL 3.1, 2 for 4.0 and ES 3.0) ──
|
||||
const char* kFrameBlock = R"(
|
||||
layout(std140) uniform Frame { // (GLSL 1.40: no instance name, so the members say F_)
|
||||
mat4 F_cascades[4];
|
||||
|
|
@ -126,12 +135,16 @@ const char* kMeshVert = R"(
|
|||
in vec3 a_pos; in vec3 a_normal; in vec2 a_uv; in vec2 a_lmuv; in uvec4 a_joints; in vec4 a_weights;
|
||||
uniform mat4 u_viewProj, u_model;
|
||||
uniform int u_skinned, u_firstJoint;
|
||||
uniform samplerBuffer u_joints;
|
||||
out vec3 v_normal, v_world; out vec2 v_uv, v_lmuv;
|
||||
#ifdef GL_ES
|
||||
mat4 joint(uint j) { return u_model; } // (never skinned here)
|
||||
#else
|
||||
uniform samplerBuffer u_joints;
|
||||
mat4 joint(uint j) {
|
||||
int b = (u_firstJoint + int(j)) * 4;
|
||||
return mat4(texelFetch(u_joints, b), texelFetch(u_joints, b + 1), texelFetch(u_joints, b + 2), texelFetch(u_joints, b + 3));
|
||||
}
|
||||
#endif
|
||||
void main() {
|
||||
mat4 m = u_model;
|
||||
if (u_skinned != 0)
|
||||
|
|
@ -345,6 +358,33 @@ out vec4 o_color;
|
|||
void main() { o_color = vec4(u_color.rgb * (0.5 + 0.5 * max(dot(v_normal, normalize(vec3(0.4, 0.3, 0.85))), 0.0)), u_color.a); }
|
||||
)";
|
||||
|
||||
// Sprites and streaks (the grid, the axes, particles): a quad facing the camera, textured, unlit.
|
||||
const char* kSpriteVert = R"(
|
||||
uniform mat4 u_viewProj;
|
||||
uniform vec4 u_posSize, u_right, u_up; // right.w: turn (radians)
|
||||
out vec2 v_uv;
|
||||
const vec2 kQuad[6] = vec2[](vec2(-1,-1), vec2(1,-1), vec2(1,1), vec2(-1,-1), vec2(1,1), vec2(-1,1));
|
||||
void main() {
|
||||
vec2 q = kQuad[gl_VertexID];
|
||||
float c = cos(u_right.w), s = sin(u_right.w);
|
||||
vec2 r = vec2(c * q.x - s * q.y, s * q.x + c * q.y);
|
||||
vec3 world = u_posSize.xyz + (u_right.xyz * r.x + u_up.xyz * r.y) * u_posSize.w;
|
||||
v_uv = vec2(q.x * 0.5 + 0.5, 0.5 - q.y * 0.5);
|
||||
gl_Position = u_viewProj * vec4(world, 1.0);
|
||||
gl_Position.z = gl_Position.z * 2.0 - gl_Position.w;
|
||||
}
|
||||
)";
|
||||
const char* kSpriteFrag = R"(
|
||||
in vec2 v_uv;
|
||||
uniform sampler2D u_albedo;
|
||||
uniform vec4 u_color;
|
||||
out vec4 o_color;
|
||||
void main() {
|
||||
vec4 t = texture(u_albedo, v_uv);
|
||||
o_color = vec4(t.rgb * u_color.rgb, clamp((t.a - 0.03) / 0.97, 0.0, 1.0) * u_color.a);
|
||||
}
|
||||
)";
|
||||
|
||||
const char* kTonemapVert = R"(
|
||||
out vec2 v_uv;
|
||||
void main() {
|
||||
|
|
@ -376,13 +416,13 @@ constexpr int kMaxLegacyLights = 16;
|
|||
struct Program {
|
||||
GLuint id = 0;
|
||||
GLint viewProj = -1, model = -1, skinned = -1, firstJoint = -1, tint = -1, params = -1, plane = -1, flags = -1, cube = -1,
|
||||
invViewProj = -1, center = -1, extents = -1, color = -1, exposure = -1;
|
||||
invViewProj = -1, center = -1, extents = -1, color = -1, exposure = -1, posSize = -1, right = -1, up = -1;
|
||||
};
|
||||
|
||||
class LegacyGLBackend final : public RenderBackend {
|
||||
public:
|
||||
explicit LegacyGLBackend(int major, int minor) : major_(major), minor_(minor), tier_(major >= 4 ? 2 : 1) {
|
||||
name_ = "OpenGL " + std::to_string(major) + "." + std::to_string(minor) + " (fallback)";
|
||||
LegacyGLBackend(int major, int minor, bool es) : major_(major), minor_(minor), tier_(major >= 4 || es ? 2 : 1), es_(es) {
|
||||
name_ = std::string(es ? "OpenGL ES " : "OpenGL ") + std::to_string(major) + "." + std::to_string(minor) + " (fallback)";
|
||||
}
|
||||
const char* name() const override { return name_.c_str(); }
|
||||
GpuInfo gpuInfo() const override { return gpu_; }
|
||||
|
|
@ -393,8 +433,8 @@ public:
|
|||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, major_);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, minor_);
|
||||
// 3.1 has no profiles; 4.0 asks for core.
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, major_ >= 4 ? SDL_GL_CONTEXT_PROFILE_CORE : 0);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, major_ >= 4 ? 0 : SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, es_ ? SDL_GL_CONTEXT_PROFILE_ES : major_ >= 4 ? SDL_GL_CONTEXT_PROFILE_CORE : 0);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, major_ >= 4 || es_ ? 0 : SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG);
|
||||
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
|
||||
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 0);
|
||||
context_ = SDL_GL_CreateContext(window_);
|
||||
|
|
@ -409,23 +449,29 @@ public:
|
|||
glx::PixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
||||
glx::PixelStorei(GL_PACK_ALIGNMENT, 1);
|
||||
|
||||
const std::string header = "#version 140\n#define TIER " + std::to_string(tier_) + "\n" + kFrameBlock;
|
||||
const std::string header = std::string(es_ ? "#version 300 es\nprecision highp float;\nprecision highp int;\nprecision highp sampler2D;\n"
|
||||
"precision highp sampler2DShadow;\n"
|
||||
: "#version 140\n") +
|
||||
"#define TIER " + std::to_string(tier_) + "\n" + kFrameBlock;
|
||||
mesh_ = link(header + kMeshVert, header + kMeshFrag);
|
||||
depth_ = link(header + kMeshVert, header + kDepthFrag);
|
||||
sky_ = link(header + kSkyVert, header + kSkyFrag);
|
||||
box_ = link(header + kBoxVert, header + kBoxFrag);
|
||||
tonemap_ = link(header + kTonemapVert, header + kTonemapFrag);
|
||||
if (!mesh_.id || !depth_.id || !sky_.id || !box_.id || !tonemap_.id) return false;
|
||||
sprite_ = link(header + kSpriteVert, header + kSpriteFrag);
|
||||
if (!mesh_.id || !depth_.id || !sky_.id || !box_.id || !tonemap_.id || !sprite_.id) return false;
|
||||
|
||||
glx::GenBuffers(1, &frameUbo_);
|
||||
glx::BindBuffer(GL_UNIFORM_BUFFER, frameUbo_);
|
||||
glx::BufferData(GL_UNIFORM_BUFFER, sizeof(FrameBlock), nullptr, GL_DYNAMIC_DRAW);
|
||||
if (glx::TexBuffer && !es_) {
|
||||
glx::GenBuffers(1, &jointBuffer_);
|
||||
glx::BindBuffer(GL_TEXTURE_BUFFER, jointBuffer_);
|
||||
glx::BufferData(GL_TEXTURE_BUFFER, kMaxJointsPerFrame * sizeof(glm::mat4), nullptr, GL_DYNAMIC_DRAW);
|
||||
glx::GenTextures(1, &jointTexture_);
|
||||
glx::BindTexture(GL_TEXTURE_BUFFER, jointTexture_);
|
||||
glx::TexBuffer(GL_TEXTURE_BUFFER, GL_RGBA32F, jointBuffer_);
|
||||
}
|
||||
glx::GenVertexArrays(1, &emptyVao_);
|
||||
|
||||
// The sun's shadow map: 2×2 cascades, smaller on the 3.1 tier.
|
||||
|
|
@ -442,7 +488,9 @@ public:
|
|||
glx::GenFramebuffers(1, &shadowFbo_);
|
||||
glx::BindFramebuffer(GL_FRAMEBUFFER, shadowFbo_);
|
||||
glx::FramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, shadowMap_, 0);
|
||||
glx::DrawBuffer(GL_NONE);
|
||||
const GLenum none = GL_NONE;
|
||||
if (glx::DrawBuffer) glx::DrawBuffer(GL_NONE);
|
||||
else if (glx::DrawBuffers) glx::DrawBuffers(1, &none);
|
||||
glx::ReadBuffer(GL_NONE);
|
||||
glx::BindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
|
||||
|
|
@ -450,7 +498,7 @@ public:
|
|||
ImGui::CreateContext();
|
||||
ImGui::GetIO().IniFilename = nullptr;
|
||||
ImGui::StyleColorsDark();
|
||||
imgui_ = ImGui_ImplSDL3_InitForOpenGL(window_, context_) && ImGui_ImplOpenGL3_Init("#version 140");
|
||||
imgui_ = ImGui_ImplSDL3_InitForOpenGL(window_, context_) && ImGui_ImplOpenGL3_Init(es_ ? "#version 300 es" : "#version 140");
|
||||
log::info("Fallback renderer: tier {} ({})", tier_, tier_ >= 2 ? "shadows, normal maps, highlights, sky reflections, 16 lights" : "lightmaps, sun shadows, sky");
|
||||
return imgui_;
|
||||
}
|
||||
|
|
@ -584,7 +632,7 @@ private:
|
|||
glx::AttachShader(p.id, f);
|
||||
const char* attrs[] = {"a_pos", "a_normal", "a_uv", "a_lmuv", "a_joints", "a_weights"};
|
||||
for (GLuint i = 0; i < 6; ++i) glx::BindAttribLocation(p.id, i, attrs[i]);
|
||||
glx::BindFragDataLocation(p.id, 0, "o_color");
|
||||
if (glx::BindFragDataLocation) glx::BindFragDataLocation(p.id, 0, "o_color"); // (ES: the only output is 0)
|
||||
glx::LinkProgram(p.id);
|
||||
glx::DeleteShader(v);
|
||||
glx::DeleteShader(f);
|
||||
|
|
@ -603,6 +651,7 @@ private:
|
|||
p.viewProj = u("u_viewProj"), p.model = u("u_model"), p.skinned = u("u_skinned"), p.firstJoint = u("u_firstJoint");
|
||||
p.tint = u("u_tint"), p.params = u("u_params"), p.plane = u("u_plane"), p.flags = u("u_flags"), p.cube = u("u_cube");
|
||||
p.invViewProj = u("u_invViewProj"), p.center = u("u_center"), p.extents = u("u_extents"), p.color = u("u_color"), p.exposure = u("u_exposure");
|
||||
p.posSize = u("u_posSize"), p.right = u("u_right"), p.up = u("u_up");
|
||||
// Fixed texture units.
|
||||
glx::UseProgram(p.id);
|
||||
const struct { const char* name; GLint unit; } units[] = {{"u_albedo", 0}, {"u_lightmap", 1}, {"u_material", 2}, {"u_skyVis", 3},
|
||||
|
|
@ -628,13 +677,18 @@ private:
|
|||
void ensureTargets(uint32_t w, uint32_t h) {
|
||||
if (sceneFbo_ && w == targetW_ && h == targetH_) return;
|
||||
releaseTargets();
|
||||
// HDR, or (a phone that can't draw into half floats) plain 8 bits: brights clip.
|
||||
auto colour = [&](bool hdr) {
|
||||
if (sceneTex_) glx::DeleteTextures(1, &sceneTex_);
|
||||
glx::GenTextures(1, &sceneTex_);
|
||||
glx::BindTexture(GL_TEXTURE_2D, sceneTex_);
|
||||
glx::TexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, GLsizei(w), GLsizei(h), 0, GL_RGBA, GL_FLOAT, nullptr);
|
||||
glx::TexImage2D(GL_TEXTURE_2D, 0, hdr ? GL_RGBA16F : GL_RGBA8, GLsizei(w), GLsizei(h), 0, GL_RGBA, hdr ? GL_HALF_FLOAT : GL_UNSIGNED_BYTE, nullptr);
|
||||
glx::TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glx::TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glx::TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glx::TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
};
|
||||
colour(!ldrTarget_);
|
||||
glx::GenTextures(1, &sceneDepth_);
|
||||
glx::BindTexture(GL_TEXTURE_2D, sceneDepth_);
|
||||
glx::TexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT24, GLsizei(w), GLsizei(h), 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, nullptr);
|
||||
|
|
@ -644,6 +698,12 @@ private:
|
|||
glx::BindFramebuffer(GL_FRAMEBUFFER, sceneFbo_);
|
||||
glx::FramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, sceneTex_, 0);
|
||||
glx::FramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, sceneDepth_, 0);
|
||||
if (!ldrTarget_ && glx::CheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
|
||||
log::warn("OpenGL (legacy): can't draw into half floats; the scene is 8 bits");
|
||||
ldrTarget_ = true;
|
||||
colour(false);
|
||||
glx::FramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, sceneTex_, 0);
|
||||
}
|
||||
if (glx::CheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) log::error("OpenGL (legacy): scene framebuffer incomplete");
|
||||
glx::BindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
targetW_ = w, targetH_ = h;
|
||||
|
|
@ -656,10 +716,11 @@ private:
|
|||
|
||||
std::string name_;
|
||||
int major_, minor_, tier_;
|
||||
bool es_ = false, ldrTarget_ = false;
|
||||
SDL_Window* window_ = nullptr;
|
||||
SDL_GLContext context_ = nullptr;
|
||||
bool imgui_ = false;
|
||||
Program mesh_, depth_, sky_, box_, tonemap_;
|
||||
Program mesh_, depth_, sky_, box_, tonemap_, sprite_;
|
||||
std::vector<GLuint> textures_;
|
||||
std::vector<Model> models_;
|
||||
GLuint frameUbo_ = 0, jointBuffer_ = 0, jointTexture_ = 0, emptyVao_ = 0;
|
||||
|
|
@ -724,12 +785,14 @@ void LegacyGLBackend::render(FramePacket& p) {
|
|||
glx::BindBuffer(GL_UNIFORM_BUFFER, frameUbo_);
|
||||
glx::BufferSubData(GL_UNIFORM_BUFFER, 0, sizeof fb, &fb);
|
||||
glx::BindBufferBase(GL_UNIFORM_BUFFER, 0, frameUbo_);
|
||||
if (!p.joints.empty()) {
|
||||
if (!p.joints.empty() && jointBuffer_) {
|
||||
glx::BindBuffer(GL_TEXTURE_BUFFER, jointBuffer_);
|
||||
glx::BufferSubData(GL_TEXTURE_BUFFER, 0, GLsizeiptr(std::min(p.joints.size(), size_t(kMaxJointsPerFrame)) * sizeof(glm::mat4)), p.joints.data());
|
||||
}
|
||||
if (jointTexture_) {
|
||||
glx::ActiveTexture(GL_TEXTURE6);
|
||||
glx::BindTexture(GL_TEXTURE_BUFFER, jointTexture_);
|
||||
}
|
||||
glx::Disable(GL_CULL_FACE);
|
||||
glx::Disable(GL_BLEND);
|
||||
glx::Enable(GL_DEPTH_TEST);
|
||||
|
|
@ -739,7 +802,7 @@ void LegacyGLBackend::render(FramePacket& p) {
|
|||
// ── Sun shadows (light shadows aren't drawn on this renderer) ──
|
||||
glx::BindFramebuffer(GL_FRAMEBUFFER, shadowFbo_);
|
||||
glx::Viewport(0, 0, GLsizei(shadowSize_), GLsizei(shadowSize_));
|
||||
glx::ClearDepth(1.0);
|
||||
glx::ClearDepth ? glx::ClearDepth(1.0) : glx::ClearDepthf(1.0f);
|
||||
glx::Clear(GL_DEPTH_BUFFER_BIT);
|
||||
glx::Enable(GL_POLYGON_OFFSET_FILL);
|
||||
glx::PolygonOffset(2.5f, 2.0f);
|
||||
|
|
@ -789,6 +852,31 @@ void LegacyGLBackend::render(FramePacket& p) {
|
|||
glx::DepthMask(GL_TRUE);
|
||||
glx::Disable(GL_BLEND);
|
||||
}
|
||||
if (!p.sprites.empty() || !p.glowSprites.empty()) { // depth-tested, not written; blended, then added on
|
||||
glx::UseProgram(sprite_.id);
|
||||
glx::UniformMatrix4fv(sprite_.viewProj, 1, GL_FALSE, glm::value_ptr(p.viewProj));
|
||||
glx::BindVertexArray(emptyVao_);
|
||||
glx::Enable(GL_BLEND);
|
||||
glx::DepthMask(GL_FALSE);
|
||||
auto draw = [&](const std::vector<gpu::Sprite>& sprites) {
|
||||
for (const gpu::Sprite& sp : sprites) {
|
||||
uint32_t tex = 0;
|
||||
std::memcpy(&tex, &sp.up.w, 4); // (the texture's index, as the float's bits)
|
||||
bindTexture(0, TextureId(tex));
|
||||
glx::Uniform4fv(sprite_.posSize, 1, glm::value_ptr(sp.posSize));
|
||||
glx::Uniform4fv(sprite_.color, 1, glm::value_ptr(sp.color));
|
||||
glx::Uniform4fv(sprite_.right, 1, glm::value_ptr(sp.right));
|
||||
glx::Uniform4fv(sprite_.up, 1, glm::value_ptr(sp.up));
|
||||
glx::DrawArrays(GL_TRIANGLES, 0, 6);
|
||||
}
|
||||
};
|
||||
glx::BlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
|
||||
draw(p.sprites);
|
||||
glx::BlendFuncSeparate(GL_SRC_ALPHA, GL_ONE, GL_ZERO, GL_ONE);
|
||||
draw(p.glowSprites);
|
||||
glx::DepthMask(GL_TRUE);
|
||||
glx::Disable(GL_BLEND);
|
||||
}
|
||||
|
||||
// ── Tonemap to the window, then UI ──
|
||||
glx::BindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
|
|
@ -817,6 +905,6 @@ void LegacyGLBackend::render(FramePacket& p) {
|
|||
}
|
||||
} // namespace
|
||||
|
||||
std::unique_ptr<RenderBackend> createLegacyOpenGLBackend(int major, int minor) { return std::make_unique<LegacyGLBackend>(major, minor); }
|
||||
std::unique_ptr<RenderBackend> createLegacyOpenGLBackend(int major, int minor, bool es) { return std::make_unique<LegacyGLBackend>(major, minor, es); }
|
||||
|
||||
} // namespace keel
|
||||
|
|
|
|||
Loading…
Reference in a new issue