vertices;
#define ADD(i, j) \
m_Terrain->CalcPosition(i, j, position); \
vertices.emplace_back(position.X); \
vertices.emplace_back(position.Y); \
vertices.emplace_back(position.Z);
CVector3D position;
ADD(i, j);
ADD(i+1, j);
ADD(i+1, j+1);
ADD(i, j+1);
#undef ADD
CShaderTechniquePtr overlayTech =
g_Renderer.GetShaderManager().LoadEffect(str_debug_line);
overlayTech->BeginPass();
CShaderProgramPtr overlayShader = overlayTech->GetShader();
overlayShader->Bind();
overlayShader->Uniform(str_transform, g_Renderer.GetSceneRenderer().GetViewCamera().GetViewProjection());
overlayShader->Uniform(str_color, color);
overlayShader->VertexPointer(3, GL_FLOAT, 0, vertices.data());
overlayShader->AssertPointersBound();
glDrawArrays(GL_QUADS, 0, vertices.size() / 3);
overlayShader->Unbind();
overlayTech->EndPass();
if (line_width != 1)
glLineWidth(1.0f);
#endif
}
//////////////////////////////////////////////////////////////////////////
TerrainTextureOverlay::TerrainTextureOverlay(float texelsPerTile, int priority) :
ITerrainOverlay(priority), m_TexelsPerTile(texelsPerTile)
{
}
TerrainTextureOverlay::~TerrainTextureOverlay() = default;
-void TerrainTextureOverlay::RenderAfterWater(int cullGroup)
+void TerrainTextureOverlay::RenderAfterWater(
+ Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext, int cullGroup)
{
CTerrain* terrain = g_Game->GetWorld()->GetTerrain();
ssize_t w = (ssize_t)(terrain->GetTilesPerSide() * m_TexelsPerTile);
ssize_t h = (ssize_t)(terrain->GetTilesPerSide() * m_TexelsPerTile);
const uint32_t requiredWidth = round_up_to_pow2(w);
const uint32_t requiredHeight = round_up_to_pow2(h);
glActiveTextureARB(GL_TEXTURE0);
// Recreate the texture with new size if necessary
if (!m_Texture || m_Texture->GetWidth() != requiredWidth || m_Texture->GetHeight() != requiredHeight)
{
m_Texture = Renderer::Backend::GL::CTexture::Create2D(
Renderer::Backend::Format::R8G8B8A8, requiredWidth, requiredHeight,
Renderer::Backend::Sampler::MakeDefaultSampler(
Renderer::Backend::Sampler::Filter::NEAREST,
Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE));
-
- glBindTexture(GL_TEXTURE_2D, m_Texture->GetHandle());
- glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, m_Texture->GetWidth(), m_Texture->GetHeight(), 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
}
u8* data = (u8*)calloc(w * h, 4);
BuildTextureRGBA(data, w, h);
- glBindTexture(GL_TEXTURE_2D, m_Texture->GetHandle());
- glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, data);
+ deviceCommandContext->UploadTexture(
+ m_Texture.get(), Renderer::Backend::Format::R8G8B8A8, data, w * h * 4);
free(data);
CMatrix3D matrix;
matrix.SetZero();
matrix._11 = m_TexelsPerTile / (m_Texture->GetWidth() * TERRAIN_TILE_SIZE);
matrix._23 = m_TexelsPerTile / (m_Texture->GetHeight() * TERRAIN_TILE_SIZE);
matrix._44 = 1;
g_Renderer.GetSceneRenderer().GetTerrainRenderer().RenderTerrainOverlayTexture(cullGroup, matrix, m_Texture.get());
}
SColor4ub TerrainTextureOverlay::GetColor(size_t idx, u8 alpha) const
{
static u8 colors[][3] =
{
{ 255, 0, 0 },
{ 0, 255, 0 },
{ 0, 0, 255 },
{ 255, 255, 0 },
{ 255, 0, 255 },
{ 0, 255, 255 },
{ 255, 255, 255 },
{ 127, 0, 0 },
{ 0, 127, 0 },
{ 0, 0, 127 },
{ 127, 127, 0 },
{ 127, 0, 127 },
{ 0, 127, 127 },
{ 127, 127, 127},
{ 255, 127, 0 },
{ 127, 255, 0 },
{ 255, 0, 127 },
{ 127, 0, 255},
{ 0, 255, 127 },
{ 0, 127, 255},
{ 255, 127, 127},
{ 127, 255, 127},
{ 127, 127, 255},
{ 127, 255, 255 },
{ 255, 127, 255 },
{ 255, 255, 127 },
};
size_t c = idx % ARRAY_SIZE(colors);
return SColor4ub(colors[c][0], colors[c][1], colors[c][2], alpha);
}
Index: ps/trunk/source/renderer/TerrainOverlay.h
===================================================================
--- ps/trunk/source/renderer/TerrainOverlay.h (revision 26169)
+++ ps/trunk/source/renderer/TerrainOverlay.h (revision 26170)
@@ -1,203 +1,207 @@
-/* Copyright (C) 2021 Wildfire Games.
+/* Copyright (C) 2022 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
/*
* System for representing tile-based information on top of the
* terrain.
*/
#ifndef INCLUDED_TERRAINOVERLAY
#define INCLUDED_TERRAINOVERLAY
+#include "renderer/backend/gl/DeviceCommandContext.h"
#include "renderer/backend/gl/Texture.h"
struct CColor;
struct SColor4ub;
class CTerrain;
class CSimContext;
/**
* Common interface for terrain-tile-based and texture-based debug overlays.
*
* An overlay object will be rendered for as long as it is allocated
* (it is automatically registered/deregistered by constructor/destructor).
*/
class ITerrainOverlay
{
NONCOPYABLE(ITerrainOverlay);
public:
virtual ~ITerrainOverlay();
virtual void RenderBeforeWater() { }
- virtual void RenderAfterWater(int UNUSED(cullGroup)) { }
+ virtual void RenderAfterWater(
+ Renderer::Backend::GL::CDeviceCommandContext* UNUSED(deviceCommandContext), int UNUSED(cullGroup)) { }
/**
* Draw all ITerrainOverlay objects that exist
* and that should be drawn before water.
*/
static void RenderOverlaysBeforeWater();
/**
* Draw all ITerrainOverlay objects that exist
* and that should be drawn after water.
*/
- static void RenderOverlaysAfterWater(int cullGroup);
+ static void RenderOverlaysAfterWater(
+ Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext, int cullGroup);
protected:
ITerrainOverlay(int priority);
};
/**
* Base class for (relatively) simple drawing of
* data onto terrain tiles, intended for debugging purposes and for the Atlas
* editor (hence not trying to be very efficient).
*
* To start drawing a terrain overlay, first create a subclass of TerrainOverlay.
* Override the method GetTileExtents if you want to change the range over which
* it is drawn.
* Override ProcessTile to do your processing for each tile, which should call
* RenderTile and RenderTileOutline as appropriate.
*/
class TerrainOverlay : public ITerrainOverlay
{
protected:
/**
* Construct the object and register it with the global
* list of terrain overlays.
*
* The priority parameter controls the order in which overlays are drawn,
* if several exist - they are processed in order of increasing priority,
* so later ones draw on top of earlier ones.
* Most should use the default of 100. Numbers from 200 are used
* by Atlas.
*
* @param priority controls the order of drawing
*/
TerrainOverlay(const CSimContext& simContext, int priority = 100);
/**
* Override to perform processing at the start of the overlay rendering,
* before the ProcessTile calls
*/
virtual void StartRender();
/**
* Override to perform processing at the end of the overlay rendering,
* after the ProcessTile calls
*/
virtual void EndRender();
/**
* Override to limit the range over which ProcessTile will
* be called. Defaults to the size of the map.
*
* @param min_i_inclusive [output] smallest i coordinate, in tile-space units
* (1 unit per tile, +i is world-space +x and game-space East)
* @param min_j_inclusive [output] smallest j coordinate
* (+j is world-space +z and game-space North)
* @param max_i_inclusive [output] largest i coordinate
* @param max_j_inclusive [output] largest j coordinate
*/
virtual void GetTileExtents(ssize_t& min_i_inclusive, ssize_t& min_j_inclusive,
ssize_t& max_i_inclusive, ssize_t& max_j_inclusive);
/**
* Override to perform processing of each tile. Typically calls
* RenderTile and/or RenderTileOutline.
*
* @param i i coordinate of tile being processed
* @param j j coordinate of tile being processed
*/
virtual void ProcessTile(ssize_t i, ssize_t j) = 0;
/**
* Draw a filled quad on top of the current tile.
*
* @param color color to draw. May be transparent (alpha < 1)
* @param draw_hidden true if hidden tiles (i.e. those behind other tiles)
* should be drawn
*/
void RenderTile(const CColor& color, bool draw_hidden);
/**
* Draw a filled quad on top of the given tile.
*/
void RenderTile(const CColor& color, bool draw_hidden, ssize_t i, ssize_t j);
/**
* Draw an outlined quad on top of the current tile.
*
* @param color color to draw. May be transparent (alpha < 1)
* @param line_width width of lines in pixels. 1 is a sensible value
* @param draw_hidden true if hidden tiles (i.e. those behind other tiles)
* should be drawn
*/
void RenderTileOutline(const CColor& color, int line_width, bool draw_hidden);
/**
* Draw an outlined quad on top of the given tile.
*/
void RenderTileOutline(const CColor& color, int line_width, bool draw_hidden, ssize_t i, ssize_t j);
private:
// Process all tiles
virtual void RenderBeforeWater();
// Temporary storage of tile coordinates, so ProcessTile doesn't need to
// pass it to RenderTile/etc (and doesn't have a chance to get it wrong)
ssize_t m_i, m_j;
CTerrain* m_Terrain;
};
/**
* Base class for texture-based terrain overlays, with an arbitrary number of
* texels per terrain tile, intended for debugging purposes.
* Subclasses must implement BuildTextureRGBA which will be called each frame.
*/
class TerrainTextureOverlay : public ITerrainOverlay
{
public:
TerrainTextureOverlay(float texelsPerTile, int priority = 100);
- virtual ~TerrainTextureOverlay();
+ ~TerrainTextureOverlay() override;
protected:
/**
* Called each frame to generate the texture to render on the terrain.
* @p data is w*h*4 bytes, where w and h are the terrain size multiplied
* by texelsPerTile. @p data defaults to fully transparent, and should
* be filled with data in RGBA order.
*/
virtual void BuildTextureRGBA(u8* data, size_t w, size_t h) = 0;
/**
* Returns an arbitrary color, for subclasses that want to distinguish
* different integers visually.
*/
SColor4ub GetColor(size_t idx, u8 alpha) const;
private:
- void RenderAfterWater(int cullGroup);
+ void RenderAfterWater(
+ Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext, int cullGroup) override;
float m_TexelsPerTile;
std::unique_ptr m_Texture;
};
#endif // INCLUDED_TERRAINOVERLAY
Index: ps/trunk/source/renderer/WaterManager.cpp
===================================================================
--- ps/trunk/source/renderer/WaterManager.cpp (revision 26169)
+++ ps/trunk/source/renderer/WaterManager.cpp (revision 26170)
@@ -1,1108 +1,1086 @@
/* Copyright (C) 2022 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#include "precompiled.h"
#include "graphics/Terrain.h"
#include "graphics/TextureManager.h"
#include "graphics/ShaderManager.h"
#include "graphics/ShaderProgram.h"
#include "lib/bits.h"
#include "lib/timer.h"
#include "lib/ogl.h"
#include "lib/tex/tex.h"
#include "maths/MathUtil.h"
#include "maths/Vector2D.h"
#include "ps/CLogger.h"
#include "ps/CStrInternStatic.h"
#include "ps/Game.h"
#include "ps/VideoMode.h"
#include "ps/World.h"
#include "renderer/WaterManager.h"
#include "renderer/Renderer.h"
#include "renderer/RenderingOptions.h"
#include "renderer/SceneRenderer.h"
#include "simulation2/Simulation2.h"
#include "simulation2/components/ICmpWaterManager.h"
#include "simulation2/components/ICmpRangeManager.h"
struct CoastalPoint
{
CoastalPoint(int idx, CVector2D pos) : index(idx), position(pos) {};
int index;
CVector2D position;
};
struct SWavesVertex
{
// vertex position
CVector3D m_BasePosition;
CVector3D m_ApexPosition;
CVector3D m_SplashPosition;
CVector3D m_RetreatPosition;
CVector2D m_PerpVect;
u8 m_UV[3];
// pad to a power of two
u8 m_Padding[5];
};
cassert(sizeof(SWavesVertex) == 64);
struct WaveObject
{
CVertexBuffer::VBChunk* m_VBvertices;
CBoundingBoxAligned m_AABB;
size_t m_Width;
float m_TimeDiff;
};
WaterManager::WaterManager()
{
// water
m_RenderWater = false; // disabled until textures are successfully loaded
m_WaterHeight = 5.0f;
m_RefTextureSize = 0;
m_ReflectionFbo = 0;
m_RefractionFbo = 0;
m_FancyEffectsFBO = 0;
m_WaterTexTimer = 0.0;
m_WindAngle = 0.0f;
m_Waviness = 8.0f;
m_WaterColor = CColor(0.3f, 0.35f, 0.7f, 1.0f);
m_WaterTint = CColor(0.28f, 0.3f, 0.59f, 1.0f);
m_Murkiness = 0.45f;
m_RepeatPeriod = 16.0f;
m_DistanceHeightmap = NULL;
m_BlurredNormalMap = NULL;
m_WindStrength = NULL;
m_ShoreWaves_VBIndices = NULL;
m_WaterEffects = true;
m_WaterFancyEffects = false;
m_WaterRealDepth = false;
m_WaterRefraction = false;
m_WaterReflection = false;
m_WaterType = L"ocean";
m_NeedsReloading = false;
m_NeedInfoUpdate = true;
m_MapSize = 0;
m_updatei0 = 0;
m_updatej0 = 0;
m_updatei1 = 0;
m_updatej1 = 0;
}
WaterManager::~WaterManager()
{
// Cleanup if the caller messed up
UnloadWaterTextures();
for (WaveObject* const& obj : m_ShoreWaves)
{
if (obj->m_VBvertices)
g_VBMan.Release(obj->m_VBvertices);
delete obj;
}
if (m_ShoreWaves_VBIndices)
g_VBMan.Release(m_ShoreWaves_VBIndices);
delete[] m_DistanceHeightmap;
delete[] m_BlurredNormalMap;
delete[] m_WindStrength;
if (!g_Renderer.GetCapabilities().m_PrettyWater)
return;
m_FancyTexture.reset();
m_FancyTextureDepth.reset();
m_ReflFboDepthTexture.reset();
m_RefrFboDepthTexture.reset();
glDeleteFramebuffersEXT(1, &m_FancyEffectsFBO);
glDeleteFramebuffersEXT(1, &m_RefractionFbo);
glDeleteFramebuffersEXT(1, &m_ReflectionFbo);
}
///////////////////////////////////////////////////////////////////
// Progressive load of water textures
int WaterManager::LoadWaterTextures()
{
// TODO: this doesn't need to be progressive-loading any more
// (since texture loading is async now)
wchar_t pathname[PATH_MAX];
// Load diffuse grayscale images (for non-fancy water)
for (size_t i = 0; i < ARRAY_SIZE(m_WaterTexture); ++i)
{
swprintf_s(pathname, ARRAY_SIZE(pathname), L"art/textures/animated/water/default/diffuse%02d.dds", (int)i+1);
CTextureProperties textureProps(pathname);
textureProps.SetWrap(GL_REPEAT);
CTexturePtr texture = g_Renderer.GetTextureManager().CreateTexture(textureProps);
texture->Prefetch();
m_WaterTexture[i] = texture;
}
if (!g_Renderer.GetCapabilities().m_PrettyWater)
{
// Enable rendering, now that we've succeeded this far
m_RenderWater = true;
return 0;
}
#if CONFIG2_GLES
#warning Fix WaterManager::LoadWaterTextures on GLES
#else
// Load normalmaps (for fancy water)
ReloadWaterNormalTextures();
// Load CoastalWaves
{
CTextureProperties textureProps(L"art/textures/terrain/types/water/coastalWave.png");
textureProps.SetWrap(GL_REPEAT);
CTexturePtr texture = g_Renderer.GetTextureManager().CreateTexture(textureProps);
texture->Prefetch();
m_WaveTex = texture;
}
// Load Foam
{
CTextureProperties textureProps(L"art/textures/terrain/types/water/foam.png");
textureProps.SetWrap(GL_REPEAT);
CTexturePtr texture = g_Renderer.GetTextureManager().CreateTexture(textureProps);
texture->Prefetch();
m_FoamTex = texture;
}
// Use screen-sized textures for minimum artifacts.
m_RefTextureSize = g_Renderer.GetHeight();
m_RefTextureSize = round_up_to_pow2(m_RefTextureSize);
// Create reflection texture
m_ReflectionTexture = Renderer::Backend::GL::CTexture::Create2D(
Renderer::Backend::Format::R8G8B8A8, m_RefTextureSize, m_RefTextureSize,
Renderer::Backend::Sampler::MakeDefaultSampler(
Renderer::Backend::Sampler::Filter::LINEAR,
Renderer::Backend::Sampler::AddressMode::MIRRORED_REPEAT));
- glBindTexture(GL_TEXTURE_2D, m_ReflectionTexture->GetHandle());
- glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, (GLsizei)m_RefTextureSize, (GLsizei)m_RefTextureSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
-
// Create refraction texture
m_RefractionTexture = Renderer::Backend::GL::CTexture::Create2D(
Renderer::Backend::Format::R8G8B8A8, m_RefTextureSize, m_RefTextureSize,
Renderer::Backend::Sampler::MakeDefaultSampler(
Renderer::Backend::Sampler::Filter::LINEAR,
Renderer::Backend::Sampler::AddressMode::MIRRORED_REPEAT));
- glBindTexture(GL_TEXTURE_2D, m_RefractionTexture->GetHandle());
- glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, (GLsizei)m_RefTextureSize, (GLsizei)m_RefTextureSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
-
// Create depth textures
m_ReflFboDepthTexture = Renderer::Backend::GL::CTexture::Create2D(
Renderer::Backend::Format::D32, m_RefTextureSize, m_RefTextureSize,
Renderer::Backend::Sampler::MakeDefaultSampler(
Renderer::Backend::Sampler::Filter::NEAREST,
Renderer::Backend::Sampler::AddressMode::REPEAT));
- glBindTexture(GL_TEXTURE_2D, m_ReflFboDepthTexture->GetHandle());
- glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT32, (GLsizei)m_RefTextureSize, (GLsizei)m_RefTextureSize, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT, NULL);
-
m_RefrFboDepthTexture = Renderer::Backend::GL::CTexture::Create2D(
Renderer::Backend::Format::D32, m_RefTextureSize, m_RefTextureSize,
Renderer::Backend::Sampler::MakeDefaultSampler(
Renderer::Backend::Sampler::Filter::NEAREST,
Renderer::Backend::Sampler::AddressMode::REPEAT));
- glBindTexture(GL_TEXTURE_2D, m_RefrFboDepthTexture->GetHandle());
- glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT32, (GLsizei)m_RefTextureSize, (GLsizei)m_RefTextureSize, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT, NULL);
-
- glBindTexture(GL_TEXTURE_2D, 0);
-
Resize();
// Create the water framebuffers
GLint currentFbo;
glGetIntegerv(GL_FRAMEBUFFER_BINDING_EXT, ¤tFbo);
m_ReflectionFbo = 0;
glGenFramebuffersEXT(1, &m_ReflectionFbo);
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_ReflectionFbo);
glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, m_ReflectionTexture->GetHandle(), 0);
glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_TEXTURE_2D, m_ReflFboDepthTexture->GetHandle(), 0);
ogl_WarnIfError();
GLenum status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
if (status != GL_FRAMEBUFFER_COMPLETE_EXT)
{
LOGWARNING("Reflection framebuffer object incomplete: 0x%04X", status);
g_RenderingOptions.SetWaterReflection(false);
UpdateQuality();
}
m_RefractionFbo = 0;
glGenFramebuffersEXT(1, &m_RefractionFbo);
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_RefractionFbo);
glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, m_RefractionTexture->GetHandle(), 0);
glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_TEXTURE_2D, m_RefrFboDepthTexture->GetHandle(), 0);
ogl_WarnIfError();
status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
if (status != GL_FRAMEBUFFER_COMPLETE_EXT)
{
LOGWARNING("Refraction framebuffer object incomplete: 0x%04X", status);
g_RenderingOptions.SetWaterRefraction(false);
UpdateQuality();
}
glGenFramebuffersEXT(1, &m_FancyEffectsFBO);
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_FancyEffectsFBO);
glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, m_FancyTexture->GetHandle(), 0);
glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_TEXTURE_2D, m_FancyTextureDepth->GetHandle(), 0);
ogl_WarnIfError();
status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
if (status != GL_FRAMEBUFFER_COMPLETE_EXT)
{
LOGWARNING("Fancy Effects framebuffer object incomplete: 0x%04X", status);
g_RenderingOptions.SetWaterRefraction(false);
UpdateQuality();
}
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, currentFbo);
// Enable rendering, now that we've succeeded this far
m_RenderWater = true;
#endif
return 0;
}
///////////////////////////////////////////////////////////////////
// Resize: Updates the fancy water textures.
void WaterManager::Resize()
{
// Create the Fancy Effects texture
m_FancyTexture = Renderer::Backend::GL::CTexture::Create2D(
Renderer::Backend::Format::R8G8B8A8, g_Renderer.GetWidth(), g_Renderer.GetHeight(),
Renderer::Backend::Sampler::MakeDefaultSampler(
Renderer::Backend::Sampler::Filter::LINEAR,
Renderer::Backend::Sampler::AddressMode::REPEAT));
- glBindTexture(GL_TEXTURE_2D, m_FancyTexture->GetHandle());
- glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, (GLsizei)g_Renderer.GetWidth(), (GLsizei)g_Renderer.GetHeight(), 0, GL_RGBA, GL_UNSIGNED_SHORT, NULL);
-
m_FancyTextureDepth = Renderer::Backend::GL::CTexture::Create2D(
Renderer::Backend::Format::D32, g_Renderer.GetWidth(), g_Renderer.GetHeight(),
Renderer::Backend::Sampler::MakeDefaultSampler(
Renderer::Backend::Sampler::Filter::LINEAR,
Renderer::Backend::Sampler::AddressMode::REPEAT));
-
- glBindTexture(GL_TEXTURE_2D, m_FancyTextureDepth->GetHandle());
- glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT32, (GLsizei)g_Renderer.GetWidth(), (GLsizei)g_Renderer.GetHeight(), 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT, NULL);
-
- glBindTexture(GL_TEXTURE_2D, 0);
}
void WaterManager::ReloadWaterNormalTextures()
{
wchar_t pathname[PATH_MAX];
for (size_t i = 0; i < ARRAY_SIZE(m_NormalMap); ++i)
{
swprintf_s(pathname, ARRAY_SIZE(pathname), L"art/textures/animated/water/%ls/normal00%02d.png", m_WaterType.c_str(), static_cast(i) + 1);
CTextureProperties textureProps(pathname);
textureProps.SetWrap(GL_REPEAT);
textureProps.SetMaxAnisotropy(4);
CTexturePtr texture = g_Renderer.GetTextureManager().CreateTexture(textureProps);
texture->Prefetch();
m_NormalMap[i] = texture;
}
}
///////////////////////////////////////////////////////////////////
// Unload water textures
void WaterManager::UnloadWaterTextures()
{
for (size_t i = 0; i < ARRAY_SIZE(m_WaterTexture); i++)
m_WaterTexture[i].reset();
if (!g_Renderer.GetCapabilities().m_PrettyWater)
return;
for (size_t i = 0; i < ARRAY_SIZE(m_NormalMap); i++)
m_NormalMap[i].reset();
m_ReflectionTexture.reset();
m_RefractionTexture.reset();
glDeleteFramebuffersEXT(1, &m_RefractionFbo);
glDeleteFramebuffersEXT(1, &m_ReflectionFbo);
}
template
static inline void ComputeDirection(float* distanceMap, const u16* heightmap, float waterHeight, size_t SideSize, size_t maxLevel)
{
#define ABOVEWATER(x, z) (HEIGHT_SCALE * heightmap[z*SideSize + x] >= waterHeight)
#define UPDATELOOKAHEAD \
for (; lookahead <= id2+maxLevel && lookahead < SideSize && \
((!Transpose && !ABOVEWATER(lookahead, id1)) || (Transpose && !ABOVEWATER(id1, lookahead))); ++lookahead)
// Algorithm:
// We want to know the distance to the closest shore point. Go through each line/column,
// keep track of when we encountered the last shore point and how far ahead the next one is.
for (size_t id1 = 0; id1 < SideSize; ++id1)
{
size_t id2 = 0;
const size_t& x = Transpose ? id1 : id2;
const size_t& z = Transpose ? id2 : id1;
size_t level = ABOVEWATER(x, z) ? 0 : maxLevel;
size_t lookahead = (size_t)(level > 0);
UPDATELOOKAHEAD;
// start moving
for (; id2 < SideSize; ++id2)
{
// update current level
if (ABOVEWATER(x, z))
level = 0;
else
level = std::min(level+1, maxLevel);
// move lookahead
if (lookahead == id2)
++lookahead;
UPDATELOOKAHEAD;
// This is the important bit: set the distance to either:
// - the distance to the previous shore point (level)
// - the distance to the next shore point (lookahead-id2)
distanceMap[z*SideSize + x] = std::min(distanceMap[z*SideSize + x], (float)std::min(lookahead-id2, level));
}
}
#undef ABOVEWATER
#undef UPDATELOOKAHEAD
}
///////////////////////////////////////////////////////////////////
// Calculate our binary heightmap from the terrain heightmap.
void WaterManager::RecomputeDistanceHeightmap()
{
CTerrain* terrain = g_Game->GetWorld()->GetTerrain();
if (!terrain || !terrain->GetHeightMap())
return;
size_t SideSize = m_MapSize;
// we want to look ahead some distance, but not too much (less efficient and not interesting). This is our lookahead.
const size_t maxLevel = 5;
if (m_DistanceHeightmap == NULL)
{
m_DistanceHeightmap = new float[SideSize*SideSize];
std::fill(m_DistanceHeightmap, m_DistanceHeightmap + SideSize*SideSize, (float)maxLevel);
}
// Create a manhattan-distance heightmap.
// This could be refined to only be done near the coast itself, but it's probably not necessary.
u16* heightmap = terrain->GetHeightMap();
ComputeDirection(m_DistanceHeightmap, heightmap, m_WaterHeight, SideSize, maxLevel);
ComputeDirection(m_DistanceHeightmap, heightmap, m_WaterHeight, SideSize, maxLevel);
}
// This requires m_DistanceHeightmap to be defined properly.
void WaterManager::CreateWaveMeshes()
{
OGL_SCOPED_DEBUG_GROUP("Create Wave Meshes");
if (m_MapSize == 0)
return;
CTerrain* terrain = g_Game->GetWorld()->GetTerrain();
if (!terrain || !terrain->GetHeightMap())
return;
for (WaveObject* const& obj : m_ShoreWaves)
{
if (obj->m_VBvertices)
g_VBMan.Release(obj->m_VBvertices);
delete obj;
}
m_ShoreWaves.clear();
if (m_ShoreWaves_VBIndices)
{
g_VBMan.Release(m_ShoreWaves_VBIndices);
m_ShoreWaves_VBIndices = NULL;
}
if (m_Waviness < 5.0f && m_WaterType != L"ocean")
return;
size_t SideSize = m_MapSize;
// First step: get the points near the coast.
std::set CoastalPointsSet;
for (size_t z = 1; z < SideSize-1; ++z)
for (size_t x = 1; x < SideSize-1; ++x)
// get the points not on the shore but near it, ocean-side
if (m_DistanceHeightmap[z*m_MapSize + x] > 0.5f && m_DistanceHeightmap[z*m_MapSize + x] < 1.5f)
CoastalPointsSet.insert((z)*SideSize + x);
// Second step: create chains out of those coastal points.
static const int around[8][2] = { { -1,-1 }, { -1,0 }, { -1,1 }, { 0,1 }, { 1,1 }, { 1,0 }, { 1,-1 }, { 0,-1 } };
std::vector > CoastalPointsChains;
while (!CoastalPointsSet.empty())
{
int index = *(CoastalPointsSet.begin());
int x = index % SideSize;
int y = (index - x ) / SideSize;
std::deque Chain;
Chain.push_front(CoastalPoint(index,CVector2D(x*4,y*4)));
// Erase us.
CoastalPointsSet.erase(CoastalPointsSet.begin());
// We're our starter points. At most we can have 2 points close to us.
// We'll pick the first one and look for its neighbors (he can only have one new)
// Up until we either reach the end of the chain, or ourselves.
// Then go down the other direction if there is any.
int neighbours[2] = { -1, -1 };
int nbNeighb = 0;
for (int i = 0; i < 8; ++i)
{
if (CoastalPointsSet.count(x + around[i][0] + (y + around[i][1])*SideSize))
{
if (nbNeighb < 2)
neighbours[nbNeighb] = x + around[i][0] + (y + around[i][1])*SideSize;
++nbNeighb;
}
}
if (nbNeighb > 2)
continue;
for (int i = 0; i < 2; ++i)
{
if (neighbours[i] == -1)
continue;
// Move to our neighboring point
int xx = neighbours[i] % SideSize;
int yy = (neighbours[i] - xx ) / SideSize;
int indexx = xx + yy*SideSize;
int endedChain = false;
if (i == 0)
Chain.push_back(CoastalPoint(indexx,CVector2D(xx*4,yy*4)));
else
Chain.push_front(CoastalPoint(indexx,CVector2D(xx*4,yy*4)));
// If there's a loop we'll be the "other" neighboring point already so check for that.
// We'll readd at the end/front the other one to have full squares.
if (CoastalPointsSet.count(indexx) == 0)
break;
CoastalPointsSet.erase(indexx);
// Start checking from there.
while(!endedChain)
{
bool found = false;
nbNeighb = 0;
for (int p = 0; p < 8; ++p)
{
if (CoastalPointsSet.count(xx+around[p][0] + (yy + around[p][1])*SideSize))
{
if (nbNeighb >= 2)
{
CoastalPointsSet.erase(xx + yy*SideSize);
continue;
}
++nbNeighb;
// We've found a new point around us.
// Move there
xx = xx + around[p][0];
yy = yy + around[p][1];
indexx = xx + yy*SideSize;
if (i == 0)
Chain.push_back(CoastalPoint(indexx,CVector2D(xx*4,yy*4)));
else
Chain.push_front(CoastalPoint(indexx,CVector2D(xx*4,yy*4)));
CoastalPointsSet.erase(xx + yy*SideSize);
found = true;
break;
}
}
if (!found)
endedChain = true;
}
}
if (Chain.size() > 10)
CoastalPointsChains.push_back(Chain);
}
// (optional) third step: Smooth chains out.
// This is also really dumb.
for (size_t i = 0; i < CoastalPointsChains.size(); ++i)
{
// Bump 1 for smoother.
for (int p = 0; p < 3; ++p)
{
for (size_t j = 1; j < CoastalPointsChains[i].size()-1; ++j)
{
CVector2D realPos = CoastalPointsChains[i][j-1].position + CoastalPointsChains[i][j+1].position;
CoastalPointsChains[i][j].position = (CoastalPointsChains[i][j].position + realPos/2.0f)/2.0f;
}
}
}
// Fourth step: create waves themselves, using those chains. We basically create subchains.
GLushort waveSizes = 14; // maximal size in width.
// Construct indices buffer (we can afford one for all of them)
std::vector water_indices;
for (GLushort a = 0; a < waveSizes - 1; ++a)
{
for (GLushort rect = 0; rect < 7; ++rect)
{
water_indices.push_back(a * 9 + rect);
water_indices.push_back(a * 9 + 9 + rect);
water_indices.push_back(a * 9 + 1 + rect);
water_indices.push_back(a * 9 + 9 + rect);
water_indices.push_back(a * 9 + 10 + rect);
water_indices.push_back(a * 9 + 1 + rect);
}
}
// Generic indexes, max-length
m_ShoreWaves_VBIndices = g_VBMan.Allocate(sizeof(GLushort), water_indices.size(), GL_STATIC_DRAW, GL_ELEMENT_ARRAY_BUFFER);
m_ShoreWaves_VBIndices->m_Owner->UpdateChunkVertices(m_ShoreWaves_VBIndices, &water_indices[0]);
float diff = (rand() % 50) / 5.0f;
for (size_t i = 0; i < CoastalPointsChains.size(); ++i)
{
for (size_t j = 0; j < CoastalPointsChains[i].size()-waveSizes; ++j)
{
if (CoastalPointsChains[i].size()- 1 - j < waveSizes)
break;
GLushort width = waveSizes;
// First pass to get some parameters out.
float outmost = 0.0f; // how far to move on the shore.
float avgDepth = 0.0f;
int sign = 1;
CVector2D firstPerp(0,0), perp(0,0), lastPerp(0,0);
for (GLushort a = 0; a < waveSizes;++a)
{
lastPerp = perp;
perp = CVector2D(0,0);
int nb = 0;
CVector2D pos = CoastalPointsChains[i][j+a].position;
CVector2D posPlus;
CVector2D posMinus;
if (a > 0)
{
++nb;
posMinus = CoastalPointsChains[i][j+a-1].position;
perp += pos-posMinus;
}
if (a < waveSizes-1)
{
++nb;
posPlus = CoastalPointsChains[i][j+a+1].position;
perp += posPlus-pos;
}
perp /= nb;
perp = CVector2D(-perp.Y,perp.X).Normalized();
if (a == 0)
firstPerp = perp;
if ( a > 1 && perp.Dot(lastPerp) < 0.90f && perp.Dot(firstPerp) < 0.70f)
{
width = a+1;
break;
}
if (terrain->GetExactGroundLevel(pos.X+perp.X*1.5f, pos.Y+perp.Y*1.5f) > m_WaterHeight)
sign = -1;
avgDepth += terrain->GetExactGroundLevel(pos.X+sign*perp.X*20.0f, pos.Y+sign*perp.Y*20.0f) - m_WaterHeight;
float localOutmost = -2.0f;
while (localOutmost < 0.0f)
{
float depth = terrain->GetExactGroundLevel(pos.X+sign*perp.X*localOutmost, pos.Y+sign*perp.Y*localOutmost) - m_WaterHeight;
if (depth < 0.0f || depth > 0.6f)
localOutmost += 0.2f;
else
break;
}
outmost += localOutmost;
}
if (width < 5)
{
j += 6;
continue;
}
outmost /= width;
if (outmost > -0.5f)
{
j += 3;
continue;
}
outmost = -2.5f + outmost * m_Waviness/10.0f;
avgDepth /= width;
if (avgDepth > -1.3f)
{
j += 3;
continue;
}
// we passed the checks, we can create a wave of size "width".
WaveObject* shoreWave = new WaveObject;
std::vector vertices;
vertices.reserve(9*width);
shoreWave->m_Width = width;
shoreWave->m_TimeDiff = diff;
diff += (rand() % 100) / 25.0f + 4.0f;
for (GLushort a = 0; a < width;++a)
{
perp = CVector2D(0,0);
int nb = 0;
CVector2D pos = CoastalPointsChains[i][j+a].position;
CVector2D posPlus;
CVector2D posMinus;
if (a > 0)
{
++nb;
posMinus = CoastalPointsChains[i][j+a-1].position;
perp += pos-posMinus;
}
if (a < waveSizes-1)
{
++nb;
posPlus = CoastalPointsChains[i][j+a+1].position;
perp += posPlus-pos;
}
perp /= nb;
perp = CVector2D(-perp.Y,perp.X).Normalized();
SWavesVertex point[9];
float baseHeight = 0.04f;
float halfWidth = (width-1.0f)/2.0f;
float sideNess = sqrtf(Clamp( (halfWidth - fabsf(a - halfWidth)) / 3.0f, 0.0f, 1.0f));
point[0].m_UV[0] = a; point[0].m_UV[1] = 8;
point[1].m_UV[0] = a; point[1].m_UV[1] = 7;
point[2].m_UV[0] = a; point[2].m_UV[1] = 6;
point[3].m_UV[0] = a; point[3].m_UV[1] = 5;
point[4].m_UV[0] = a; point[4].m_UV[1] = 4;
point[5].m_UV[0] = a; point[5].m_UV[1] = 3;
point[6].m_UV[0] = a; point[6].m_UV[1] = 2;
point[7].m_UV[0] = a; point[7].m_UV[1] = 1;
point[8].m_UV[0] = a; point[8].m_UV[1] = 0;
point[0].m_PerpVect = perp;
point[1].m_PerpVect = perp;
point[2].m_PerpVect = perp;
point[3].m_PerpVect = perp;
point[4].m_PerpVect = perp;
point[5].m_PerpVect = perp;
point[6].m_PerpVect = perp;
point[7].m_PerpVect = perp;
point[8].m_PerpVect = perp;
static const float perpT1[9] = { 6.0f, 6.05f, 6.1f, 6.2f, 6.3f, 6.4f, 6.5f, 6.6f, 9.7f };
static const float perpT2[9] = { 2.0f, 2.1f, 2.2f, 2.3f, 2.4f, 3.0f, 3.3f, 3.6f, 9.5f };
static const float perpT3[9] = { 1.1f, 0.7f, -0.2f, 0.0f, 0.6f, 1.3f, 2.2f, 3.6f, 9.0f };
static const float perpT4[9] = { 2.0f, 2.1f, 1.2f, 1.5f, 1.7f, 1.9f, 2.7f, 3.8f, 9.0f };
static const float heightT1[9] = { 0.0f, 0.2f, 0.5f, 0.8f, 0.9f, 0.85f, 0.6f, 0.2f, 0.0 };
static const float heightT2[9] = { -0.8f, -0.4f, 0.0f, 0.1f, 0.1f, 0.03f, 0.0f, 0.0f, 0.0 };
static const float heightT3[9] = { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0 };
for (size_t t = 0; t < 9; ++t)
{
float terrHeight = 0.05f + terrain->GetExactGroundLevel(pos.X+sign*perp.X*(perpT1[t]+outmost),
pos.Y+sign*perp.Y*(perpT1[t]+outmost));
point[t].m_BasePosition = CVector3D(pos.X+sign*perp.X*(perpT1[t]+outmost), baseHeight + heightT1[t]*sideNess + std::max(m_WaterHeight,terrHeight),
pos.Y+sign*perp.Y*(perpT1[t]+outmost));
}
for (size_t t = 0; t < 9; ++t)
{
float terrHeight = 0.05f + terrain->GetExactGroundLevel(pos.X+sign*perp.X*(perpT2[t]+outmost),
pos.Y+sign*perp.Y*(perpT2[t]+outmost));
point[t].m_ApexPosition = CVector3D(pos.X+sign*perp.X*(perpT2[t]+outmost), baseHeight + heightT1[t]*sideNess + std::max(m_WaterHeight,terrHeight),
pos.Y+sign*perp.Y*(perpT2[t]+outmost));
}
for (size_t t = 0; t < 9; ++t)
{
float terrHeight = 0.05f + terrain->GetExactGroundLevel(pos.X+sign*perp.X*(perpT3[t]+outmost*sideNess),
pos.Y+sign*perp.Y*(perpT3[t]+outmost*sideNess));
point[t].m_SplashPosition = CVector3D(pos.X+sign*perp.X*(perpT3[t]+outmost*sideNess), baseHeight + heightT2[t]*sideNess + std::max(m_WaterHeight,terrHeight), pos.Y+sign*perp.Y*(perpT3[t]+outmost*sideNess));
}
for (size_t t = 0; t < 9; ++t)
{
float terrHeight = 0.05f + terrain->GetExactGroundLevel(pos.X+sign*perp.X*(perpT4[t]+outmost),
pos.Y+sign*perp.Y*(perpT4[t]+outmost));
point[t].m_RetreatPosition = CVector3D(pos.X+sign*perp.X*(perpT4[t]+outmost), baseHeight + heightT3[t]*sideNess + std::max(m_WaterHeight,terrHeight),
pos.Y+sign*perp.Y*(perpT4[t]+outmost));
}
vertices.push_back(point[8]);
vertices.push_back(point[7]);
vertices.push_back(point[6]);
vertices.push_back(point[5]);
vertices.push_back(point[4]);
vertices.push_back(point[3]);
vertices.push_back(point[2]);
vertices.push_back(point[1]);
vertices.push_back(point[0]);
shoreWave->m_AABB += point[8].m_SplashPosition;
shoreWave->m_AABB += point[8].m_BasePosition;
shoreWave->m_AABB += point[0].m_SplashPosition;
shoreWave->m_AABB += point[0].m_BasePosition;
shoreWave->m_AABB += point[4].m_ApexPosition;
}
if (sign == 1)
{
// Let's do some fancy reversing.
std::vector reversed;
reversed.reserve(vertices.size());
for (int a = width-1; a >= 0; --a)
{
for (size_t t = 0; t < 9; ++t)
reversed.push_back(vertices[a*9+t]);
}
vertices = reversed;
}
j += width/2-1;
shoreWave->m_VBvertices = g_VBMan.Allocate(sizeof(SWavesVertex), vertices.size(), GL_STATIC_DRAW, GL_ARRAY_BUFFER);
shoreWave->m_VBvertices->m_Owner->UpdateChunkVertices(shoreWave->m_VBvertices, &vertices[0]);
m_ShoreWaves.push_back(shoreWave);
}
}
}
void WaterManager::RenderWaves(const CFrustum& frustrum)
{
OGL_SCOPED_DEBUG_GROUP("Render Waves");
#if CONFIG2_GLES
UNUSED2(frustrum);
#warning Fix WaterManager::RenderWaves on GLES
#else
if (!m_WaterFancyEffects)
return;
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_FancyEffectsFBO);
GLuint attachments[1] = { GL_COLOR_ATTACHMENT0_EXT };
glDrawBuffers(1, attachments);
glClearColor(0.0f,0.0f, 0.0f,0.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_ALWAYS);
CShaderTechniquePtr tech = g_Renderer.GetShaderManager().LoadEffect(str_water_waves);
tech->BeginPass();
CShaderProgramPtr shader = tech->GetShader();
shader->BindTexture(str_waveTex, m_WaveTex);
shader->BindTexture(str_foamTex, m_FoamTex);
shader->Uniform(str_time, (float)m_WaterTexTimer);
shader->Uniform(str_transform, g_Renderer.GetSceneRenderer().GetViewCamera().GetViewProjection());
for (size_t a = 0; a < m_ShoreWaves.size(); ++a)
{
if (!frustrum.IsBoxVisible(m_ShoreWaves[a]->m_AABB))
continue;
CVertexBuffer::VBChunk* VBchunk = m_ShoreWaves[a]->m_VBvertices;
SWavesVertex* base = (SWavesVertex*)VBchunk->m_Owner->Bind();
// setup data pointers
GLsizei stride = sizeof(SWavesVertex);
shader->VertexPointer(3, GL_FLOAT, stride, &base[VBchunk->m_Index].m_BasePosition);
shader->TexCoordPointer(GL_TEXTURE0, 2, GL_UNSIGNED_BYTE, stride, &base[VBchunk->m_Index].m_UV);
// NormalPointer(gl_FLOAT, stride, &base[m_VBWater->m_Index].m_UV)
glVertexAttribPointerARB(2, 2, GL_FLOAT, GL_FALSE, stride, &base[VBchunk->m_Index].m_PerpVect); // replaces commented above because my normal is vec2
shader->VertexAttribPointer(str_a_apexPosition, 3, GL_FLOAT, false, stride, &base[VBchunk->m_Index].m_ApexPosition);
shader->VertexAttribPointer(str_a_splashPosition, 3, GL_FLOAT, false, stride, &base[VBchunk->m_Index].m_SplashPosition);
shader->VertexAttribPointer(str_a_retreatPosition, 3, GL_FLOAT, false, stride, &base[VBchunk->m_Index].m_RetreatPosition);
shader->AssertPointersBound();
shader->Uniform(str_translation, m_ShoreWaves[a]->m_TimeDiff);
shader->Uniform(str_width, (int)m_ShoreWaves[a]->m_Width);
u8* indexBase = m_ShoreWaves_VBIndices->m_Owner->Bind();
glDrawElements(GL_TRIANGLES, (GLsizei) (m_ShoreWaves[a]->m_Width-1)*(7*6),
GL_UNSIGNED_SHORT, indexBase + sizeof(u16)*(m_ShoreWaves_VBIndices->m_Index));
shader->Uniform(str_translation, m_ShoreWaves[a]->m_TimeDiff + 6.0f);
// TODO: figure out why this doesn't work.
//g_Renderer.m_Stats.m_DrawCalls++;
//g_Renderer.m_Stats.m_WaterTris += m_ShoreWaves_VBIndices->m_Count / 3;
CVertexBuffer::Unbind();
}
tech->EndPass();
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
glDisable(GL_BLEND);
glDepthFunc(GL_LEQUAL);
#endif
}
void WaterManager::RecomputeWaterData()
{
if (!m_MapSize)
return;
RecomputeDistanceHeightmap();
RecomputeWindStrength();
CreateWaveMeshes();
}
///////////////////////////////////////////////////////////////////
// Calculate the strength of the wind at a given point on the map.
void WaterManager::RecomputeWindStrength()
{
if (m_MapSize <= 0)
return;
if (m_WindStrength == nullptr)
m_WindStrength = new float[m_MapSize*m_MapSize];
CTerrain* terrain = g_Game->GetWorld()->GetTerrain();
if (!terrain || !terrain->GetHeightMap())
return;
CVector2D windDir = CVector2D(cos(m_WindAngle), sin(m_WindAngle));
int stepSize = 10;
ssize_t windX = -round(stepSize * windDir.X);
ssize_t windY = -round(stepSize * windDir.Y);
struct SWindPoint {
SWindPoint(size_t x, size_t y, float strength) : X(x), Y(y), windStrength(strength) {}
ssize_t X;
ssize_t Y;
float windStrength;
};
std::vector startingPoints;
std::vector> movement; // Every increment, move each starting point by all of these.
// Compute starting points (one or two edges of the map) and how much to move each computation increment.
if (fabs(windDir.X) < 0.01f)
{
movement.emplace_back(0, windY > 0.f ? 1 : -1);
startingPoints.reserve(m_MapSize);
size_t start = windY > 0 ? 0 : m_MapSize - 1;
for (size_t x = 0; x < m_MapSize; ++x)
startingPoints.emplace_back(x, start, 0.f);
}
else if (fabs(windDir.Y) < 0.01f)
{
movement.emplace_back(windX > 0.f ? 1 : - 1, 0);
startingPoints.reserve(m_MapSize);
size_t start = windX > 0 ? 0 : m_MapSize - 1;
for (size_t z = 0; z < m_MapSize; ++z)
startingPoints.emplace_back(start, z, 0.f);
}
else
{
startingPoints.reserve(m_MapSize * 2);
// Points along X.
size_t start = windY > 0 ? 0 : m_MapSize - 1;
for (size_t x = 0; x < m_MapSize; ++x)
startingPoints.emplace_back(x, start, 0.f);
// Points along Z, avoid repeating the corner point.
start = windX > 0 ? 0 : m_MapSize - 1;
if (windY > 0)
for (size_t z = 1; z < m_MapSize; ++z)
startingPoints.emplace_back(start, z, 0.f);
else
for (size_t z = 0; z < m_MapSize-1; ++z)
startingPoints.emplace_back(start, z, 0.f);
// Compute movement array.
movement.reserve(std::max(std::abs(windX),std::abs(windY)));
while (windX != 0 || windY != 0)
{
std::pair move = {
windX == 0 ? 0 : windX > 0 ? +1 : -1,
windY == 0 ? 0 : windY > 0 ? +1 : -1
};
windX -= move.first;
windY -= move.second;
movement.push_back(move);
}
}
// We have all starting points ready, move them all until the map is covered.
for (SWindPoint& point : startingPoints)
{
// Starting velocity is 1.0 unless in shallow water.
m_WindStrength[point.Y * m_MapSize + point.X] = 1.f;
float depth = m_WaterHeight - terrain->GetVertexGroundLevel(point.X, point.Y);
if (depth > 0.f && depth < 2.f)
m_WindStrength[point.Y * m_MapSize + point.X] = depth / 2.f;
point.windStrength = m_WindStrength[point.Y * m_MapSize + point.X];
bool onMap = true;
while (onMap)
for (size_t step = 0; step < movement.size(); ++step)
{
// Move wind speed towards the mean.
point.windStrength = 0.15f + point.windStrength * 0.85f;
// Adjust speed based on height difference, a positive height difference slowly increases speed (simulate venturi effect)
// and a lower height reduces speed (wind protection from hills/...)
float heightDiff = std::max(m_WaterHeight, terrain->GetVertexGroundLevel(point.X + movement[step].first, point.Y + movement[step].second)) -
std::max(m_WaterHeight, terrain->GetVertexGroundLevel(point.X, point.Y));
if (heightDiff > 0.f)
point.windStrength = std::min(2.f, point.windStrength + std::min(4.f, heightDiff) / 40.f);
else
point.windStrength = std::max(0.f, point.windStrength + std::max(-4.f, heightDiff) / 5.f);
point.X += movement[step].first;
point.Y += movement[step].second;
if (point.X < 0 || point.X >= static_cast(m_MapSize) || point.Y < 0 || point.Y >= static_cast(m_MapSize))
{
onMap = false;
break;
}
m_WindStrength[point.Y * m_MapSize + point.X] = point.windStrength;
}
}
// TODO: should perhaps blur a little, or change the above code to incorporate neighboring tiles a bit.
}
////////////////////////////////////////////////////////////////////////
// TODO: This will always recalculate for now
void WaterManager::SetMapSize(size_t size)
{
// TODO: Im' blindly trusting the user here.
m_MapSize = size;
m_NeedInfoUpdate = true;
m_updatei0 = 0;
m_updatei1 = size;
m_updatej0 = 0;
m_updatej1 = size;
SAFE_ARRAY_DELETE(m_DistanceHeightmap);
SAFE_ARRAY_DELETE(m_BlurredNormalMap);
SAFE_ARRAY_DELETE(m_WindStrength);
}
////////////////////////////////////////////////////////////////////////
// This will set the bools properly
void WaterManager::UpdateQuality()
{
if (g_RenderingOptions.GetWaterEffects() != m_WaterEffects)
{
m_WaterEffects = g_RenderingOptions.GetWaterEffects();
m_NeedsReloading = true;
}
if (g_RenderingOptions.GetWaterFancyEffects() != m_WaterFancyEffects)
{
m_WaterFancyEffects = g_RenderingOptions.GetWaterFancyEffects();
m_NeedsReloading = true;
}
if (g_RenderingOptions.GetWaterRealDepth() != m_WaterRealDepth)
{
m_WaterRealDepth = g_RenderingOptions.GetWaterRealDepth();
m_NeedsReloading = true;
}
if (g_RenderingOptions.GetWaterRefraction() != m_WaterRefraction)
{
m_WaterRefraction = g_RenderingOptions.GetWaterRefraction();
m_NeedsReloading = true;
}
if (g_RenderingOptions.GetWaterReflection() != m_WaterReflection)
{
m_WaterReflection = g_RenderingOptions.GetWaterReflection();
m_NeedsReloading = true;
}
}
bool WaterManager::WillRenderFancyWater() const
{
return
m_RenderWater && g_VideoMode.GetBackend() != CVideoMode::Backend::GL_ARB &&
g_RenderingOptions.GetWaterEffects() && g_Renderer.GetCapabilities().m_PrettyWater;
}
size_t WaterManager::GetCurrentTextureIndex(const double& period) const
{
ENSURE(period > 0.0);
return static_cast(m_WaterTexTimer * ARRAY_SIZE(m_WaterTexture) / period) % ARRAY_SIZE(m_WaterTexture);
}
size_t WaterManager::GetNextTextureIndex(const double& period) const
{
ENSURE(period > 0.0);
return (GetCurrentTextureIndex(period) + 1) % ARRAY_SIZE(m_WaterTexture);
}
Index: ps/trunk/source/renderer/backend/gl/DeviceCommandContext.cpp
===================================================================
--- ps/trunk/source/renderer/backend/gl/DeviceCommandContext.cpp (nonexistent)
+++ ps/trunk/source/renderer/backend/gl/DeviceCommandContext.cpp (revision 26170)
@@ -0,0 +1,84 @@
+/* Copyright (C) 2022 Wildfire Games.
+ * This file is part of 0 A.D.
+ *
+ * 0 A.D. is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 0 A.D. is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with 0 A.D. If not, see .
+ */
+
+#include "precompiled.h"
+
+#include "DeviceCommandContext.h"
+
+#include "renderer/backend/gl/Texture.h"
+
+namespace Renderer
+{
+
+namespace Backend
+{
+
+namespace GL
+{
+
+// static
+std::unique_ptr CDeviceCommandContext::Create()
+{
+ std::unique_ptr deviceCommandContext(new CDeviceCommandContext());
+
+ return deviceCommandContext;
+}
+
+CDeviceCommandContext::CDeviceCommandContext() = default;
+
+CDeviceCommandContext::~CDeviceCommandContext() = default;
+
+void CDeviceCommandContext::UploadTexture(
+ CTexture* texture, const Format format, const void* data, const size_t dataSize)
+{
+ UploadTextureRegion(texture, format, data, dataSize, 0, 0, texture->GetWidth(), texture->GetHeight());
+}
+
+void CDeviceCommandContext::UploadTextureRegion(CTexture* texture, const Format format, const void* data, const size_t dataSize,
+ const uint32_t xOffset, const uint32_t yOffset, const uint32_t width, const uint32_t height)
+{
+ if (texture->GetType() == CTexture::Type::TEXTURE_2D)
+ {
+ if (texture->GetFormat() == Format::R8G8B8A8 || texture->GetFormat() == Format::A8)
+ {
+ ENSURE(width > 0 && height > 0);
+ ENSURE(texture->GetFormat() == format);
+ const size_t bpp = format == Format::R8G8B8A8 ? 4 : 1;
+ ENSURE(dataSize == width * height * bpp);
+ ENSURE(xOffset + width <= texture->GetWidth());
+ ENSURE(yOffset + height <= texture->GetHeight());
+
+ glBindTexture(GL_TEXTURE_2D, texture->GetHandle());
+ glTexSubImage2D(GL_TEXTURE_2D, 0,
+ xOffset, yOffset, width, height,
+ format == Format::R8G8B8A8 ? GL_RGBA : GL_ALPHA, GL_UNSIGNED_BYTE, data);
+ glBindTexture(GL_TEXTURE_2D, 0);
+
+ ogl_WarnIfError();
+ }
+ else
+ debug_warn("Unsupported format");
+ }
+ else
+ debug_warn("Unsupported type");
+}
+
+} // namespace GL
+
+} // namespace Backend
+
+} // namespace Renderer
Property changes on: ps/trunk/source/renderer/backend/gl/DeviceCommandContext.cpp
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Index: ps/trunk/source/renderer/backend/gl/DeviceCommandContext.h
===================================================================
--- ps/trunk/source/renderer/backend/gl/DeviceCommandContext.h (nonexistent)
+++ ps/trunk/source/renderer/backend/gl/DeviceCommandContext.h (revision 26170)
@@ -0,0 +1,57 @@
+/* Copyright (C) 2022 Wildfire Games.
+ * This file is part of 0 A.D.
+ *
+ * 0 A.D. is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 0 A.D. is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with 0 A.D. If not, see .
+ */
+
+#ifndef INCLUDED_RENDERER_GL_DEVICECOMMANDCONTEXT
+#define INCLUDED_RENDERER_GL_DEVICECOMMANDCONTEXT
+
+#include "renderer/backend/Format.h"
+
+#include
+
+namespace Renderer
+{
+
+namespace Backend
+{
+
+namespace GL
+{
+
+class CTexture;
+
+class CDeviceCommandContext
+{
+public:
+ ~CDeviceCommandContext();
+
+ static std::unique_ptr Create();
+
+ void UploadTexture(CTexture* texture, const Format format, const void* data, const size_t dataSize);
+ void UploadTextureRegion(CTexture* texture, const Format format, const void* data, const size_t dataSize,
+ const uint32_t xOffset, const uint32_t yOffset, const uint32_t width, const uint32_t height);
+
+private:
+ CDeviceCommandContext();
+};
+
+} // namespace GL
+
+} // namespace Backend
+
+} // namespace Renderer
+
+#endif // INCLUDED_RENDERER_GL_DEVICECOMMANDCONTEXT
Property changes on: ps/trunk/source/renderer/backend/gl/DeviceCommandContext.h
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Index: ps/trunk/source/renderer/backend/gl/Texture.cpp
===================================================================
--- ps/trunk/source/renderer/backend/gl/Texture.cpp (revision 26169)
+++ ps/trunk/source/renderer/backend/gl/Texture.cpp (revision 26170)
@@ -1,174 +1,255 @@
-/* Copyright (C) 2021 Wildfire Games.
+/* Copyright (C) 2022 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#include "precompiled.h"
#include "Texture.h"
+#include "lib/code_annotation.h"
#include "lib/config2.h"
#include "lib/res/graphics/ogl_tex.h"
#include "renderer/backend/gl/Device.h"
namespace Renderer
{
namespace Backend
{
namespace GL
{
namespace
{
GLint CalculateMinFilter(const Sampler::Desc& defaultSamplerDesc, const uint32_t mipCount)
{
if (mipCount == 1)
return defaultSamplerDesc.minFilter == Sampler::Filter::LINEAR ? GL_LINEAR : GL_NEAREST;
if (defaultSamplerDesc.minFilter == Sampler::Filter::LINEAR)
return defaultSamplerDesc.mipFilter == Sampler::Filter::LINEAR ? GL_LINEAR_MIPMAP_LINEAR : GL_LINEAR_MIPMAP_NEAREST;
return defaultSamplerDesc.mipFilter == Sampler::Filter::LINEAR ? GL_NEAREST_MIPMAP_LINEAR : GL_NEAREST_MIPMAP_NEAREST;
}
GLint AddressModeToGLEnum(Sampler::AddressMode addressMode)
{
switch (addressMode)
{
case Sampler::AddressMode::REPEAT: return GL_REPEAT;
case Sampler::AddressMode::MIRRORED_REPEAT: return GL_MIRRORED_REPEAT;
case Sampler::AddressMode::CLAMP_TO_EDGE: return GL_CLAMP_TO_EDGE;
case Sampler::AddressMode::CLAMP_TO_BORDER: return GL_CLAMP_TO_BORDER;
}
return GL_REPEAT;
}
GLenum TypeToGLEnum(CTexture::Type type)
{
GLenum target = GL_TEXTURE_2D;
switch (type)
{
case CTexture::Type::TEXTURE_2D:
target = GL_TEXTURE_2D;
break;
case CTexture::Type::TEXTURE_2D_MULTISAMPLE:
#if CONFIG2_GLES
ENSURE(false && "Multisample textures are unsupported on GLES");
#else
target = GL_TEXTURE_2D_MULTISAMPLE;
#endif
break;
case CTexture::Type::TEXTURE_CUBE:
target = GL_TEXTURE_CUBE_MAP;
break;
}
return target;
}
} // anonymous namespace
// static
std::unique_ptr CTexture::Create2D(const Format format,
const uint32_t width, const uint32_t height,
- const Sampler::Desc& defaultSamplerDesc, const uint32_t mipCount)
+ const Sampler::Desc& defaultSamplerDesc, const uint32_t mipCount, const uint32_t sampleCount)
{
- return Create(Type::TEXTURE_2D, format, width, height, defaultSamplerDesc, mipCount);
+ return Create(Type::TEXTURE_2D, format, width, height, defaultSamplerDesc, mipCount, sampleCount);
}
// static
std::unique_ptr CTexture::Create(const Type type, const Format format,
const uint32_t width, const uint32_t height,
- const Sampler::Desc& defaultSamplerDesc, const uint32_t mipCount)
+ const Sampler::Desc& defaultSamplerDesc, const uint32_t mipCount, const uint32_t sampleCount)
{
std::unique_ptr texture(new CTexture());
ENSURE(format != Format::UNDEFINED);
ENSURE(width > 0 && height > 0 && mipCount > 0);
+ ENSURE((type == Type::TEXTURE_2D_MULTISAMPLE && sampleCount > 1) || sampleCount == 1);
texture->m_Format = format;
texture->m_Width = width;
texture->m_Height = height;
texture->m_MipCount = mipCount;
glGenTextures(1, &texture->m_Handle);
ogl_WarnIfError();
glActiveTextureARB(GL_TEXTURE0);
const GLenum target = TypeToGLEnum(type);
glBindTexture(target, texture->m_Handle);
// It's forbidden to set sampler state for multisample textures.
if (type != Type::TEXTURE_2D_MULTISAMPLE)
{
glTexParameteri(target, GL_TEXTURE_MIN_FILTER, CalculateMinFilter(defaultSamplerDesc, mipCount));
glTexParameteri(target, GL_TEXTURE_MAG_FILTER, defaultSamplerDesc.magFilter == Sampler::Filter::LINEAR ? GL_LINEAR : GL_NEAREST);
ogl_WarnIfError();
glTexParameteri(target, GL_TEXTURE_WRAP_S, AddressModeToGLEnum(defaultSamplerDesc.addressModeU));
glTexParameteri(target, GL_TEXTURE_WRAP_T, AddressModeToGLEnum(defaultSamplerDesc.addressModeV));
}
#if !CONFIG2_GLES
if (type == Type::TEXTURE_CUBE)
glTexParameteri(target, GL_TEXTURE_WRAP_R, AddressModeToGLEnum(defaultSamplerDesc.addressModeW));
#endif
ogl_WarnIfError();
#if !CONFIG2_GLES
glTexParameteri(target, GL_TEXTURE_BASE_LEVEL, 0);
glTexParameteri(target, GL_TEXTURE_MAX_LEVEL, mipCount - 1);
if (defaultSamplerDesc.mipLODBias != 0.0f)
glTexParameteri(target, GL_TEXTURE_LOD_BIAS, defaultSamplerDesc.mipLODBias);
#endif // !CONFIG2_GLES
if (type == Type::TEXTURE_2D && defaultSamplerDesc.anisotropyEnabled && ogl_tex_has_anisotropy())
glTexParameterf(target, GL_TEXTURE_MAX_ANISOTROPY_EXT, defaultSamplerDesc.maxAnisotropy);
if (defaultSamplerDesc.addressModeU == Sampler::AddressMode::CLAMP_TO_BORDER ||
defaultSamplerDesc.addressModeV == Sampler::AddressMode::CLAMP_TO_BORDER ||
defaultSamplerDesc.addressModeW == Sampler::AddressMode::CLAMP_TO_BORDER)
{
glTexParameterfv(target, GL_TEXTURE_BORDER_COLOR, defaultSamplerDesc.borderColor.AsFloatArray());
}
ogl_WarnIfError();
+ ENSURE(mipCount == 1);
+
+ if (type == CTexture::Type::TEXTURE_2D)
+ {
+ GLint internalFormat = GL_RGBA;
+ // Actually pixel data is nullptr so it doesn't make sense to account
+ // it, but in theory some buggy drivers might complain about invalid
+ // pixel format.
+ GLenum pixelFormat = GL_RGBA;
+ GLenum pixelType = GL_UNSIGNED_BYTE;
+ switch (format)
+ {
+ case Format::UNDEFINED:
+ debug_warn("Texture should defined format");
+ break;
+ case Format::R8G8B8A8:
+ break;
+ case Format::A8:
+ internalFormat = GL_ALPHA;
+ pixelFormat = GL_ALPHA;
+ pixelType = GL_UNSIGNED_BYTE;
+ break;
+#if CONFIG2_GLES
+ // GLES requires pixel type == UNSIGNED_SHORT or UNSIGNED_INT for depth.
+ case Format::D16: FALLTHROUGH;
+ case Format::D24: FALLTHROUGH;
+ case Format::D32:
+ internalFormat = GL_DEPTH_COMPONENT;
+ pixelFormat = GL_DEPTH_COMPONENT;
+ pixelType = GL_UNSIGNED_SHORT;
+ break;
+ case Format::D24_S8:
+ debug_warn("Unsupported format");
+ break;
+#else
+ case Format::D16:
+ internalFormat = GL_DEPTH_COMPONENT16;
+ pixelFormat = GL_DEPTH_COMPONENT;
+ pixelType = GL_UNSIGNED_SHORT;
+ break;
+ case Format::D24:
+ internalFormat = GL_DEPTH_COMPONENT24;
+ pixelFormat = GL_DEPTH_COMPONENT;
+ pixelType = GL_UNSIGNED_SHORT;
+ break;
+ case Format::D32:
+ internalFormat = GL_DEPTH_COMPONENT32;
+ pixelFormat = GL_DEPTH_COMPONENT;
+ pixelType = GL_UNSIGNED_SHORT;
+ break;
+ case Format::D24_S8:
+ internalFormat = GL_DEPTH24_STENCIL8_EXT;
+ pixelFormat = GL_DEPTH_STENCIL_EXT;
+ pixelType = GL_UNSIGNED_INT_24_8_EXT;
+ break;
+#endif
+ }
+ glTexImage2D(target, 0, internalFormat, width, height, 0, pixelFormat, pixelType, nullptr);
+ }
+ else if (type == CTexture::Type::TEXTURE_2D_MULTISAMPLE)
+ {
+#if !CONFIG2_GLES
+ if (format == Format::R8G8B8A8)
+ {
+ glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, sampleCount, GL_RGBA8, width, height, GL_TRUE);
+ }
+ else if (format == Format::D24_S8)
+ {
+ glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, sampleCount, GL_DEPTH24_STENCIL8_EXT, width, height, GL_TRUE);
+ }
+ else
+#endif // !CONFIG2_GLES
+ {
+ debug_warn("Unsupported format");
+ }
+ }
+
+ ogl_WarnIfError();
+
glBindTexture(target, 0);
return texture;
}
CTexture::CTexture() = default;
CTexture::~CTexture()
{
if (m_Handle)
glDeleteTextures(1, &m_Handle);
}
} // namespace GL
} // namespace Backend
} // namespace Renderer
Index: ps/trunk/source/renderer/backend/gl/Texture.h
===================================================================
--- ps/trunk/source/renderer/backend/gl/Texture.h (revision 26169)
+++ ps/trunk/source/renderer/backend/gl/Texture.h (revision 26170)
@@ -1,88 +1,88 @@
-/* Copyright (C) 2021 Wildfire Games.
+/* Copyright (C) 2022 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#ifndef INCLUDED_RENDERER_BACKEND_GL_TEXTURE
#define INCLUDED_RENDERER_BACKEND_GL_TEXTURE
#include "lib/ogl.h"
#include "renderer/backend/Format.h"
#include "renderer/backend/Sampler.h"
#include
namespace Renderer
{
namespace Backend
{
namespace GL
{
/**
* Represents a low-level GL texture, encapsulates all properties initialization.
*/
class CTexture
{
public:
enum class Type
{
TEXTURE_2D,
TEXTURE_2D_MULTISAMPLE,
TEXTURE_CUBE
};
~CTexture();
// GL before 3.3 doesn't support sampler objects, so each texture should have
// an own default sampler.
static std::unique_ptr Create(const Type type, const Format format,
const uint32_t width, const uint32_t height,
- const Sampler::Desc& defaultSamplerDesc, const uint32_t mipCount);
+ const Sampler::Desc& defaultSamplerDesc, const uint32_t mipCount, const uint32_t sampleCount);
// Shorthands for particular types.
static std::unique_ptr Create2D(const Format format,
const uint32_t width, const uint32_t height,
- const Sampler::Desc& defaultSamplerDesc, const uint32_t mipCount = 1);
+ const Sampler::Desc& defaultSamplerDesc, const uint32_t mipCount = 1, const uint32_t sampleCount = 1);
GLuint GetHandle() const { return m_Handle; }
Type GetType() const { return m_Type; }
Format GetFormat() const { return m_Format; }
uint32_t GetWidth() const { return m_Width; }
uint32_t GetHeight() const { return m_Height; }
uint32_t GetMipCount() const { return m_MipCount; }
private:
CTexture();
GLuint m_Handle = 0;
Type m_Type = Type::TEXTURE_2D;
Format m_Format = Format::UNDEFINED;
uint32_t m_Width = 0;
uint32_t m_Height = 0;
uint32_t m_MipCount = 0;
};
} // namespace GL
} // namespace Backend
} // namespace Renderer
#endif // INCLUDED_RENDERER_BACKEND_GL_TEXTURE
Index: ps/trunk/source/tools/atlas/GameInterface/ActorViewer.cpp
===================================================================
--- ps/trunk/source/tools/atlas/GameInterface/ActorViewer.cpp (revision 26169)
+++ ps/trunk/source/tools/atlas/GameInterface/ActorViewer.cpp (revision 26170)
@@ -1,558 +1,558 @@
/* Copyright (C) 2022 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#include "precompiled.h"
#include "ActorViewer.h"
#include "View.h"
#include "graphics/ColladaManager.h"
#include "graphics/LOSTexture.h"
#include "graphics/MiniMapTexture.h"
#include "graphics/Model.h"
#include "graphics/ModelDef.h"
#include "graphics/ObjectManager.h"
#include "graphics/ParticleManager.h"
#include "graphics/Patch.h"
#include "graphics/SkeletonAnimManager.h"
#include "graphics/Terrain.h"
#include "graphics/TerrainTextureEntry.h"
#include "graphics/TerrainTextureManager.h"
#include "graphics/TerritoryTexture.h"
#include "graphics/Unit.h"
#include "graphics/UnitManager.h"
#include "graphics/Overlay.h"
#include "maths/MathUtil.h"
#include "ps/Filesystem.h"
#include "ps/CLogger.h"
#include "ps/GameSetup/Config.h"
#include "ps/ProfileViewer.h"
#include "renderer/Renderer.h"
#include "renderer/RenderingOptions.h"
#include "renderer/Scene.h"
#include "renderer/SceneRenderer.h"
#include "renderer/SkyManager.h"
#include "renderer/WaterManager.h"
#include "scriptinterface/ScriptContext.h"
#include "simulation2/Simulation2.h"
#include "simulation2/components/ICmpAttack.h"
#include "simulation2/components/ICmpOwnership.h"
#include "simulation2/components/ICmpPosition.h"
#include "simulation2/components/ICmpRangeManager.h"
#include "simulation2/components/ICmpTerrain.h"
#include "simulation2/components/ICmpUnitMotion.h"
#include "simulation2/components/ICmpVisual.h"
#include "simulation2/components/ICmpWaterManager.h"
#include "simulation2/helpers/Render.h"
struct ActorViewerImpl : public Scene
{
NONCOPYABLE(ActorViewerImpl);
public:
ActorViewerImpl() :
Entity(INVALID_ENTITY),
Terrain(),
ColladaManager(g_VFS),
MeshManager(ColladaManager),
SkeletonAnimManager(ColladaManager),
UnitManager(),
Simulation2(&UnitManager, g_ScriptContext, &Terrain),
ObjectManager(MeshManager, SkeletonAnimManager, Simulation2),
LOSTexture(Simulation2),
TerritoryTexture(Simulation2),
MiniMapTexture(Simulation2)
{
UnitManager.SetObjectManager(ObjectManager);
}
entity_id_t Entity;
CStrW CurrentUnitID;
CStr CurrentUnitAnim;
float CurrentSpeed;
bool WalkEnabled;
bool GroundEnabled;
bool WaterEnabled;
bool ShadowsEnabled;
// Whether shadows, sky and water are enabled outside of the actor viewer.
bool OldShadows;
bool OldSky;
bool OldWater;
bool SelectionBoxEnabled;
bool AxesMarkerEnabled;
int PropPointsMode; // 0 disabled, 1 for point markers, 2 for point markers + axes
CTerrain Terrain;
CColladaManager ColladaManager;
CMeshManager MeshManager;
CSkeletonAnimManager SkeletonAnimManager;
CUnitManager UnitManager;
CSimulation2 Simulation2;
CObjectManager ObjectManager; // Keep this after Simulation2 - it needs it for initialisation.
CLOSTexture LOSTexture;
CTerritoryTexture TerritoryTexture;
CMiniMapTexture MiniMapTexture;
SOverlayLine SelectionBoxOverlay;
SOverlayLine AxesMarkerOverlays[3];
std::vector Props;
std::vector PropPointOverlays;
// Simplistic implementation of the Scene interface
virtual void EnumerateObjects(const CFrustum& frustum, SceneCollector* c)
{
if (GroundEnabled)
{
for (ssize_t pj = 0; pj < Terrain.GetPatchesPerSide(); ++pj)
for (ssize_t pi = 0; pi < Terrain.GetPatchesPerSide(); ++pi)
c->Submit(Terrain.GetPatch(pi, pj));
}
CmpPtr cmpVisual(Simulation2, Entity);
if (cmpVisual)
{
// add selection box outlines manually
if (SelectionBoxEnabled)
{
SelectionBoxOverlay.m_Color = CColor(35/255.f, 86/255.f, 188/255.f, .75f); // pretty blue
SelectionBoxOverlay.m_Thickness = 0.1f;
SimRender::ConstructBoxOutline(cmpVisual->GetSelectionBox(), SelectionBoxOverlay);
c->Submit(&SelectionBoxOverlay);
}
// add origin axis thingy
if (AxesMarkerEnabled)
{
CMatrix3D worldSpaceAxes;
// offset from the ground a little bit to prevent fighting with the floor texture (also note: SetTranslation
// sets the identity 3x3 transformation matrix, which are the world axes)
worldSpaceAxes.SetTranslation(cmpVisual->GetPosition() + CVector3D(0, 0.02f, 0));
SimRender::ConstructAxesMarker(worldSpaceAxes, AxesMarkerOverlays[0], AxesMarkerOverlays[1], AxesMarkerOverlays[2]);
c->Submit(&AxesMarkerOverlays[0]);
c->Submit(&AxesMarkerOverlays[1]);
c->Submit(&AxesMarkerOverlays[2]);
}
// add prop point overlays
if (PropPointsMode > 0 && Props.size() > 0)
{
PropPointOverlays.clear(); // doesn't clear capacity, but should be ok since the number of prop points is usually pretty limited
for (size_t i = 0; i < Props.size(); ++i)
{
CModel::Prop& prop = Props[i];
if (prop.m_Model) // should always be the case
{
// prop point positions are automatically updated during animations etc. by CModel::ValidatePosition
const CMatrix3D& propCoordSystem = prop.m_Model->GetTransform();
SOverlayLine pointGimbal;
pointGimbal.m_Color = CColor(1.f, 0.f, 1.f, 1.f);
SimRender::ConstructGimbal(propCoordSystem.GetTranslation(), 0.05f, pointGimbal);
PropPointOverlays.push_back(pointGimbal);
if (PropPointsMode > 1)
{
// scale the prop axes coord system down a bit to distinguish them from the main world-space axes markers
CMatrix3D displayCoordSystem = propCoordSystem;
displayCoordSystem.Scale(0.5f, 0.5f, 0.5f);
// revert translation scaling
displayCoordSystem._14 = propCoordSystem._14;
displayCoordSystem._24 = propCoordSystem._24;
displayCoordSystem._34 = propCoordSystem._34;
// construct an XYZ axes marker for the prop's coordinate system
SOverlayLine xAxis, yAxis, zAxis;
SimRender::ConstructAxesMarker(displayCoordSystem, xAxis, yAxis, zAxis);
PropPointOverlays.push_back(xAxis);
PropPointOverlays.push_back(yAxis);
PropPointOverlays.push_back(zAxis);
}
}
}
for (size_t i = 0; i < PropPointOverlays.size(); ++i)
{
c->Submit(&PropPointOverlays[i]);
}
}
}
// send a RenderSubmit message so the components can submit their visuals to the renderer
Simulation2.RenderSubmit(*c, frustum, false);
}
virtual CLOSTexture& GetLOSTexture()
{
return LOSTexture;
}
virtual CTerritoryTexture& GetTerritoryTexture()
{
return TerritoryTexture;
}
virtual CMiniMapTexture& GetMiniMapTexture()
{
return MiniMapTexture;
}
/**
* Recursively fetches the props of the currently displayed entity model and its submodels, and stores them for rendering.
*/
void UpdatePropList();
void UpdatePropListRecursive(CModelAbstract* model);
};
void ActorViewerImpl::UpdatePropList()
{
Props.clear();
CmpPtr cmpVisual(Simulation2, Entity);
if (cmpVisual)
{
CUnit* unit = cmpVisual->GetUnit();
if (unit)
{
CModelAbstract& modelAbstract = unit->GetModel();
UpdatePropListRecursive(&modelAbstract);
}
}
}
void ActorViewerImpl::UpdatePropListRecursive(CModelAbstract* modelAbstract)
{
ENSURE(modelAbstract);
CModel* model = modelAbstract->ToCModel();
if (model)
{
std::vector& modelProps = model->GetProps();
for (CModel::Prop& modelProp : modelProps)
{
Props.push_back(modelProp);
if (modelProp.m_Model)
UpdatePropListRecursive(modelProp.m_Model);
}
}
}
ActorViewer::ActorViewer()
: m(*new ActorViewerImpl())
{
m.WalkEnabled = false;
m.GroundEnabled = true;
m.WaterEnabled = false;
m.ShadowsEnabled = g_RenderingOptions.GetShadows();
m.SelectionBoxEnabled = false;
m.AxesMarkerEnabled = false;
m.PropPointsMode = 0;
// Create a tiny empty piece of terrain, just so we can put shadows
// on it without having to think too hard
m.Terrain.Initialize(2, NULL);
CTerrainTextureEntry* tex = g_TexMan.FindTexture("whiteness");
if (tex)
{
for (ssize_t pi = 0; pi < m.Terrain.GetPatchesPerSide(); ++pi)
{
for (ssize_t pj = 0; pj < m.Terrain.GetPatchesPerSide(); ++pj)
{
CPatch* patch = m.Terrain.GetPatch(pi, pj);
for (ssize_t i = 0; i < PATCH_SIZE; ++i)
{
for (ssize_t j = 0; j < PATCH_SIZE; ++j)
{
CMiniPatch& mp = patch->m_MiniPatches[i][j];
mp.Tex = tex;
mp.Priority = 0;
}
}
}
}
}
else
{
debug_warn(L"Failed to load whiteness texture");
}
// Prepare the simulation
m.Simulation2.LoadDefaultScripts();
m.Simulation2.ResetState();
// Set player data
m.Simulation2.SetMapSettings(m.Simulation2.GetPlayerDefaults());
m.Simulation2.LoadPlayerSettings(true);
// Tell the simulation we've already loaded the terrain
CmpPtr cmpTerrain(m.Simulation2, SYSTEM_ENTITY);
if (cmpTerrain)
cmpTerrain->ReloadTerrain(false);
// Remove FOW since we're in Atlas
CmpPtr cmpRangeManager(m.Simulation2, SYSTEM_ENTITY);
if (cmpRangeManager)
cmpRangeManager->SetLosRevealAll(-1, true);
m.Simulation2.InitGame();
}
ActorViewer::~ActorViewer()
{
delete &m;
}
CSimulation2* ActorViewer::GetSimulation2()
{
return &m.Simulation2;
}
entity_id_t ActorViewer::GetEntity()
{
return m.Entity;
}
void ActorViewer::UnloadObjects()
{
m.ObjectManager.UnloadObjects();
}
void ActorViewer::SetActor(const CStrW& name, const CStr& animation, player_id_t playerID)
{
bool needsAnimReload = false;
CStrW id = name;
// Recreate the entity, if we don't have one or if the new one is different
if (m.Entity == INVALID_ENTITY || id != m.CurrentUnitID)
{
// Delete the old entity (if any)
if (m.Entity != INVALID_ENTITY)
{
m.Simulation2.DestroyEntity(m.Entity);
m.Simulation2.FlushDestroyedEntities();
m.Entity = INVALID_ENTITY;
}
// Clear particles associated with deleted entity
g_Renderer.GetSceneRenderer().GetParticleManager().ClearUnattachedEmitters();
// If there's no actor to display, return with nothing loaded
if (id.empty())
return;
m.Entity = m.Simulation2.AddEntity(L"preview|" + id);
if (m.Entity == INVALID_ENTITY)
return;
CmpPtr cmpPosition(m.Simulation2, m.Entity);
if (cmpPosition)
{
ssize_t c = TERRAIN_TILE_SIZE * m.Terrain.GetPatchesPerSide()*PATCH_SIZE/2;
cmpPosition->JumpTo(entity_pos_t::FromInt(c), entity_pos_t::FromInt(c));
cmpPosition->SetYRotation(entity_angle_t::Pi());
}
CmpPtr cmpOwnership(m.Simulation2, m.Entity);
if (cmpOwnership)
cmpOwnership->SetOwner(playerID);
needsAnimReload = true;
}
if (animation != m.CurrentUnitAnim)
needsAnimReload = true;
if (needsAnimReload)
{
// Emulate the typical simulation animation behaviour.
CStr anim = animation.LowerCase();
float speed = 1.0f;
// Speed will be ignored if we have a repeat time.
float repeatTime = 0.0f;
m.CurrentSpeed = 0.0f;
if (anim == "walk")
{
CmpPtr cmpUnitMotion(m.Simulation2, m.Entity);
if (cmpUnitMotion)
speed = cmpUnitMotion->GetWalkSpeed().ToFloat();
else
speed = 7.f; // Typical unit walk speed.
m.CurrentSpeed = speed;
}
else if (anim == "run")
{
CmpPtr cmpUnitMotion(m.Simulation2, m.Entity);
if (cmpUnitMotion)
speed = cmpUnitMotion->GetWalkSpeed().ToFloat() * cmpUnitMotion->GetRunMultiplier().ToFloat();
else
speed = 12.f; // Typical unit run speed.
m.CurrentSpeed = speed;
}
else if (anim.Find("attack_") == 0)
{
CmpPtr cmpAttack(m.Simulation2, m.Entity);
if (cmpAttack)
for (const CStr& type : cmpAttack->GetAttackTypes())
if (anim == "attack_" + type.LowerCase())
{
repeatTime = GetRepeatTimeByAttackType(type);
break;
}
}
CmpPtr cmpVisual(m.Simulation2, m.Entity);
if (cmpVisual)
{
// TODO: SetEntitySelection(anim)
cmpVisual->SelectAnimation(anim, false, fixed::FromFloat(speed));
if (repeatTime > 0.0f)
cmpVisual->SetAnimationSyncRepeat(fixed::FromFloat(repeatTime));
}
// update prop list for new entity/animation (relies on needsAnimReload also getting called for entire entity changes)
m.UpdatePropList();
}
m.CurrentUnitID = id;
m.CurrentUnitAnim = animation;
}
void ActorViewer::SetEnabled(bool enabled)
{
if (enabled)
{
// Set shadows, sky and water.
m.OldShadows = g_RenderingOptions.GetShadows();
SetShadowsEnabled(m.ShadowsEnabled);
m.OldSky = g_Renderer.GetSceneRenderer().GetSkyManager().GetRenderSky();
g_Renderer.GetSceneRenderer().GetSkyManager().SetRenderSky(false);
m.OldWater = g_Renderer.GetSceneRenderer().GetWaterManager().m_RenderWater;
g_Renderer.GetSceneRenderer().GetWaterManager().m_RenderWater = m.WaterEnabled;
}
else
{
// Restore the old renderer state
SetShadowsEnabled(m.OldShadows);
g_Renderer.GetSceneRenderer().GetSkyManager().SetRenderSky(m.OldSky);
g_Renderer.GetSceneRenderer().GetWaterManager().m_RenderWater = m.OldWater;
}
}
void ActorViewer::SetWalkEnabled(bool enabled) { m.WalkEnabled = enabled; }
void ActorViewer::SetGroundEnabled(bool enabled) { m.GroundEnabled = enabled; }
void ActorViewer::SetWaterEnabled(bool enabled)
{
m.WaterEnabled = enabled;
// Adjust water level
entity_pos_t waterLevel = entity_pos_t::FromFloat(enabled ? 10.f : 0.f);
CmpPtr cmpWaterManager(m.Simulation2, SYSTEM_ENTITY);
if (cmpWaterManager)
cmpWaterManager->SetWaterLevel(waterLevel);
}
void ActorViewer::SetShadowsEnabled(bool enabled) {
g_RenderingOptions.SetShadows(enabled);
m.ShadowsEnabled = enabled;
}
void ActorViewer::SetBoundingBoxesEnabled(bool enabled) { m.SelectionBoxEnabled = enabled; }
void ActorViewer::SetAxesMarkerEnabled(bool enabled) { m.AxesMarkerEnabled = enabled; }
void ActorViewer::SetPropPointsMode(int mode) { m.PropPointsMode = mode; }
void ActorViewer::SetStatsEnabled(bool enabled)
{
if (enabled)
g_ProfileViewer.ShowTable("renderer");
else
g_ProfileViewer.ShowTable("");
}
float ActorViewer::GetRepeatTimeByAttackType(const std::string& type) const
{
CmpPtr cmpAttack(m.Simulation2, m.Entity);
if (cmpAttack)
return cmpAttack->GetRepeatTime(type);
return 0.0f;
}
void ActorViewer::Render()
{
// TODO: ActorViewer should reuse CRenderer code and not duplicate it.
m.Terrain.MakeDirty(RENDERDATA_UPDATE_COLOR);
// Set simulation context for rendering purposes
g_Renderer.GetSceneRenderer().SetSimulation(&m.Simulation2);
g_Renderer.BeginFrame();
// Find the centre of the interesting region, in the middle of the patch
// and half way up the model (assuming there is one)
CVector3D centre;
CmpPtr cmpVisual(m.Simulation2, m.Entity);
if (cmpVisual)
cmpVisual->GetBounds().GetCenter(centre);
else
centre.Y = 0.f;
centre.X = centre.Z = TERRAIN_TILE_SIZE * m.Terrain.GetPatchesPerSide()*PATCH_SIZE/2;
CCamera camera = AtlasView::GetView_Actor()->GetCamera();
camera.m_Orientation.Translate(centre.X, centre.Y, centre.Z);
camera.UpdateFrustum();
g_Renderer.GetSceneRenderer().SetSceneCamera(camera, camera);
- g_Renderer.GetSceneRenderer().RenderScene(m);
+ g_Renderer.GetSceneRenderer().RenderScene(g_Renderer.GetDeviceCommandContext(), m);
glDisable(GL_DEPTH_TEST);
g_Logger->Render();
g_ProfileViewer.RenderProfile();
glEnable(GL_DEPTH_TEST);
g_Renderer.EndFrame();
ogl_WarnIfError();
}
void ActorViewer::Update(float simFrameLength, float realFrameLength)
{
m.Simulation2.Update((int)(simFrameLength*1000));
m.Simulation2.Interpolate(simFrameLength, 0, realFrameLength);
if (m.WalkEnabled && m.CurrentSpeed)
{
CmpPtr cmpPosition(m.Simulation2, m.Entity);
if (cmpPosition)
{
// Move the model by speed*simFrameLength forwards
float z = cmpPosition->GetPosition().Z.ToFloat();
z -= m.CurrentSpeed*simFrameLength;
// Wrap at the edges, so it doesn't run off into the horizon
ssize_t c = TERRAIN_TILE_SIZE * m.Terrain.GetPatchesPerSide()*PATCH_SIZE/2;
if (z < c - TERRAIN_TILE_SIZE*PATCH_SIZE * 0.1f)
z = c + TERRAIN_TILE_SIZE*PATCH_SIZE * 0.1f;
cmpPosition->JumpTo(cmpPosition->GetPosition().X, entity_pos_t::FromFloat(z));
}
}
}