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:
323
rendering/opengl46/opengl46geometrybank.cpp
Normal file
323
rendering/opengl46/opengl46geometrybank.cpp
Normal 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
|
||||
82
rendering/opengl46/opengl46geometrybank.h
Normal file
82
rendering/opengl46/opengl46geometrybank.h
Normal 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
|
||||
22
rendering/opengl46/opengl46light.cpp
Normal file
22
rendering/opengl46/opengl46light.cpp
Normal 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 */ }
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
28
rendering/opengl46/opengl46light.h
Normal file
28
rendering/opengl46/opengl46light.h
Normal 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; }
|
||||
};
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
140
rendering/opengl46/opengl46particles.cpp
Normal file
140
rendering/opengl46/opengl46particles.cpp
Normal 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;
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
54
rendering/opengl46/opengl46particles.h
Normal file
54
rendering/opengl46/opengl46particles.h
Normal 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
|
||||
};
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
159
rendering/opengl46/opengl46precipitation.cpp
Normal file
159
rendering/opengl46/opengl46precipitation.cpp
Normal 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();
|
||||
}
|
||||
43
rendering/opengl46/opengl46precipitation.h
Normal file
43
rendering/opengl46/opengl46precipitation.h
Normal 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;
|
||||
};
|
||||
6256
rendering/opengl46/opengl46renderer.cpp
Normal file
6256
rendering/opengl46/opengl46renderer.cpp
Normal file
File diff suppressed because it is too large
Load Diff
594
rendering/opengl46/opengl46renderer.h
Normal file
594
rendering/opengl46/opengl46renderer.h
Normal 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;
|
||||
}
|
||||
};
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
67
rendering/opengl46/opengl46skydome.cpp
Normal file
67
rendering/opengl46/opengl46skydome.cpp
Normal 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 ) );
|
||||
}
|
||||
36
rendering/opengl46/opengl46skydome.h
Normal file
36
rendering/opengl46/opengl46skydome.h
Normal 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;
|
||||
};
|
||||
434
rendering/opengl46/opengl46terrain.cpp
Normal file
434
rendering/opengl46/opengl46terrain.cpp
Normal 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();
|
||||
}
|
||||
85
rendering/opengl46/opengl46terrain.h
Normal file
85
rendering/opengl46/opengl46terrain.h
Normal 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;
|
||||
};
|
||||
Reference in New Issue
Block a user