17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-30 20:10:29 +01:00

Move modern OpenGL renderer to rendering/opengl46 and rename classes

This commit is contained in:
2026-10-15 01:12:05 +02:00
parent 0e10ce4d0b
commit 61bfc6eb1a
27 changed files with 220 additions and 219 deletions

View File

@@ -0,0 +1,323 @@
/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
#include "stdafx.h"
#include "rendering/opengl46/opengl46geometrybank.h"
#include "utilities/Logs.h"
namespace gfx {
// opengl vao/vbo-based variant of the geometry bank
// create() subclass details
void
opengl46_vaogeometrybank::create_( gfx::geometry_handle const &Geometry ) {
// adding a chunk means we'll be (re)building the buffer, which will fill the chunk records, amongst other things.
// thus we don't need to initialize the values here
m_chunkrecords.emplace_back( chunk_record() );
// kiss the existing buffer goodbye, new overall data size means we'll be making a new one
delete_buffer();
}
// replace() subclass details
void
opengl46_vaogeometrybank::replace_( gfx::geometry_handle const &Geometry ) {
auto &chunkrecord = m_chunkrecords[ Geometry.chunk - 1 ];
chunkrecord.is_good = false;
// if the overall length of the chunk didn't change we can get away with reusing the old buffer...
if( geometry_bank::chunk( Geometry ).vertices.size() != chunkrecord.vertex_count ) {
// ...but otherwise we'll need to allocate a new one
// TBD: we could keep and reuse the old buffer also if the new chunk is smaller than the old one,
// but it'd require some extra tracking and work to keep all chunks up to date; also wasting vram; may be not worth it?
delete_buffer();
}
}
void opengl46_vaogeometrybank::setup_buffer()
{
if( m_vertexbuffer ) { return; }
// if there's no buffer, we'll have to make one
// NOTE: this isn't exactly optimal in terms of ensuring the gfx card doesn't stall waiting for the data
// may be better to initiate upload earlier (during update phase) and trust this effort won't go to waste
if( true == m_chunks.empty() ) { return; }
std::size_t
vertexcount{ 0 },
indexcount{ 0 };
auto chunkiterator = m_chunks.cbegin();
bool has_userdata{ false };
for( auto &chunkrecord : m_chunkrecords ) {
// fill records for all chunks, based on the chunk data
chunkrecord.is_good = false; // if we're re-creating buffer, chunks might've been uploaded in the old one
chunkrecord.vertex_offset = vertexcount;
chunkrecord.vertex_count = chunkiterator->vertices.size();
vertexcount += chunkrecord.vertex_count;
chunkrecord.index_offset = indexcount;
chunkrecord.index_count = chunkiterator->indices.size();
chunkrecord.has_userdata = !chunkiterator->userdata.empty();
indexcount += chunkrecord.index_count;
has_userdata |= chunkrecord.has_userdata;
++chunkiterator;
}
// the odds for all created chunks to get replaced with empty ones are quite low, but the possibility does exist
if( vertexcount == 0 ) { return; }
if( !m_vao ) {
m_vao.emplace();
}
m_vao->bind();
// try to set up the buffers we need:
// optional index buffer...
if( indexcount > 0 ) {
m_indexbuffer.emplace();
m_indexbuffer->allocate( gl::buffer::ELEMENT_ARRAY_BUFFER, indexcount * sizeof( gfx::basic_index ), GL_STATIC_DRAW );
if( ::glGetError() == GL_OUT_OF_MEMORY ) {
ErrorLog( "openGL error: out of memory; failed to create a geometry index buffer" );
throw std::bad_alloc();
}
m_vao->setup_ebo( *m_indexbuffer );
}
else {
gl::buffer::unbind( gl::buffer::ELEMENT_ARRAY_BUFFER );
}
// ...and geometry buffer
m_vertexbuffer.emplace();
// NOTE: we're using static_draw since it's generally true for all we have implemented at the moment
// TODO: allow to specify usage hint at the object creation, and pass it here
const int vertex_size = has_userdata ? sizeof( gfx::basic_vertex ) + sizeof(gfx::vertex_userdata) : sizeof(gfx::basic_vertex);
m_vertexbuffer->allocate( gl::buffer::ARRAY_BUFFER, static_cast<int>(vertexcount * vertex_size), GL_STATIC_DRAW );
if( ::glGetError() == GL_OUT_OF_MEMORY ) {
ErrorLog( "openGL error: out of memory; failed to create a geometry buffer" );
throw std::bad_alloc();
}
m_vertex_count = static_cast<int>(vertexcount);
setup_attrib();
if(has_userdata)
setup_userdata();
}
void
opengl46_vaogeometrybank::setup_attrib(size_t offset)
{
m_vao->setup_attrib( *m_vertexbuffer, 0, 3, GL_FLOAT, sizeof( basic_vertex ), offsetof(basic_vertex, position) + offset * sizeof( basic_vertex ) );
// NOTE: normal and color streams share the data
m_vao->setup_attrib( *m_vertexbuffer, 1, 3, GL_FLOAT, sizeof( basic_vertex ), offsetof(basic_vertex, normal) + offset * sizeof( basic_vertex ) );
m_vao->setup_attrib( *m_vertexbuffer, 2, 2, GL_FLOAT, sizeof( basic_vertex ), offsetof(basic_vertex, texture) + offset * sizeof( basic_vertex ) );
m_vao->setup_attrib( *m_vertexbuffer, 3, 4, GL_FLOAT, sizeof( basic_vertex ), offsetof(basic_vertex, tangent) + offset * sizeof( basic_vertex ) );
}
void opengl46_vaogeometrybank::setup_userdata(size_t offset) {
const size_t offset_evaluated = m_vertex_count * sizeof(basic_vertex) + offset * sizeof( vertex_userdata );
m_vao->setup_attrib( *m_vertexbuffer, 4, 4, GL_FLOAT, sizeof( vertex_userdata ), offsetof(vertex_userdata, data) + offset_evaluated );}
// draw() subclass details
// NOTE: units and stream parameters are unused, but they're part of (legacy) interface
// TBD: specialized bank/manager pair without the cruft?
std::size_t
opengl46_vaogeometrybank::draw_( gfx::geometry_handle const &Geometry, gfx::stream_units const &Units, unsigned int const Streams )
{
setup_buffer();
auto &chunkrecord = m_chunkrecords.at(Geometry.chunk - 1);
// sanity check; shouldn't be needed but, eh
if( chunkrecord.vertex_count == 0 )
return 0;
auto const &chunk = gfx::geometry_bank::chunk( Geometry );
if( !chunkrecord.is_good ) {
m_vao->bind();
// we may potentially need to upload new buffer data before we can draw it
if( chunkrecord.index_count > 0 ) {
m_indexbuffer->upload( gl::buffer::ELEMENT_ARRAY_BUFFER, chunk.indices.data(), chunkrecord.index_offset * sizeof( gfx::basic_index ), chunkrecord.index_count * sizeof( gfx::basic_index ) );
}
m_vertexbuffer->upload( gl::buffer::ARRAY_BUFFER, chunk.vertices.data(), chunkrecord.vertex_offset * sizeof( gfx::basic_vertex ), chunkrecord.vertex_count * sizeof( gfx::basic_vertex ) );
if(chunkrecord.has_userdata)
m_vertexbuffer->upload(gl::buffer::ARRAY_BUFFER, chunk.userdata.data(),
static_cast<int>(m_vertex_count * sizeof(gfx::basic_vertex) + chunkrecord.vertex_offset * sizeof(gfx::vertex_userdata)),
static_cast<int>(chunkrecord.vertex_count * sizeof(gfx::vertex_userdata)));
chunkrecord.is_good = true;
}
// render
if( chunkrecord.index_count > 0 ) {
if (glDrawRangeElementsBaseVertex) {
m_vao->bind();
::glDrawRangeElementsBaseVertex(
chunk.type,
0, chunkrecord.vertex_count,
chunkrecord.index_count, GL_UNSIGNED_INT, reinterpret_cast<void const *>( chunkrecord.index_offset * sizeof( gfx::basic_index ) ),
chunkrecord.vertex_offset );
}
else {
setup_attrib(chunkrecord.vertex_offset);
m_vao->bind();
::glDrawRangeElements(
chunk.type,
0, chunkrecord.vertex_count,
chunkrecord.index_count, GL_UNSIGNED_INT, reinterpret_cast<void const *>( chunkrecord.index_offset * sizeof( gfx::basic_index ) ) );
}
}
else {
m_vao->bind();
::glDrawArrays( chunk.type, chunkrecord.vertex_offset, chunkrecord.vertex_count );
}
/*
m_vao->unbind();
*/
auto const vertexcount { ( chunkrecord.index_count > 0 ? chunkrecord.index_count : chunkrecord.vertex_count ) };
switch( chunk.type ) {
case GL_TRIANGLES: { return vertexcount / 3; }
case GL_TRIANGLE_STRIP: { return vertexcount - 2; }
default: { return 0; }
}
}
// draw_instanced() subclass details — single GL instanced draw for InstanceCount instances.
// The vertex shader reads per-instance modelview from instance_ubo[gl_InstanceID].
std::size_t
opengl46_vaogeometrybank::draw_instanced_( gfx::geometry_handle const &Geometry, gfx::stream_units const &Units, std::size_t const InstanceCount, unsigned int const Streams )
{
setup_buffer();
auto &chunkrecord = m_chunkrecords.at(Geometry.chunk - 1);
if( chunkrecord.vertex_count == 0 ) { return 0; }
auto const &chunk = gfx::geometry_bank::chunk( Geometry );
if( !chunkrecord.is_good ) {
m_vao->bind();
if( chunkrecord.index_count > 0 ) {
m_indexbuffer->upload( gl::buffer::ELEMENT_ARRAY_BUFFER, chunk.indices.data(), chunkrecord.index_offset * sizeof( gfx::basic_index ), chunkrecord.index_count * sizeof( gfx::basic_index ) );
}
m_vertexbuffer->upload( gl::buffer::ARRAY_BUFFER, chunk.vertices.data(), chunkrecord.vertex_offset * sizeof( gfx::basic_vertex ), chunkrecord.vertex_count * sizeof( gfx::basic_vertex ) );
if( chunkrecord.has_userdata ) {
m_vertexbuffer->upload( gl::buffer::ARRAY_BUFFER, chunk.userdata.data(),
static_cast<int>(m_vertex_count * sizeof(gfx::basic_vertex) + chunkrecord.vertex_offset * sizeof(gfx::vertex_userdata)),
static_cast<int>(chunkrecord.vertex_count * sizeof(gfx::vertex_userdata)) );
}
chunkrecord.is_good = true;
}
GLsizei const inst_count = static_cast<GLsizei>(InstanceCount);
if( chunkrecord.index_count > 0 ) {
// GLES 3.0 context doesn't provide the base vertex variant, same fallbacks as in draw_()
if( glDrawElementsInstancedBaseVertex ) {
m_vao->bind();
::glDrawElementsInstancedBaseVertex(
chunk.type,
chunkrecord.index_count, GL_UNSIGNED_INT,
reinterpret_cast<void const *>( chunkrecord.index_offset * sizeof( gfx::basic_index ) ),
inst_count,
chunkrecord.vertex_offset );
}
else {
setup_attrib( chunkrecord.vertex_offset );
m_vao->bind();
::glDrawElementsInstanced(
chunk.type,
chunkrecord.index_count, GL_UNSIGNED_INT,
reinterpret_cast<void const *>( chunkrecord.index_offset * sizeof( gfx::basic_index ) ),
inst_count );
}
}
else {
m_vao->bind();
::glDrawArraysInstanced( chunk.type, chunkrecord.vertex_offset, chunkrecord.vertex_count, inst_count );
}
auto const vertexcount { ( chunkrecord.index_count > 0 ? chunkrecord.index_count : chunkrecord.vertex_count ) };
std::size_t prims = 0;
switch( chunk.type ) {
case GL_TRIANGLES: prims = vertexcount / 3; break;
case GL_TRIANGLE_STRIP: prims = ( vertexcount > 2 ? vertexcount - 2 : 0 ); break;
default: prims = 0; break;
}
return prims * InstanceCount;
}
// draw_batch() subclass details — neighbouring non-indexed chunks of the same type are sent with a single GL call.
// The chunks are drawn in the supplied order, same as with a draw_() call for each of them.
std::size_t
opengl46_vaogeometrybank::draw_batch_( gfx::geometry_handle const *First, gfx::geometry_handle const *Last, gfx::stream_units const &Units, unsigned int const Streams )
{
std::size_t count { 0 };
if( !glMultiDrawArrays ) {
// GLES context doesn't provide the call
for( ; First != Last; ++First ) { count += draw_( *First, Units, Streams ); }
return count;
}
setup_buffer();
std::array<GLint, 64> offsets;
std::array<GLsizei, 64> sizes;
GLsizei pending { 0 };
unsigned int pendingtype { 0 };
auto const flush = [ & ]() {
if( pending == 0 ) { return; }
m_vao->bind();
if( pending == 1 ) { ::glDrawArrays( pendingtype, offsets[ 0 ], sizes[ 0 ] ); }
else { ::glMultiDrawArrays( pendingtype, offsets.data(), sizes.data(), pending ); }
pending = 0;
};
for( ; First != Last; ++First ) {
auto const &chunkrecord = m_chunkrecords.at( First->chunk - 1 );
if( ( false == chunkrecord.is_good )
|| ( chunkrecord.index_count > 0 ) ) {
// chunks which wait for upload of their data and indexed chunks take the regular route
flush();
count += draw_( *First, Units, Streams );
continue;
}
auto const type { gfx::geometry_bank::chunk( *First ).type };
if( ( pending == static_cast<GLsizei>( offsets.size() ) )
|| ( ( pending > 0 ) && ( type != pendingtype ) ) ) {
flush();
}
pendingtype = type;
offsets[ pending ] = static_cast<GLint>( chunkrecord.vertex_offset );
sizes[ pending ] = static_cast<GLsizei>( chunkrecord.vertex_count );
++pending;
switch( type ) {
case GL_TRIANGLES: { count += chunkrecord.vertex_count / 3; break; }
case GL_TRIANGLE_STRIP: { count += ( chunkrecord.vertex_count > 2 ? chunkrecord.vertex_count - 2 : 0 ); break; }
default: { break; }
}
}
flush();
return count;
}
// release () subclass details
void
opengl46_vaogeometrybank::release_() {
delete_buffer();
}
void
opengl46_vaogeometrybank::delete_buffer() {
m_vao.reset();
m_vertexbuffer.reset();
m_indexbuffer.reset();
// NOTE: since we've deleted the buffer all chunks it held were rendered invalid as well
// instead of clearing their state here we're delaying it until new buffer is created to avoid looping through chunk records twice
}
} // namespace gfx

View File

@@ -0,0 +1,82 @@
/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
#pragma once
#include "rendering/geometrybank.h"
#include "gl/buffer.h"
#include "gl/vao.h"
namespace gfx {
// opengl vao/vbo-based variant of the geometry bank
class opengl46_vaogeometrybank : public geometry_bank {
public:
// constructors:
opengl46_vaogeometrybank() = default;
// destructor
~opengl46_vaogeometrybank() {
delete_buffer(); }
// methods:
static
void
reset() {;}
private:
// types:
struct chunk_record {
std::size_t vertex_offset{ 0 }; // beginning of the chunk vertex data as offset from the beginning of the last established buffer
std::size_t vertex_count{ 0 }; // size of the chunk in the last established buffer
std::size_t index_offset{ 0 };
std::size_t index_count{ 0 };
bool has_userdata{ false };
bool is_good{ false }; // true if local content of the chunk matches the data on the opengl end
};
typedef std::vector<chunk_record> chunkrecord_sequence;
// methods:
// create() subclass details
void
create_( gfx::geometry_handle const &Geometry ) override;
// replace() subclass details
void
replace_( gfx::geometry_handle const &Geometry ) override;
// draw() subclass details
auto
draw_( gfx::geometry_handle const &Geometry, gfx::stream_units const &Units, unsigned int const Streams ) -> std::size_t override;
// draw_instanced() subclass details — issues glDrawElementsInstancedBaseVertex
auto
draw_instanced_( gfx::geometry_handle const &Geometry, gfx::stream_units const &Units, std::size_t const InstanceCount, unsigned int const Streams ) -> std::size_t override;
// draw_batch() subclass details — issues glMultiDrawArrays for runs of non-indexed chunks of the same type
auto
draw_batch_( gfx::geometry_handle const *First, gfx::geometry_handle const *Last, gfx::stream_units const &Units, unsigned int const Streams ) -> std::size_t override;
// release() subclass details
void
release_() override;
void
setup_buffer();
void
setup_attrib(size_t offset = 0);
void
setup_userdata(size_t offset = 0);
void
delete_buffer();
// members:
std::optional<gl::buffer> m_vertexbuffer; // vertex buffer data on the opengl end
std::optional<gl::buffer> m_indexbuffer; // index buffer data on the opengl end
std::optional<gl::vao> m_vao;
chunkrecord_sequence m_chunkrecords; // helper data for all stored geometry chunks, in matching order
int m_vertex_count;
};
} // namespace gfx

View File

@@ -0,0 +1,22 @@
/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
#include "stdafx.h"
#include "rendering/opengl46/opengl46light.h"
void opengl46_light::apply_intensity(float const Factor) {
factor = Factor;
}
void opengl46_light::apply_angle() const { /* nothing to do */ }
//---------------------------------------------------------------------------

View File

@@ -0,0 +1,28 @@
/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
#pragma once
#include "rendering/light.h"
struct opengl46_light : public basic_light {
GLuint id{(GLuint)-1};
float factor;
void apply_intensity(float const Factor = 1.0f);
void apply_angle() const;
opengl46_light &operator=(basic_light const &Right) {
basic_light::operator=(Right);
return *this; }
};
//---------------------------------------------------------------------------

View File

@@ -0,0 +1,140 @@
/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
#include "stdafx.h"
#include "rendering/opengl46/opengl46particles.h"
#include "rendering/particles.h"
#include "rendering/openglcamera.h"
#include "simulation/simulation.h"
#include "simulation/simulationenvironment.h"
std::vector<std::pair<glm::vec3, glm::vec2>> const billboard_vertices {
{ { -0.5f, -0.5f, 0.f }, { 0.f, 0.f } },
{ { 0.5f, -0.5f, 0.f }, { 1.f, 0.f } },
{ { 0.5f, 0.5f, 0.f }, { 1.f, 1.f } },
{ { -0.5f, -0.5f, 0.f }, { 0.f, 0.f } },
{ { 0.5f, 0.5f, 0.f }, { 1.f, 1.f } },
{ { -0.5f, 0.5f, 0.f }, { 0.f, 1.f } },
};
void
opengl46_particles::update( opengl_camera const &Camera ) {
if (!Global.Smoke)
return;
m_particlevertices.clear();
// build a list of visible smoke sources
// NOTE: arranged by distance to camera, if we ever need sorting and/or total amount cap-based culling
std::multimap<float, smoke_source const &> sources;
for( auto const &source : simulation::Particles.sequence() ) {
if( false == Camera.visible( source.area() ) ) { continue; }
// NOTE: the distance is negative when the camera is inside the source's bounding area
sources.emplace(
static_cast<float>( glm::length( Camera.position() - source.area().center ) - source.area().radius ),
source );
}
if( true == sources.empty() ) { return; }
// build billboard data for particles from visible sources
auto const camerarotation { glm::mat3( Camera.modelview() ) };
particle_vertex vertex;
for( auto const &[sourcekey, sourcedata] : sources ) {
auto const particlecolor {
glm::clamp(
sourcedata.color()
* ( glm::vec3 { Global.DayLight.ambient }
+ 0.35f * glm::vec3{ Global.DayLight.diffuse } ) * simulation::Environment.light_intensity(),
glm::vec3{ 0.f }, glm::vec3{ 1.f } ) };
auto const &particles { sourcedata.sequence() };
// TODO: put sanity cap on the overall amount of particles that can be drawn
auto const sizestep { 256.0 * billboard_vertices.size() };
m_particlevertices.reserve(
static_cast<std::size_t>( sizestep * std::ceil( static_cast<double>(m_particlevertices.size()) + ( static_cast<double>(particles.size() * billboard_vertices.size()) ) / sizestep ) ) );
for( auto const &particle : particles ) {
// TODO: particle color support
vertex.color[ 0 ] = particlecolor.r;
vertex.color[ 1 ] = particlecolor.g;
vertex.color[ 2 ] = particlecolor.b;
vertex.color.a = std::clamp(particle.opacity, 0.0f, 1.0f);
auto const offset { glm::vec3{ particle.position - Camera.position() } };
auto const rotation { glm::angleAxis( particle.rotation, glm::vec3{ 0.f, 0.f, 1.f } ) };
for( auto const &[billboardoffset, billboardtexture] : billboard_vertices ) {
vertex.position = offset + ( rotation * billboardoffset * particle.size ) * camerarotation;
vertex.texture = billboardtexture;
m_particlevertices.emplace_back( vertex );
}
}
}
// ship the billboard data to the gpu:
// make sure we have enough room...
if( m_buffercapacity < m_particlevertices.size() ) {
m_buffercapacity = m_particlevertices.size();
if (!m_buffer)
m_buffer.emplace();
m_buffer->allocate(gl::buffer::ARRAY_BUFFER,
m_buffercapacity * sizeof(particle_vertex), GL_STREAM_DRAW);
}
if (m_buffer) {
// ...send the data...
m_buffer->upload(gl::buffer::ARRAY_BUFFER,
m_particlevertices.data(), 0, m_particlevertices.size() * sizeof(particle_vertex));
}
}
std::size_t
opengl46_particles::render() {
if( false == Global.Smoke ) { return 0; }
if( m_buffercapacity == 0 ) { return 0; }
if( m_particlevertices.empty() ) { return 0; }
if (!m_vao) {
m_vao.emplace();
m_vao->setup_attrib(*m_buffer, 0, 3, GL_FLOAT, sizeof(particle_vertex), 0);
m_vao->setup_attrib(*m_buffer, 1, 4, GL_FLOAT, sizeof(particle_vertex), 12);
m_vao->setup_attrib(*m_buffer, 2, 2, GL_FLOAT, sizeof(particle_vertex), 28);
gl::buffer::unbind(gl::buffer::ARRAY_BUFFER);
gl::vao::unbind();
}
if (!m_shader) {
gl::shader vert("smoke.vert");
gl::shader frag("smoke.frag");
auto prog = new gl::program({vert, frag});
m_shader = std::unique_ptr<gl::program>(prog);
}
m_buffer->bind(gl::buffer::ARRAY_BUFFER);
m_shader->bind();
m_vao->bind();
glDrawArrays(GL_TRIANGLES, 0, static_cast<GLsizei>(m_particlevertices.size()));
gl::program::unbind();
gl::vao::unbind();
gl::buffer::unbind(gl::buffer::ARRAY_BUFFER);
return m_particlevertices.size() / 6;
}
//---------------------------------------------------------------------------

View File

@@ -0,0 +1,54 @@
/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
#pragma once
#include "gl/buffer.h"
#include "gl/vao.h"
#include "gl/shader.h"
class opengl_camera;
// particle data visualizer
class opengl46_particles {
public:
// constructors
opengl46_particles() = default;
// methods
void
update( opengl_camera const &Camera );
std::size_t
render( );
private:
// types
struct particle_vertex {
glm::vec3 position; // 3d space
glm::vec4 color; // rgba, unsigned byte format
glm::vec2 texture; // uv space
};
/*
using sourcedistance_pair = std::pair<smoke_source *, float>;
using source_sequence = std::vector<sourcedistance_pair>;
*/
using particlevertex_sequence = std::vector<particle_vertex>;
// methods
// members
/*
source_sequence m_sources; // list of particle sources visible in current render pass, with their respective distances to the camera
*/
particlevertex_sequence m_particlevertices; // geometry data of visible particles, generated on the cpu end
std::optional<gl::buffer> m_buffer;
std::optional<gl::vao> m_vao;
std::unique_ptr<gl::program> m_shader;
std::size_t m_buffercapacity{ 0 }; // total capacity of the last established buffer
};
//---------------------------------------------------------------------------

View File

@@ -0,0 +1,159 @@
/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
#include "stdafx.h"
#include "rendering/opengl46/opengl46precipitation.h"
#include "utilities/Globals.h"
#include "rendering/renderer.h"
#include "simulation/simulationenvironment.h"
opengl46_precipitation::~opengl46_precipitation() {
// TODO: release allocated resources
}
void
opengl46_precipitation::create( int const Tesselation ) {
m_vertices.clear();
m_uvs.clear();
m_indices.clear();
auto const heightfactor { 10.f }; // height-to-radius factor
auto const verticaltexturestretchfactor { 1.5f }; // crude motion blur
// create geometry chunk
auto const latitudes { 3 }; // just a cylinder with end cones
auto const longitudes { Tesselation };
auto const longitudehalfstep { 0.5f * static_cast<float>( 2.0 * M_PI * 1.f / longitudes ) }; // for crude uv correction
std::uint16_t index = 0;
// auto const radius { 25.f }; // cylinder radius
std::vector radii { 25.f, 10.f, 5.f, 1.f };
for( auto radius : radii ) {
for( int i = 0; i <= latitudes; ++i ) {
auto const latitude{ static_cast<float>( M_PI * ( -0.5f + (float)( i ) / latitudes ) ) };
auto const z{ std::sin( latitude ) };
auto const zr{ std::cos( latitude ) };
for( int j = 0; j <= longitudes; ++j ) {
// NOTE: for the first and last row half of the points we create end up unused but, eh
auto const longitude{ static_cast<float>( 2.0 * M_PI * (float)( j ) / longitudes ) };
auto const x{ std::cos( longitude ) };
auto const y{ std::sin( longitude ) };
// NOTE: cartesian to opengl swap would be: -x, -z, -y
m_vertices.emplace_back( glm::vec3( -x * zr, -z * heightfactor, -y * zr ) * radius );
// uvs
// NOTE: first and last row receives modified u values to deal with limitation of mapping onto triangles
auto u = (
i == 0 ? longitude + longitudehalfstep :
i == latitudes ? longitude - longitudehalfstep :
longitude );
m_uvs.emplace_back(
u / ( 2.0 * M_PI ) * radius,
1.f - (float)( i ) / latitudes * radius * heightfactor * 0.5f / verticaltexturestretchfactor );
if( ( i == 0 ) || ( j == 0 ) ) {
// initial edge of the dome, don't start indices yet
++index;
}
else {
// the end cones are built from one triangle of each quad, the middle rows use both
if( i < latitudes ) {
m_indices.emplace_back( index - 1 - ( longitudes + 1 ) );
m_indices.emplace_back( index - 1 );
m_indices.emplace_back( index );
}
if( i > 1 ) {
m_indices.emplace_back( index );
m_indices.emplace_back( index - ( longitudes + 1 ) );
m_indices.emplace_back( index - 1 - ( longitudes + 1 ) );
}
++index;
}
} // longitude
} // latitude
} // radius
}
void
opengl46_precipitation::update() {
if (!m_shader)
{
gl::shader vert("precipitation.vert");
gl::shader frag("precipitation.frag");
m_shader.emplace(std::vector<std::reference_wrapper<const gl::shader>>({vert, frag}));
}
if (!m_vertexbuffer) {
m_vao.emplace();
m_vao->bind();
if( m_vertices.empty() ) {
// create visualization mesh
create( 18 );
}
// build the buffers
m_vertexbuffer.emplace();
m_vertexbuffer->allocate(gl::buffer::ARRAY_BUFFER, m_vertices.size() * sizeof( glm::vec3 ), GL_STATIC_DRAW);
m_vertexbuffer->upload(gl::buffer::ARRAY_BUFFER, m_vertices.data(), 0, m_vertices.size() * sizeof( glm::vec3 ));
m_vao->setup_attrib(*m_vertexbuffer, 0, 3, GL_FLOAT, sizeof(glm::vec3), 0);
m_uvbuffer.emplace();
m_uvbuffer->allocate(gl::buffer::ARRAY_BUFFER, m_uvs.size() * sizeof( glm::vec2 ), GL_STATIC_DRAW);
m_uvbuffer->upload(gl::buffer::ARRAY_BUFFER, m_uvs.data(), 0, m_uvs.size() * sizeof( glm::vec2 ));
m_vao->setup_attrib(*m_uvbuffer, 1, 2, GL_FLOAT, sizeof(glm::vec2), 0);
m_indexbuffer.emplace();
m_indexbuffer->allocate(gl::buffer::ELEMENT_ARRAY_BUFFER, m_indices.size() * sizeof( unsigned short ), GL_STATIC_DRAW);
m_indexbuffer->upload(gl::buffer::ELEMENT_ARRAY_BUFFER, m_indices.data(), 0, m_indices.size() * sizeof( unsigned short ));
m_vao->setup_ebo(*m_indexbuffer);
gl::vao::unbind();
// NOTE: vertex and index source data is superfluous past this point, but, eh
}
// TODO: include weather type check in the entry conditions
if( m_overcast == Global.Overcast ) { return; }
m_overcast = Global.Overcast;
std::string const densitysuffix { (
m_overcast < 1.35 ?
"_light" :
"_medium" ) };
if( Global.Weather == "rain:" ) {
m_texture = GfxRenderer->Fetch_Texture( "fx/rain" + densitysuffix );
}
else if( Global.Weather == "snow:" ) {
m_texture = GfxRenderer->Fetch_Texture( "fx/snow" + densitysuffix );
}
}
void
opengl46_precipitation::render() {
if( !m_shader ) { return; }
if( !m_vao ) { return; }
GfxRenderer->Bind_Texture( 0, m_texture );
m_shader->bind();
m_vao->bind();
::glDrawElements( GL_TRIANGLES, static_cast<GLsizei>( m_indices.size() ), GL_UNSIGNED_SHORT, reinterpret_cast<void const*>( 0 ) );
gl::vao::unbind();
}

View File

@@ -0,0 +1,43 @@
/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
#pragma once
#include "model/Texture.h"
#include "gl/vao.h"
#include "gl/shader.h"
class opengl46_precipitation {
public:
// constructors
opengl46_precipitation() = default;
// destructor
~opengl46_precipitation();
// methods
void
update();
void
render();
private:
// methods
void create( int const Tesselation );
// members
std::vector<glm::vec3> m_vertices;
std::vector<glm::vec2> m_uvs;
std::vector<std::uint16_t> m_indices;
texture_handle m_texture { -1 };
float m_overcast { -1.f }; // cached overcast level, difference from current state triggers texture update
std::optional<gl::buffer> m_vertexbuffer;
std::optional<gl::buffer> m_uvbuffer;
std::optional<gl::buffer> m_indexbuffer;
std::optional<gl::program> m_shader;
std::optional<gl::vao> m_vao;
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,594 @@
/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
#pragma once
#include "rendering/renderer.h"
#include "rendering/openglcamera.h"
#include "rendering/opengl46/opengl46light.h"
#include "rendering/opengl46/opengl46particles.h"
#include "rendering/opengl46/opengl46skydome.h"
#include "rendering/opengl46/opengl46precipitation.h"
#include "simulation/simulationenvironment.h"
#include "scene/scene.h"
#include "world/MemCell.h"
#include "rendering/lightarray.h"
#include "vr/vr_interface.h"
#include "gl/ubo.h"
#include "gl/framebuffer.h"
#include "gl/renderbuffer.h"
#include "gl/postfx.h"
#include "gl/shader.h"
#include "gl/cubemap.h"
#include "gl/glsl_common.h"
#include "gl/pbo.h"
#include "gl/query.h"
#include "rendering/opengl46/opengl46terrain.h"
// bare-bones render controller, in lack of anything better yet
class opengl46_renderer : public gfx_renderer {
public:
// constructors
opengl46_renderer() = default;
// destructor
~opengl46_renderer() {}
// methods
bool
Init( GLFWwindow *Window ) override;
void
Shutdown() override;
bool
AddViewport(const global_settings::extraviewport_config &conf) override;
// main draw call. returns false on error
bool
Render() override;
void
SwapBuffers() override;
inline
float
Framerate() override { return m_framerate; }
// geometry methods
// NOTE: hands-on geometry management is exposed as a temporary measure; ultimately all visualization data should be generated/handled automatically by the renderer itself
// creates a new geometry bank. returns: handle to the bank or NULL
gfx::geometrybank_handle
Create_Bank() override;
// creates a new indexed geometry chunk of specified type from supplied data, in specified bank. returns: handle to the chunk or NULL
gfx::geometry_handle Insert(gfx::index_array &Indices, gfx::vertex_array &Vertices, gfx::userdata_array &Userdata, gfx::geometrybank_handle const &Geometry, int const Type) override;
// creates a new geometry chunk of specified type from supplied data, in specified bank. returns: handle to the chunk or NULL
gfx::geometry_handle Insert(gfx::vertex_array &Vertices, gfx::userdata_array &Userdata, gfx::geometrybank_handle const &Geometry, int const Type) override;
// replaces data of specified chunk with the supplied vertex data, starting from specified offset
bool Replace(gfx::vertex_array &Vertices, gfx::userdata_array &Userdata, gfx::geometry_handle const &Geometry, int const Type, const std::size_t Offset = 0) override;
// adds supplied vertex data at the end of specified chunk
bool Append(gfx::vertex_array &Vertices, gfx::userdata_array &Userdata, gfx::geometry_handle const &Geometry, int const Type) override;
// provides direct access to index data of specfied chunk
gfx::index_array const &
Indices( gfx::geometry_handle const &Geometry ) const override;
// provides direct access to vertex data of specfied chunk
gfx::vertex_array const &
Vertices( gfx::geometry_handle const &Geometry ) const override;
// provides direct access to vertex data of specfied chunk
gfx::userdata_array const &
UserData( gfx::geometry_handle const &Geometry ) const override;
// material methods
material_handle
Fetch_Material( std::string const &Filename, bool const Loadnow = true ) override;
void
Bind_Material( material_handle const Material, TSubModel const *sm = nullptr, lighting_data const *lighting = nullptr ) override;
IMaterial const *
Material( material_handle const Material ) const override;
material_handle
Terrain_Material( material_handle const Reuse, std::vector<gfx::terrain_layer> const &Layers, int const Samples, std::uint8_t const *Weights, glm::vec3 const &Placement ) override;
void
Terrain_Release( material_handle const Material ) override;
// shader methods
auto Fetch_Shader( std::string const &name ) -> std::shared_ptr<gl::program> override;
// texture methods
texture_handle
Fetch_Texture( std::string const &Filename, bool const Loadnow = true, GLint format_hint = GL_SRGB_ALPHA ) override;
void
Bind_Texture( texture_handle const Texture ) override;
void
Bind_Texture( std::size_t const Unit, texture_handle const Texture ) override;
ITexture &
Texture( texture_handle const Texture ) override;
ITexture const &
Texture( texture_handle const Texture ) const override;
// utility methods
void
Pick_Control_Callback( std::function<void( TSubModel const *, const glm::vec2 )> Callback ) override;
void
Pick_Node_Callback( std::function<void( scene::basic_node * )> Callback ) override;
TSubModel const *
Pick_Control() const override { return m_pickcontrolitem; }
scene::basic_node const *
Pick_Node() const override { return m_picksceneryitem; }
glm::dvec3
Mouse_Position() const override { return m_worldmousecoordinates; }
bool
Mouse_Hit() const override { return m_mousehit; }
glm::mat4
Camera_View_Matrix() const override { return glm::mat4( glm::mat3( m_colorpass.pass_camera.modelview() ) ); }
glm::mat4
Camera_Projection_Matrix() const override { return m_colorpass.pass_camera.projection(); }
glm::dvec3
Camera_Position() const override { return m_colorpass.pass_camera.position(); }
// maintenance methods
void
Update( double const Deltatime ) override;
bool
Debug_Ui_State(std::optional<bool>) override;
void
Update_Pick_Control() override;
void
Update_Pick_Node() override;
glm::dvec3
Update_Mouse_Position() override;
// debug methods
std::string const &
info_times() const override;
std::string const &
info_stats() const override;
void MakeScreenshot() override;
bool Render_Preview( TAnimModel *Instance, int const Size, int const Margin, bool const Shadows, std::vector<std::uint8_t> &Image ) override;
opengl_material & Material( material_handle const Material );
opengl_material const & Material( TSubModel const * Submodel ) const;
// draws supplied geometry handles
void Draw_Geometry(std::vector<gfx::geometrybank_handle>::iterator begin, std::vector<gfx::geometrybank_handle>::iterator end);
void Draw_Geometry(const gfx::geometrybank_handle &handle);
// material methods
void Bind_Material_Shadow(material_handle const Material);
void Update_AnimModel(TAnimModel *model);
// members
GLenum static const sunlight{0};
static std::unique_ptr<gfx_renderer> create_func();
private:
// types
enum class rendermode
{
none,
color,
shadows,
reflections,
pickcontrols,
pickscenery
};
struct debug_stats
{
int dynamics{0};
int models{0};
int submodels{0};
int paths{0};
int traction{0};
int shapes{0};
int lines{0};
int particles{0};
int drawcalls{0};
int instanced_drawcalls{0}; // drawcalls issued via instanced render path (one per TModel3d batch)
int instances{0}; // total instances drawn via instanced render path
int triangles{0};
debug_stats& operator+=( const debug_stats& Right ) {
dynamics += Right.dynamics;
models += Right.models;
submodels += Right.submodels;
paths += Right.paths;
traction += Right.traction;
shapes += Right.shapes;
lines += Right.lines;
particles += Right.particles;
drawcalls += Right.drawcalls;
instanced_drawcalls += Right.instanced_drawcalls;
instances += Right.instances;
return *this; }
};
using section_sequence = std::vector<scene::basic_section *>;
using distancecell_pair = std::pair<double, scene::basic_cell *>;
using cell_sequence = std::vector<distancecell_pair>;
struct renderpass_config
{
opengl_camera pass_camera;
opengl_camera viewport_camera;
rendermode draw_mode{rendermode::none};
float draw_range{0.0f};
debug_stats draw_stats;
};
struct viewport_config {
int width;
int height;
float draw_range;
bool main = false;
GLFWwindow *window = nullptr; // ogl window context
bool real_window = true; // whether we need to blit onto GLFWwindow surface
bool custom_backbuffer = false; // whether we want to render to our offscreen LDR backbuffer (pipeline required)
bool shadow = false; // include this viewport area in shadowmap
enum vp_type {
normal,
custom,
vr_left,
vr_right
} proj_type;
viewport_proj_config projection;
// main msaa render target for pipeline mode
std::unique_ptr<gl::framebuffer> msaa_fb;
std::unique_ptr<gl::renderbuffer> msaa_rbc;
std::unique_ptr<gl::renderbuffer> msaa_rbv;
std::unique_ptr<gl::renderbuffer> msaa_rbd;
// msaa resolve buffer (when using motion blur)
std::unique_ptr<gl::framebuffer> main_fb;
std::unique_ptr<opengl_texture> main_texv;
std::unique_ptr<opengl_texture> main_texd;
std::unique_ptr<opengl_texture> main_tex;
// final HDR buffer (also serving as msaa resolve buffer when not using motion blur)
std::unique_ptr<gl::framebuffer> main2_fb;
std::unique_ptr<opengl_texture> main2_tex;
// ssao
std::unique_ptr<opengl_texture> ssao_tex;
std::unique_ptr<opengl_texture> ssao_blur_tex;
std::unique_ptr<gl::framebuffer> ssao_fb;
std::unique_ptr<gl::framebuffer> ssao_blur_fb;
// LDR backbuffer for offscreen rendering
std::unique_ptr<gl::framebuffer> backbuffer_fb;
std::unique_ptr<opengl_texture> backbuffer_tex;
bool initialized = false;
};
viewport_config *m_current_viewport = nullptr;
typedef std::vector<opengl46_light> opengllight_array;
// methods
std::unique_ptr<gl::program> make_shader(std::string v, std::string f);
bool Init_caps();
void setup_pass(viewport_config &Viewport, renderpass_config &Config, rendermode const Mode, float const Znear = 0.f, float const Zfar = 1.f, bool const Ignoredebug = false);
void setup_matrices();
void setup_drawing(bool const Alpha = false);
void setup_shadow_unbind_map();
void setup_shadow_bind_map();
void setup_shadow_color( glm::vec4 const &Shadowcolor );
void setup_env_map(gl::cubemap *tex);
void setup_environment_light(TEnvironmentType const Environment = e_flat);
void setup_sunlight_intensity( float const Factor = 1.f);
// runs jobs needed to generate graphics for specified render pass
void Render_pass(viewport_config &vp, rendermode const Mode);
// creates dynamic environment cubemap
bool Render_reflections(viewport_config &vp);
bool Render(world_environment *Environment);
void Render(scene::basic_region *Region);
void Render(section_sequence::iterator First, section_sequence::iterator Last);
void Render(cell_sequence::iterator First, cell_sequence::iterator Last);
void Render(scene::shape_node const &Shape, bool const Ignorerange);
void Render(TAnimModel *Instance);
// batched render path for many TAnimModel instances that share the same TModel3d.
// Caller guarantees every instance has m_instanceable == true (no per-instance lights,
// no submodel animation, no replacable skins). Each call counts as one
// instanced_drawcall in draw_stats and contributes Instances.size() to the
// instances counter. Instances are still individually frustum/distance culled.
void Render_Instanced( TModel3d *Model, std::vector<TAnimModel *> const &Instances );
// adds the bucketed instances of a scene cell to the frame-level buckets drawn by Render_Instanced(),
// less the ones the copies of their bounds show to be out of sight in the current render pass
void Queue_Instances( scene::basic_cell const *Cell );
// makes the copies of the bounds of the instances held by a bucket of a scene cell
void Update_Instance_Bounds( TModel3d const *Model, scene::basic_cell::instance_bucket const &Bucket );
bool Render(TDynamicObject *Dynamic);
bool Render(TModel3d *Model, material_data const *Material, float const Squaredistance, glm::dvec3 const &Position, glm::vec3 const &Angle);
bool Render(TModel3d *Model, material_data const *Material, float const Squaredistance);
void Render(TSubModel *Submodel);
void Render(TTrack *Track);
void Render(scene::basic_cell::path_sequence::const_iterator First, scene::basic_cell::path_sequence::const_iterator Last);
// renders the per-track sleeper instances (TTrack::m_sleeper_local_transforms) via GPU instancing.
// caller must already have the camera-relative world-space transform set on the matrix stack.
// no-op if the track has no sleepermodel, Global.SleeperDistance is 0, or the camera is beyond
// Global.SleeperDistance from the track origin.
void Render_Sleepers( TTrack *Track );
bool Render_cab(TDynamicObject const *Dynamic, float const Lightlevel, bool const Alpha = false);
bool Render_interior( bool const Alpha = false );
bool Render_lowpoly( TDynamicObject *Dynamic, float const Squaredistance, bool const Setup, bool const Alpha = false );
bool Render_coupler_adapter( TDynamicObject *Dynamic, float const Squaredistance, int const End, bool const Alpha = false );
// releases gl textures used only by vehicles farther than gfx.textures.releasedistance times the draw range
void Update_Texture_Release();
// textures used by materials of the sub-models of specified model, replaceable skins excluded
std::vector<texture_handle> const &Model_Textures( TModel3d *Model );
void Render(TMemCell *Memcell);
void Render_particles();
void Render_precipitation();
void Render_vr_models();
void Render_Alpha(scene::basic_region *Region);
void Render_Alpha(cell_sequence::reverse_iterator First, cell_sequence::reverse_iterator Last);
void Render_Alpha(TAnimModel *Instance);
void Render_Alpha(TTraction *Traction);
void Render_Alpha(scene::lines_node const &Lines);
void Render_editor_overlay();
bool Render_Alpha(TDynamicObject *Dynamic);
bool Render_Alpha(TModel3d *Model, material_data const *Material, float const Squaredistance, glm::dvec3 const &Position, glm::vec3 const &Angle);
bool Render_Alpha(TModel3d *Model, material_data const *Material, float const Squaredistance);
void Render_Alpha(TSubModel *Submodel);
void Render_Alpha_text(TSubModel *Submodel); // characters of a text display
void Update_Lights(light_array &Lights);
glm::vec3 pick_color(std::size_t const Index);
std::size_t pick_index(glm::ivec3 const &Color);
bool init_viewport(viewport_config &vp);
void draw(const gfx::geometry_handle &handle);
void draw(std::vector<gfx::geometrybank_handle>::iterator begin, std::vector<gfx::geometrybank_handle>::iterator end);
void draw_debug_ui();
// model previews
// extends provided bounds with the geometry of the submodel and its siblings and children visible in a preview
void Preview_Bounds( TSubModel const *Submodel, glm::dmat4 const &Transform, glm::dvec3 &Min, glm::dvec3 &Max ) const;
// prepares the offscreen targets of model previews for square images of specified size
bool Preview_Targets( int const Size );
// draws the instance on its own with the current render pass settings
void Preview_Model( TAnimModel *Instance, float const Squaredistance, bool const Alpha );
// draws the lit instance into the hdr preview target, marking in its stencil the pixels covered by the model
void Preview_Color( TAnimModel *Instance, float const Squaredistance, int const Size, glm::dvec3 const &Eye, glm::mat4 const &View, glm::mat4 const &Projection, bool const Shadows );
// members
GLFWwindow *m_window{nullptr}; // main window
gfx::geometrybank_manager m_geometry;
material_manager m_materials;
texture_manager m_textures;
opengl46_terrain_materials m_terrainmaterials{m_materials, m_textures};
opengl46_light m_sunlight;
opengllight_array m_lights;
/*
float m_sunandviewangle; // cached dot product of sunlight and camera vectors
*/
gfx::geometry_handle m_billboardgeometry{0, 0};
texture_handle m_glaretexture{-1};
texture_handle m_suntexture{-1};
texture_handle m_moontexture{-1};
texture_handle m_smoketexture{-1};
texture_handle m_headlightstexture{-1};
// main shadowmap resources
int m_shadowbuffersize{2048};
glm::mat4 m_shadowtexturematrix; // conversion from camera-centric world space to light-centric clip space
int m_environmentcubetextureface{0}; // helper, currently processed cube map face
int m_environmentupdateface{6}; // cube map face the environment map update in progress draws next; 6: no update in progress
bool m_environmentready{false}; // the environment map was drawn whole at least once
double m_environmentupdatetime{0}; // time of the most recent environment map update
glm::dvec3 m_environmentupdatelocation; // coordinates of most recent environment map update
opengl46_skydome m_skydomerenderer;
opengl46_precipitation m_precipitationrenderer;
opengl46_particles m_particlerenderer; // particle visualization subsystem
unsigned int m_framestamp; // id of currently rendered gfx frame
float m_framerate;
double m_updateaccumulator{0.0};
std::string m_debugtimestext;
std::string m_pickdebuginfo;
//debug_stats m_debugstats;
std::string m_debugstatstext;
struct simulation_state {
std::string weather;
std::string season;
} m_simulationstate;
glm::vec4 m_baseambient{0.0f, 0.0f, 0.0f, 1.0f};
glm::vec4 m_shadowcolor{colors::shadow};
// TEnvironmentType m_environment { e_flat };
float m_specularopaquescalefactor{1.f};
float m_speculartranslucentscalefactor{1.f};
float m_fogrange = 2000.0f;
renderpass_config m_renderpass; // parameters for current render pass
section_sequence m_sectionqueue; // list of sections in current render pass
cell_sequence m_cellqueue;
// frame-level accumulation of per-cell opaque instance buckets. Each visited
// cell's buckets are merged here keyed by (TModel3d*, skins), so that
// Render_Instanced() runs once per unique model across the whole pass instead
// of once per cell -- collapsing many tiny instanced draws into a few large
// batches. Reused every pass; the vectors are emptied (not freed) at the top of Render(scene::basic_region*).
scene::basic_cell::instance_bucket_map m_frame_instance_buckets;
// textures used by vehicle models, for the release of far textures
std::unordered_map<TModel3d const *, std::vector<texture_handle>> m_modeltextures;
// geometry of neighbouring tracks of a cell which share the material, gathered to be drawn together. reused by each Render(path_sequence)
std::vector<gfx::geometrybank_handle> m_pathbatch;
// copies of instance bounds kept by the buckets of scene cells are good as long as they carry this number.
// it changes with each frame drawn with the scenery editor active, as the editor moves and resizes instances in place
unsigned int m_instanceboundsversion { 1 };
renderpass_config m_colorpass; // parametrs of most recent color pass
std::array<renderpass_config, 3> m_shadowpass; // parametrs of most recent shadowmap pass for each of csm stages
std::vector<TSubModel const *> m_pickcontrolsitems;
std::vector<TSubModel const *> m_picksurfaceitems;
TSubModel const *m_pickcontrolitem{nullptr};
std::vector<scene::basic_node *> m_picksceneryitems;
scene::basic_node *m_picksceneryitem{nullptr};
glm::vec3 m_worldmousecoordinates { 0.f };
bool m_mousehit { false };
#ifdef EU07_USE_DEBUG_CAMERA
renderpass_config m_worldcamera; // debug item
#endif
std::optional<gl::query> m_timequery;
GLuint64 m_gllasttime = 0;
double m_precipitationrotation;
glm::mat4 perspective_projection_raw(float fovy, float aspect, float znear, float zfar);
glm::mat4 perspective_projection(const viewport_proj_config &c, float n, float f, glm::mat4 &frustum);
glm::mat4 ortho_projection(float left, float right, float bottom, float top, float z_near, float z_far);
glm::mat4 ortho_frustumtest_projection(float left, float right, float bottom, float top, float z_near, float z_far);
void editor_ortho_projection(viewport_config const &Viewport, float const Zfar, glm::mat4 &Projection, glm::mat4 &Frustum);
std::vector<std::function<void(TSubModel const *, glm::vec2)>> m_control_pick_requests;
std::vector<std::function<void(scene::basic_node *)>> m_node_pick_requests;
std::unique_ptr<gl::shader> m_vertex_shader;
std::unique_ptr<gl::ubo> scene_ubo;
std::unique_ptr<gl::ubo> model_ubo;
std::unique_ptr<gl::ubo> light_ubo;
// per-instance modelview UBO for GPU-instanced draws.
// Slot 0 is initialized once to identity and never overwritten by non-instanced
// rendering, so existing draws (where gl_InstanceID==0) compute identity*modelview
// in the vertex shader and behave exactly as before. Render_Instanced uploads
// per-instance camera-space root matrices to slots 0..N-1, calls glDrawElementsInstanced*,
// then restores slot 0 to identity for subsequent non-instanced draws.
std::unique_ptr<gl::ubo> instance_ubo;
// when > 0, the renderer is inside a Render_Instanced() call. The draw(handle)
// method routes to glDrawElementsInstanced* with this many instances rather
// than a single regular draw. The vertex shader reads per-instance modelview
// from instance_ubo[gl_InstanceID]. Reset to 0 by Render_Instanced() on exit.
std::size_t m_current_instance_count { 0 };
// persistent scratch buffer for Render_Instanced(): holds the per-instance
// camera-space root modelview matrices for the batch currently being built.
// Kept as a member rather than a function-local so its heap allocation is
// reused across calls -- clear() retains capacity, so once it has grown to
// the largest batch seen, steady-state frames perform no allocation here.
std::vector<glm::mat4> m_instance_modelviews;
// further persistent scratch buffers for Render_Instanced(): culled instances paired with their
// squared distance, and the lod bounds of the drawn model which split them into lod bands
struct instance_survivor {
glm::mat4 modelview;
float distancesquared;
std::uint32_t band;
};
std::vector<instance_survivor> m_instance_survivors;
std::vector<float> m_instance_lodbounds;
std::vector<TSubModel const *> m_instance_lodpending;
// per sort key (lod band and distance slot): first index in m_instance_modelviews (with end sentinel)
// and fill cursor; per lod band: nearest distance
std::vector<std::size_t> m_instance_bandstarts;
std::vector<std::size_t> m_instance_bandcursors;
std::vector<float> m_instance_banddistances;
gl::scene_ubs scene_ubs;
gl::model_ubs model_ubs;
gl::light_ubs light_ubs;
std::unordered_map<std::string, std::shared_ptr<gl::program>> m_shaders;
std::unique_ptr<gl::program> m_line_shader;
gfx::geometrybank_handle m_editor_overlay_bank;
std::vector<gfx::geometry_handle> m_editor_overlay_geometry;
unsigned int m_editor_overlay_revision{0};
std::unique_ptr<gl::program> m_freespot_shader;
std::unique_ptr<gl::program> m_billboard_shader;
std::unique_ptr<gl::program> m_celestial_shader;
std::unique_ptr<gl::program> m_hiddenarea_shader;
std::unique_ptr<gl::program> m_copy_shader;
std::unique_ptr<gl::vao> m_empty_vao;
std::vector<std::unique_ptr<viewport_config>> m_viewports;
std::unique_ptr<gl::postfx> m_pfx_motionblur;
std::unique_ptr<gl::postfx> m_pfx_tonemapping;
std::unique_ptr<gl::postfx> m_pfx_chromaticaberration;
// postfx ssao
std::unique_ptr<gl::postfx> m_pfx_ssao;
std::unique_ptr<gl::postfx> m_pfx_ssao_blur;
std::unique_ptr<gl::postfx> m_pfx_ssao_apply;
std::unique_ptr<opengl_texture> m_ssao_noise_tex;
glm::vec3 m_ssao_kernel[32];
std::unique_ptr<gl::program> m_shadow_shader;
std::unique_ptr<gl::program> m_alpha_shadow_shader;
std::unique_ptr<gl::framebuffer> m_pick_fb;
std::unique_ptr<opengl_texture> m_pick_tex;
std::unique_ptr<gl::renderbuffer> m_pick_rb;
std::unique_ptr<gl::program> m_pick_surface_shader;
std::unique_ptr<gl::program> m_pick_shader;
std::unique_ptr<gl::cubemap> m_empty_cubemap;
std::unique_ptr<gl::framebuffer> m_env_fb;
std::unique_ptr<gl::renderbuffer> m_env_rb;
std::unique_ptr<gl::cubemap> m_env_tex;
std::unique_ptr<gl::framebuffer> m_shadow_fb;
std::unique_ptr<opengl_texture> m_shadow_tex;
std::unique_ptr<gl::pbo> m_picking_pbo;
std::unique_ptr<gl::pbo> m_picking_node_pbo;
std::unique_ptr<gl::pbo> m_depth_pointer_pbo;
std::unique_ptr<gl::framebuffer> m_depth_pointer_fb;
std::unique_ptr<gl::renderbuffer> m_depth_pointer_rb;
material_handle m_invalid_material;
bool m_blendingenabled;
bool m_widelines_supported;
struct headlight_config_s
{
float in_cutoff = 1.005f;
float out_cutoff = 0.993f;
float falloff_linear = 0.15f;
float falloff_quadratic = 0.15f;
float intensity = 1.0f;
float ambient = 0.0f;
};
headlight_config_s headlight_config;
std::unique_ptr<vr_interface> vr;
bool debug_ui_active = false;
// offscreen targets of model previews
struct preview_targets
{
int size { 0 };
std::unique_ptr<opengl_texture> hdr_tex;
std::unique_ptr<gl::renderbuffer> hdr_rbds; // depth and stencil, the stencil marks pixels covered by the model
std::unique_ptr<gl::framebuffer> hdr_fb;
std::unique_ptr<opengl_texture> ldr_tex;
std::unique_ptr<gl::framebuffer> ldr_fb;
} m_preview;
// set while a preview is drawn: submodels faded out entirely are skipped, instead of being drawn invisible over the stencil
bool m_previewing { false };
static bool renderer_register;
class opengl46_imgui_renderer : public imgui_renderer
{
virtual bool Init() override;
virtual void Shutdown() override;
virtual void BeginFrame() override;
virtual void Render() override;
virtual std::uint64_t Create_Image(std::uint8_t const *Rgba, int const Width, int const Height) override;
virtual void Release_Image(std::uint64_t const Image) override;
} m_imgui_renderer;
virtual imgui_renderer* GetImguiRenderer() override {
return &m_imgui_renderer;
}
};
//---------------------------------------------------------------------------

View File

@@ -0,0 +1,67 @@
/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
#include "stdafx.h"
#include "rendering/opengl46/opengl46skydome.h"
#include "simulation/simulationenvironment.h"
void opengl46_skydome::update() {
if (!m_shader) {
gl::shader vert("vbocolor.vert");
gl::shader frag("color.frag");
m_shader.emplace(std::vector<std::reference_wrapper<const gl::shader>>({vert, frag}));
}
auto &skydome { simulation::Environment.skydome() };
if (!m_vertexbuffer) {
m_vao.emplace();
m_vao->bind();
m_vertexbuffer.emplace();
m_vertexbuffer->allocate(gl::buffer::ARRAY_BUFFER, skydome.vertices().size() * sizeof( glm::vec3 ), GL_STATIC_DRAW);
m_vertexbuffer->upload(gl::buffer::ARRAY_BUFFER, skydome.vertices().data(), 0, skydome.vertices().size() * sizeof( glm::vec3 ));
m_vao->setup_attrib(*m_vertexbuffer, 0, 3, GL_FLOAT, sizeof(glm::vec3), 0);
m_coloursbuffer.emplace();
m_coloursbuffer->allocate(gl::buffer::ARRAY_BUFFER, skydome.colors().size() * sizeof( glm::vec3 ), GL_STATIC_DRAW);
m_coloursbuffer->upload(gl::buffer::ARRAY_BUFFER, skydome.colors().data(), 0, skydome.colors().size() * sizeof( glm::vec3 ));
m_vao->setup_attrib(*m_coloursbuffer, 1, 3, GL_FLOAT, sizeof(glm::vec3), 0);
m_indexbuffer.emplace();
m_indexbuffer->allocate(gl::buffer::ELEMENT_ARRAY_BUFFER, skydome.indices().size() * sizeof( unsigned short ), GL_STATIC_DRAW);
m_indexbuffer->upload(gl::buffer::ELEMENT_ARRAY_BUFFER, skydome.indices().data(), 0, skydome.indices().size() * sizeof( unsigned short ));
m_vao->setup_ebo(*m_indexbuffer);
gl::vao::unbind();
}
// ship the current dynamic data to the gpu
if (true == skydome.is_dirty() && m_coloursbuffer) {
// the colour buffer was already initialized, so on this run we update its content
m_coloursbuffer->upload( gl::buffer::ARRAY_BUFFER, skydome.colors().data(), 0, skydome.colors().size() * sizeof( glm::vec3 ) );
skydome.is_dirty() = false;
}
}
// render skydome to screen
void opengl46_skydome::render() {
if( ( !m_shader) || ( !m_vao ) ) { return; }
m_shader->bind();
m_vao->bind();
auto const &skydome { simulation::Environment.skydome() };
::glDrawElements( GL_TRIANGLES, static_cast<GLsizei>( skydome.indices().size() ), GL_UNSIGNED_SHORT, reinterpret_cast<void const*>( 0 ) );
}

View File

@@ -0,0 +1,36 @@
/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
#pragma once
#include "gl/vao.h"
#include "gl/shader.h"
#include "gl/buffer.h"
class opengl46_skydome {
public:
// constructors
opengl46_skydome() = default;
// destructor
~opengl46_skydome() = default;
// methods
// updates data stores on the opengl end. NOTE: unbinds buffers
void update();
// draws the skydome
void render();
private:
// members
std::optional<gl::buffer> m_vertexbuffer;
std::optional<gl::buffer> m_indexbuffer;
std::optional<gl::buffer> m_coloursbuffer;
std::optional<gl::program> m_shader;
std::optional<gl::vao> m_vao;
};

View File

@@ -0,0 +1,434 @@
/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
#include "stdafx.h"
#include "rendering/opengl46/opengl46terrain.h"
#include "rendering/opengl46/opengl46geometrybank.h"
#include "utilities/Globals.h"
#include "utilities/Logs.h"
namespace
{
std::size_t constexpr max_layers{8};
int constexpr initial_capacity{8};
int constexpr copy_attempts{600}; // frames to wait for the textures of a layer to load before it's left with what it has
float constexpr default_repeat{8.f};
// array textures: diffuse, normal maps, specular/gloss maps
std::array<int, 3> constexpr array_sizes{1024, 512, 256};
void bind_for_upload(opengl_texture const &Texture)
{
::glActiveTexture(GL_TEXTURE0);
::glBindTexture(Texture.target, Texture.id);
}
void set_anisotropy(GLenum const Target)
{
if (Global.AnisotropicFiltering >= 0 && (GLAD_GL_EXT_texture_filter_anisotropic || GLAD_GL_ARB_texture_filter_anisotropic))
::glTexParameterf(Target, GL_TEXTURE_MAX_ANISOTROPY_EXT, Global.AnisotropicFiltering);
}
} // namespace
bool opengl46_terrain_materials::init()
{
if (m_state != 0)
return m_state > 0;
m_state = -1;
try
{
m_copy = std::make_unique<gl::postfx>(gl::shader("copy.frag"));
}
catch (gl::shader_exception const &e)
{
ErrorLog("heightmap terrain: can't use the copy shader: " + std::string(e.what()));
return false;
}
m_framebuffer = std::make_unique<gl::framebuffer>();
m_white = make_texture("internal_src:terrain/white", GL_TEXTURE_2D);
{
auto const &white{m_textures.texture(m_white)};
std::uint8_t const texel[4]{255, 255, 255, 255};
bind_for_upload(white);
::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, texel);
}
char const *names[]{"internal_src:terrain/layers_diffuse", "internal_src:terrain/layers_normal", "internal_src:terrain/layers_specgloss"};
for (std::size_t i = 0; i < m_arrays.size(); ++i)
{
auto &array{m_arrays[i]};
array.handle = make_texture(names[i], GL_TEXTURE_2D_ARRAY);
array.size = array_sizes[i];
array.srgb = (i == 0);
array.format = (array.srgb ? GL_SRGB8_ALPHA8 : GL_RGBA8);
make_storage(array, initial_capacity);
}
m_capacity = initial_capacity;
opengl_texture::reset_unit_cache();
// the shader is checked now, so a broken one makes the terrain fall back on plain materials instead of the failure indicator
try
{
GfxRenderer->Fetch_Shader("terrain");
}
catch (gl::shader_exception const &e)
{
ErrorLog("heightmap terrain: can't use the terrain shader: " + std::string(e.what()));
return false;
}
m_state = 1;
return true;
}
texture_handle opengl46_terrain_materials::make_texture(std::string const &Name, GLenum const Target)
{
auto const handle{m_textures.create(Name, false, GL_RGBA)};
auto &texture{m_textures.texture(handle)};
if (texture.id == opengl_texture::invalid_id)
::glGenTextures(1, &texture.id);
texture.target = Target;
// the content is supplied here, the texture manager isn't to load, release or replace it
texture.is_ready = true;
texture.is_static = true;
texture.is_rendertarget = true;
texture.is_texstub = false;
texture.data_state = resource_state::good;
return handle;
}
void opengl46_terrain_materials::make_storage(texture_array &Array, int const Layers)
{
auto &texture{m_textures.texture(Array.handle)};
bind_for_upload(texture);
::glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
::glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
::glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_REPEAT);
::glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_REPEAT);
set_anisotropy(GL_TEXTURE_2D_ARRAY);
::glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, Array.format, Array.size, Array.size, Layers, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
texture.data_width = Array.size;
texture.data_height = Array.size;
texture.layers = Layers;
texture.data_format = Array.format;
texture.data_components = GL_RGBA;
}
int opengl46_terrain_materials::layer_index(gfx::terrain_layer const &Layer)
{
for (auto const &existing : m_layers)
if (existing.material == Layer.material && existing.size == Layer.size)
return existing.index;
layer entry;
entry.material = Layer.material;
entry.size = Layer.size;
entry.index = static_cast<int>(m_layers.size());
auto const &material{m_materials.material(Layer.material)};
entry.repeat = (Layer.size > 0.f ? Layer.size : (material.size.x > 0.f ? material.size.x : default_repeat));
if (material.shader)
{
entry.normalmap = material.shader->texture_conf.count("normalmap") > 0;
entry.specgloss = material.shader->texture_conf.count("specgloss") > 0;
}
entry.reflection = (std::isfinite(material.params[1].z) ? material.params[1].z : 0.f);
m_layers.push_back(entry);
m_pending = true;
if (static_cast<int>(m_layers.size()) > m_capacity)
{
// more room: the arrays are made anew, and every layer copied again
auto const capacity{m_capacity * 2};
for (auto &array : m_arrays)
{
auto &texture{m_textures.texture(array.handle)};
::glDeleteTextures(1, &texture.id);
::glGenTextures(1, &texture.id);
make_storage(array, capacity);
}
m_capacity = capacity;
for (auto &existing : m_layers)
{
existing.copied = false;
existing.attempts = 0;
}
opengl_texture::reset_unit_cache();
}
return entry.index;
}
std::size_t opengl46_terrain_materials::acquire_slot(material_handle const Reuse)
{
if (Reuse != null_handle)
{
auto const lookup{m_slotof.find(Reuse)};
if (lookup != m_slotof.end())
{
m_slots[lookup->second].used = true;
return lookup->second;
}
}
if (false == m_free.empty())
{
auto const index{m_free.back()};
m_free.pop_back();
m_slots[index].used = true;
return index;
}
auto const index{m_slots.size()};
auto const suffix{std::to_string(index)};
slot entry;
entry.weights[0] = make_texture("internal_src:terrain/weights0_" + suffix, GL_TEXTURE_2D);
entry.weights[1] = make_texture("internal_src:terrain/weights1_" + suffix, GL_TEXTURE_2D);
entry.table = make_texture("internal_src:terrain/table_" + suffix, GL_TEXTURE_2D);
// the textures get their content before the material is made of them, which checks they're there
std::uint8_t const blank[4]{0, 0, 0, 0};
for (auto const handle : entry.weights)
{
bind_for_upload(m_textures.texture(handle));
::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, blank);
::glGenerateMipmap(GL_TEXTURE_2D);
}
{
bind_for_upload(m_textures.texture(entry.table));
::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
std::array<float, max_layers * 2 * 4> const zeros{};
::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, static_cast<GLsizei>(max_layers), 2, 0, GL_RGBA, GL_FLOAT, zeros.data());
}
opengl_texture::reset_unit_cache();
auto const name = [&](texture_handle const Handle) { return "internal_src:" + m_textures.texture(Handle).name; };
std::string const definition{
"shader: terrain\n"
"texture1: " + name(m_white) + "\n"
"texture2: " + name(entry.weights[0]) + "\n"
"texture3: " + name(entry.weights[1]) + "\n"
"texture4: " + name(m_arrays[0].handle) + "\n"
"texture5: " + name(m_arrays[1].handle) + "\n"
"texture6: " + name(m_arrays[2].handle) + "\n"
"texture7: " + name(entry.table) + "\n"};
entry.material = m_materials.create_from_text("internal_src:terrain/chunk_" + suffix, definition);
if (entry.material == null_handle || false == m_materials.material(entry.material).is_good)
{
ErrorLog("heightmap terrain: can't make the material of a chunk");
m_state = -1;
return static_cast<std::size_t>(-1);
}
entry.used = true;
m_slots.push_back(entry);
m_slotof.emplace(entry.material, index);
return index;
}
material_handle opengl46_terrain_materials::make(material_handle const Reuse, std::vector<gfx::terrain_layer> const &Layers, int const Samples, std::uint8_t const *Weights, glm::vec3 const &Placement)
{
if (Layers.empty() || false == init())
return null_handle;
auto const count{std::min(Layers.size(), max_layers)};
std::array<int, max_layers> indices{};
for (std::size_t i = 0; i < count; ++i)
{
if (Layers[i].material == null_handle)
return null_handle;
indices[i] = layer_index(Layers[i]);
}
auto const slotindex{acquire_slot(Reuse)};
if (slotindex == static_cast<std::size_t>(-1))
return null_handle;
auto &entry{m_slots[slotindex]};
// weights, four layers to a texture
auto const painted{Weights != nullptr && Samples > 0 && count > 1};
auto const side{painted ? Samples + 1 : 1};
auto const samples{static_cast<std::size_t>(side) * side};
std::vector<std::uint8_t> texels(samples * 4);
for (std::size_t texture = 0; texture < 2; ++texture)
{
std::fill(texels.begin(), texels.end(), 0);
for (std::size_t channel = 0; channel < 4; ++channel)
{
auto const layer{texture * 4 + channel};
if (layer >= count)
break;
if (false == painted)
{
if (layer == 0)
texels[channel] = 255;
continue;
}
auto const *source{Weights + layer * samples};
for (std::size_t i = 0; i < samples; ++i)
texels[i * 4 + channel] = source[i];
}
bind_for_upload(m_textures.texture(entry.weights[texture]));
::glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, side, side, 0, GL_RGBA, GL_UNSIGNED_BYTE, texels.data());
::glGenerateMipmap(GL_TEXTURE_2D);
}
// what the layers are: index in the arrays, repeat, which maps they have, reflection
std::array<float, max_layers * 2 * 4> table{};
for (std::size_t i = 0; i < count; ++i)
{
auto const &layer{m_layers[indices[i]]};
table[i * 4 + 0] = static_cast<float>(layer.index);
table[i * 4 + 1] = 1.f / std::max(0.01f, layer.repeat);
table[i * 4 + 2] = layer.normalmap ? 1.f : 0.f;
table[i * 4 + 3] = layer.specgloss ? 1.f : 0.f;
table[(max_layers + i) * 4 + 0] = layer.reflection;
// the textures turned by the layer of the chunk (the same material can be turned differently in another layer)
auto const angle{glm::radians(static_cast<double>(Layers[i].rotation))};
table[(max_layers + i) * 4 + 1] = static_cast<float>(std::cos(angle));
table[(max_layers + i) * 4 + 2] = static_cast<float>(std::sin(angle));
}
bind_for_upload(m_textures.texture(entry.table));
::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, static_cast<GLsizei>(max_layers), 2, 0, GL_RGBA, GL_FLOAT, table.data());
opengl_texture::reset_unit_cache();
auto &material{m_materials.material(entry.material)};
material.params[2] = glm::vec4(Placement.x, Placement.y, 1.f / std::max(1.f, Placement.z), static_cast<float>(side - 1));
return entry.material;
}
void opengl46_terrain_materials::release(material_handle const Material)
{
auto const lookup{m_slotof.find(Material)};
if (lookup == m_slotof.end() || false == m_slots[lookup->second].used)
return;
m_slots[lookup->second].used = false;
m_free.push_back(lookup->second);
}
bool opengl46_terrain_materials::copy_layer(layer &Layer)
{
auto const &material{m_materials.material(Layer.material)};
auto const slot = [&](char const *Name, int const Fallback) {
if (material.shader)
{
auto const lookup{material.shader->texture_conf.find(Name)};
if (lookup != material.shader->texture_conf.end())
return static_cast<int>(lookup->second.id);
return -1;
}
return Fallback;
};
std::array<int, 3> const slots{slot("diffuse", 0), Layer.normalmap ? slot("normalmap", 1) : -1, Layer.specgloss ? slot("specgloss", 2) : -1};
// all maps of the layer have to be ready, so they go together
std::array<opengl_texture *, 3> sources{};
for (std::size_t i = 0; i < slots.size(); ++i)
{
if (slots[i] < 0 || material.textures[slots[i]] == null_handle)
continue;
auto &texture{m_textures.mark_as_used(material.textures[slots[i]])};
if (false == texture.is_ready)
{
// a texture which is on the card already may have dropped its data after the upload (create() would refuse it then),
// one released by the sweep or never read has to be read first
if (texture.data_state == resource_state::none)
{
texture.reload_on_use = false;
texture.load();
}
if (texture.data_state == resource_state::failed)
continue;
if (false == texture.create())
{
if (++Layer.attempts < copy_attempts)
return false;
WriteLog("heightmap terrain: texture \"" + texture.name + "\" isn't ready, the terrain goes without it");
continue; // waited long enough, the layer goes without it
}
}
sources[i] = &texture;
}
// defaults for maps the layer doesn't have: grey, flat, without specular reflections
std::array<std::array<float, 4>, 3> const defaults{{{0.5f, 0.5f, 0.5f, 1.f}, {0.5f, 0.5f, 1.f, 1.f}, {0.f, 1.f, 0.f, 1.f}}};
for (std::size_t i = 0; i < m_arrays.size(); ++i)
{
auto const &array{m_arrays[i]};
auto &target{m_textures.texture(array.handle)};
m_framebuffer->attach(target, GL_COLOR_ATTACHMENT0, Layer.index);
m_framebuffer->setup_drawing(1);
::glViewport(0, 0, array.size, array.size);
if (sources[i] == nullptr)
{
::glClearBufferfv(GL_COLOR, 0, defaults[i].data());
continue;
}
if (array.srgb)
::glEnable(GL_FRAMEBUFFER_SRGB);
else
::glDisable(GL_FRAMEBUFFER_SRGB);
m_copy->apply(*sources[i], m_framebuffer.get());
}
Layer.copied = true;
return true;
}
void opengl46_terrain_materials::update()
{
if (false == m_pending || m_state <= 0)
return;
auto const blend{::glIsEnabled(GL_BLEND)};
auto const scissor{::glIsEnabled(GL_SCISSOR_TEST)};
auto const cull{::glIsEnabled(GL_CULL_FACE)};
auto const depth{::glIsEnabled(GL_DEPTH_TEST)};
auto const srgb{::glIsEnabled(GL_FRAMEBUFFER_SRGB)};
::glDisable(GL_BLEND);
::glDisable(GL_SCISSOR_TEST);
::glDisable(GL_CULL_FACE);
bool copied{false};
m_pending = false;
for (auto &layer : m_layers)
{
if (layer.copied)
continue;
if (copy_layer(layer))
copied = true;
else
m_pending = true;
}
if (copied)
{
for (auto const &array : m_arrays)
{
bind_for_upload(m_textures.texture(array.handle));
::glGenerateMipmap(GL_TEXTURE_2D_ARRAY);
}
}
// leave the state as the rest of the renderer knows it
gl::framebuffer::unbind();
if (blend)
::glEnable(GL_BLEND);
if (scissor)
::glEnable(GL_SCISSOR_TEST);
if (cull)
::glEnable(GL_CULL_FACE);
if (depth)
::glEnable(GL_DEPTH_TEST);
else
::glDisable(GL_DEPTH_TEST);
if (srgb)
::glEnable(GL_FRAMEBUFFER_SRGB);
else
::glDisable(GL_FRAMEBUFFER_SRGB);
::glDepthMask(GL_TRUE);
opengl_texture::reset_unit_cache();
gfx::opengl46_vaogeometrybank::reset();
}

View File

@@ -0,0 +1,85 @@
/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
#pragma once
#include <array>
#include <memory>
#include <unordered_map>
#include <vector>
#include "rendering/renderer.h"
#include "model/material.h"
#include "model/Texture.h"
#include "gl/framebuffer.h"
#include "gl/postfx.h"
// materials of the heightmap terrain for the opengl 3.3 renderer (shader mat_terrain).
// the textures of the layer materials are copied into texture arrays shared by all chunks; each chunk gets a material
// of its own, with textures holding the weights of its layers and a table of what its layers are
class opengl46_terrain_materials
{
public:
opengl46_terrain_materials(material_manager &Materials, texture_manager &Textures) : m_materials(Materials), m_textures(Textures) {}
// see gfx_renderer::Terrain_Material. returns null_handle if the terrain shader can't be used
material_handle make(material_handle const Reuse, std::vector<gfx::terrain_layer> const &Layers, int const Samples, std::uint8_t const *Weights, glm::vec3 const &Placement);
void release(material_handle const Material);
// copies the textures of new layers into the arrays; to be called before the frame is drawn
void update();
private:
struct layer
{
material_handle material{null_handle};
float size{0.f}; // as given by the chunk
float repeat{8.f}; // metres the textures are repeated at
int index{0}; // in the arrays
bool normalmap{false};
bool specgloss{false};
float reflection{0.f};
bool copied{false};
int attempts{0};
};
struct texture_array
{
texture_handle handle{null_handle};
int size{0};
GLint format{0};
bool srgb{false};
};
struct slot
{
material_handle material{null_handle};
std::array<texture_handle, 2> weights{null_handle, null_handle};
texture_handle table{null_handle};
bool used{false};
};
bool init();
texture_handle make_texture(std::string const &Name, GLenum const Target);
void make_storage(texture_array &Array, int const Layers);
int layer_index(gfx::terrain_layer const &Layer);
bool copy_layer(layer &Layer);
std::size_t acquire_slot(material_handle const Reuse);
material_manager &m_materials;
texture_manager &m_textures;
int m_state{0}; // 0: not set up yet, 1: ready, -1: can't be used
texture_handle m_white{null_handle};
std::array<texture_array, 3> m_arrays; // diffuse, normal maps, specular/gloss
int m_capacity{0}; // layers the arrays have room for
std::vector<layer> m_layers;
bool m_pending{false};
std::vector<slot> m_slots;
std::vector<std::size_t> m_free;
std::unordered_map<material_handle, std::size_t> m_slotof;
std::unique_ptr<gl::postfx> m_copy;
std::unique_ptr<gl::framebuffer> m_framebuffer;
};