Implemented motion vector grid.

Have to do the motion reverse-propagation on the GPU, as the warp mesh is also calculated there. This is done storing the U/V warp coordinates in a separate texture attachment and then read the (already interpolated) coordinate in the next frame.

Added a second color buffer output to the HLSL shader to accomplish it with both the default warp shader and preset-supplied ones. Luckily, the HLSL transpiler knows what to do.

Since all framebuffer textures must match in size, this adds a bit more VRAM usage. We also need to mask all other draw calls from writing to the second texture expect in the warp shader.
This commit is contained in:
Kai Blaschke
2023-03-15 20:26:12 +01:00
parent ce0e5853b2
commit 904292e577
14 changed files with 321 additions and 121 deletions

View File

@ -74,25 +74,36 @@ void MilkdropPreset::RenderFrame(const libprojectM::Audio::FrameAudioData& audio
m_state.renderContext = renderContext;
// Update framebuffer size if needed
m_framebuffer.SetSize(renderContext.viewportSizeX, renderContext.viewportSizeY);
if (m_framebuffer.SetSize(renderContext.viewportSizeX, renderContext.viewportSizeY))
{
m_isFirstFrame = true;
}
m_state.mainTexture = m_framebuffer.GetColorAttachmentTexture(m_previousFrameBuffer, 0);
// First evaluate per-frame code
PerFrameUpdate();
// Motion vector field. Drawn to the previous frame texture before warping it.
m_framebuffer.Bind(m_previousFrameBuffer);
m_motionVectors.Draw(m_perFrameContext);
// Only do it after drawing one frame after init or resize.
if (!m_isFirstFrame)
{
m_framebuffer.Bind(m_previousFrameBuffer);
m_motionVectors.Draw(m_perFrameContext, m_framebuffer.GetColorAttachmentTexture(m_previousFrameBuffer, 1));
}
// We now draw to the first framebuffer, but read from the second one for warping and textured shapes.
m_framebuffer.BindRead(m_previousFrameBuffer);
m_framebuffer.BindDraw(m_currentFrameBuffer);
// Unmask the motion vector u/v texture for the warp mesh draw and clean both buffers.
m_framebuffer.MaskDrawBuffer(1, false);
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
// Draw previous frame image warped via per-pixel mesh and warp shader
m_perPixelMesh.Draw(m_state, m_perFrameContext, m_perPixelContext);
m_framebuffer.MaskDrawBuffer(1, true);
// Update blur textures
m_state.blurTexture.Update(m_state, m_perFrameContext);
@ -135,6 +146,8 @@ void MilkdropPreset::RenderFrame(const libprojectM::Audio::FrameAudioData& audio
// Swap framebuffers for the next frame.
std::swap(m_currentFrameBuffer, m_previousFrameBuffer);
m_isFirstFrame = false;
}
@ -189,8 +202,14 @@ void MilkdropPreset::Load(std::istream& stream)
void MilkdropPreset::InitializePreset(PresetFileParser& parsedFile)
{
// Create the offscreen rendering surfaces.
m_framebuffer.CreateColorAttachment(0, 0);
m_framebuffer.CreateColorAttachment(1, 0);
m_framebuffer.CreateColorAttachment(0, 0); // Main image
m_framebuffer.CreateColorAttachment(0, 1, GL_RG32F, GL_RG, GL_FLOAT); // Motion vector u/v
m_framebuffer.CreateColorAttachment(1, 0); // Main image
m_framebuffer.CreateColorAttachment(1, 1, GL_RG32F, GL_RG, GL_FLOAT); // Motion vector u/v
// Mask the motion vector buffer by default.
m_framebuffer.MaskDrawBuffer(1, true);
Framebuffer::Unbind();
// Load global init variables into the state

View File

@ -122,4 +122,6 @@ private:
Border m_border; //!< Inner/outer borders.
FinalComposite m_finalComposite; //!< Final composite shader or filters.
bool m_isFirstFrame{true}; //!< Controls drawing the motion vectors starting with the second frame.
};

View File

@ -327,7 +327,7 @@ void MilkdropShader::PreprocessPresetShader(std::string& program)
if (m_type == ShaderType::WarpShader)
{
program.replace(int(found), 11, "void PS(float4 _vDiffuse : COLOR, float4 _uv : TEXCOORD0, float2 _rad_ang : TEXCOORD1, out float4 _return_value : COLOR)\n");
program.replace(int(found), 11, "void PS(float4 _vDiffuse : COLOR, float4 _uv : TEXCOORD0, float2 _rad_ang : TEXCOORD1, out float4 _return_value : COLOR0, out float4 _mv_tex_coords : COLOR1)\n");
}
else
{
@ -346,9 +346,11 @@ void MilkdropShader::PreprocessPresetShader(std::string& program)
#ifdef MILKDROP_PRESET_DEBUG
std::cerr << "[MilkdropShader] First '{' found at: " << int(found) << std::endl;
#endif
const char* progMain =
"{\n"
"float3 ret = 0;\n";
std::string progMain = "{\nfloat3 ret = 0;\n";
if (m_type == ShaderType::WarpShader)
{
progMain.append("_mv_tex_coords.xy = _uv.xy;\n");
}
program.replace(int(found), 1, progMain);
}
else

View File

@ -1,23 +1,36 @@
#include "MotionVectors.hpp"
#include <glm/gtc/type_ptr.hpp>
#include <Renderer/StaticGlShaders.hpp>
#include <Renderer/TextureManager.hpp>
MotionVectors::MotionVectors(PresetState& presetState)
: RenderItem()
, m_presetState(presetState)
{
auto staticShaders = StaticGlShaders::Get();
m_motionVectorShader.CompileProgram(staticShaders->GetPresetMotionVectorsVertexShader(),
staticShaders->GetV2fC4fFragmentShader());
RenderItem::Init();
}
void MotionVectors::InitVertexAttrib()
{
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, nullptr);
glDisableVertexAttribArray(1);
glEnableVertexAttribArray(2);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(MotionVectorVertex), reinterpret_cast<void*>(offsetof(MotionVectorVertex, x)));
glVertexAttribIPointer(2, 1, GL_INT, sizeof(MotionVectorVertex), reinterpret_cast<void*>(offsetof(MotionVectorVertex, index)));
}
void MotionVectors::Draw(const PerFrameContext& presetPerFrameContext)
void MotionVectors::Draw(const PerFrameContext& presetPerFrameContext, std::shared_ptr<class Texture> motionTexture)
{
// Don't draw if invisible.
if (*presetPerFrameContext.mv_a < 0.0001f)
{
return;
}
int countX = static_cast<int>(*presetPerFrameContext.mv_x);
int countY = static_cast<int>(*presetPerFrameContext.mv_y);
@ -40,10 +53,8 @@ void MotionVectors::Draw(const PerFrameContext& presetPerFrameContext)
divertY = 0.0f;
}
auto divertX2 = static_cast<float>(*presetPerFrameContext.mv_dx);
auto divertY2 = static_cast<float>(*presetPerFrameContext.mv_dy);
auto lengthMultiplier = static_cast<float>(*presetPerFrameContext.mv_l);
auto const divertX2 = static_cast<float>(*presetPerFrameContext.mv_dx);
auto const divertY2 = static_cast<float>(*presetPerFrameContext.mv_dy);
// Clamp X/Y diversions to 0..1
if (divertX < 0.0f)
@ -63,16 +74,25 @@ void MotionVectors::Draw(const PerFrameContext& presetPerFrameContext)
divertY = 1.0f;
}
float const inverseWidth = 1.0f / static_cast<float>(m_presetState.renderContext.viewportSizeX);
float const minimalLength = 1.0f * inverseWidth;
// Tweaked this a bit to ensure lines are always at least a bit more than 1px long.
// Line smoothing makes some of them disappear otherwise.
float const inverseWidth = 1.25f / static_cast<float>(m_presetState.renderContext.viewportSizeX);
float const inverseHeight = 1.25f / static_cast<float>(m_presetState.renderContext.viewportSizeY);
float const minimumLength = sqrtf(inverseWidth * inverseWidth + inverseHeight * inverseHeight);
std::vector<Point> lineVertices(static_cast<std::size_t>(countX + 1) * 2); // countX + 1 lines for each grid row, 2 vertices each.
std::vector<MotionVectorVertex> lineVertices(static_cast<std::size_t>(countX + 1) * 2); // countX + 1 lines for each grid row, 2 vertices each.
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
m_presetState.untexturedShader.Bind();
m_presetState.untexturedShader.SetUniformMat4x4("vertex_transformation", m_presetState.orthogonalProjection);
m_motionVectorShader.Bind();
m_motionVectorShader.SetUniformMat4x4("vertex_transformation", m_presetState.orthogonalProjection);
m_motionVectorShader.SetUniformFloat("length_multiplier", static_cast<float>(*presetPerFrameContext.mv_l));
m_motionVectorShader.SetUniformFloat("minimum_length", minimumLength);
m_motionVectorShader.SetUniformInt("warp_coordinates", 0);
motionTexture->Bind(0, m_sampler);
glVertexAttrib4f(1,
static_cast<float>(*presetPerFrameContext.mv_r),
@ -83,6 +103,10 @@ void MotionVectors::Draw(const PerFrameContext& presetPerFrameContext)
glBindVertexArray(m_vaoID);
glBindBuffer(GL_ARRAY_BUFFER, m_vboID);
glLineWidth(1);
#if USE_GLES == 0
glEnable(GL_LINE_SMOOTH);
#endif
for (int y = 0; y < countY; y++)
{
@ -97,64 +121,31 @@ void MotionVectors::Draw(const PerFrameContext& presetPerFrameContext)
if (posX > 0.0001f && posX < 0.9999f)
{
float posX2{};
float posY2{};
lineVertices[vertex].x = posX;
lineVertices[vertex].y = posY;
lineVertices[vertex].index = vertex;
// Uses the warp mesh texture transformation to get the motion direction of this point.
ReversePropagatePoint(posX, posY, posX2, posY2);
// Enforce minimum trail length
{
float distX = posX2 - posX;
float distY = posY2 - posY;
distX *= lengthMultiplier;
distY *= lengthMultiplier;
float length = sqrtf(distX * distX + distY * distY);
if (length > minimalLength)
{
}
else if (length > 0.00000001f)
{
length = minimalLength / length;
distX *= length;
distY *= length;
}
else
{
distX = minimalLength;
distY = minimalLength;
}
posX2 = posX + distX;
posY2 = posY + distY;
}
// Assign line vertices
lineVertices.at(vertex).x = posX * 2.0f - 1.0f;
lineVertices.at(vertex).y = posY * 2.0f - 1.0f;
lineVertices.at(vertex + 1).x = posX2 * 2.0f - 1.0f;
lineVertices.at(vertex + 1).y = posY2 * 2.0f - 1.0f;
lineVertices[vertex + 1] = lineVertices[vertex];
lineVertices[vertex + 1].index++;
vertex += 2;
}
}
// Draw a row of lines.
glBufferData(GL_ARRAY_BUFFER, sizeof(Point) * lineVertices.size(), lineVertices.data(), GL_DYNAMIC_DRAW);
glDrawArrays(GL_LINES, 0, static_cast<GLsizei>(lineVertices.size()));
glBufferData(GL_ARRAY_BUFFER, sizeof(MotionVectorVertex) * vertex, lineVertices.data(), GL_DYNAMIC_DRAW);
glDrawArrays(GL_LINES, 0, static_cast<GLsizei>(vertex));
}
}
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
#if USE_GLES == 0
glDisable(GL_LINE_SMOOTH);
#endif
Shader::Unbind();
glDisable(GL_BLEND);
}
void MotionVectors::ReversePropagatePoint(float posX1, float posY1, float& posX2, float& posY2)
{
}

View File

@ -26,11 +26,20 @@ public:
/**
* Redners the motion vectors.
* @param presetPerFrameContext The per-frame context variables.
* @param motionTexture The u/v "motion" texture written by the warp shader.
*/
void Draw(const PerFrameContext& presetPerFrameContext);
void Draw(const PerFrameContext& presetPerFrameContext, std::shared_ptr<class Texture> motionTexture);
private:
void ReversePropagatePoint(float posX1, float posY1, float& posX2, float&posY2);
struct MotionVectorVertex
{
float x{};
float y{};
int32_t index{};
};
PresetState& m_presetState; //!< The global preset state.
Shader m_motionVectorShader; //!< The motion vector shader, calculates the trace positions in the GPU.
std::shared_ptr<Sampler> m_sampler{std::make_shared<Sampler>(GL_CLAMP_TO_EDGE, GL_LINEAR)}; //!< The texture sampler.
};

View File

@ -53,7 +53,34 @@ public:
const PerFrameContext& perFrameContext,
PerPixelContext& perPixelContext);
private:
/**
* Warp mesh vertex with all required attributes.
*/
struct MeshVertex {
float x{};
float y{};
float radius{};
float angle{};
float zoom{};
float zoomExp{};
float rot{};
float warp{};
float centerX{};
float centerY{};
float distanceX{};
float distanceY{};
float stretchX{};
float stretchY{};
};
using VertexList = std::vector<MeshVertex>;
/**
* @brief Initializes the vertex array and fills in static data if needed.
*
@ -81,40 +108,16 @@ private:
*/
void WarpedBlit(const PresetState& presetState, const PerFrameContext& perFrameContext);
/**
* Warp mesh vertex with all required attributes.
*/
struct MeshVertex {
float x{};
float y{};
float radius{};
float angle{};
float zoom{};
float zoomExp{};
float rot{};
float warp{};
float centerX{};
float centerY{};
float distanceX{};
float distanceY{};
float stretchX{};
float stretchY{};
};
int m_gridSizeX{}; //!< Warp mesh X resolution.
int m_gridSizeY{}; //!< Warp mesh Y resolution.
int m_viewportWidth{}; //!< Last known viewport width.
int m_viewportHeight{}; //!< Last known viewport height.
std::vector<MeshVertex> m_vertices; //!< The calculated mesh vertices.
VertexList m_vertices; //!< The calculated mesh vertices.
std::vector<int> m_listIndices; //!< List of vertex indices to render.
std::vector<MeshVertex> m_drawVertices; //!< Temp data buffer for the vertices to be drawn.
VertexList m_drawVertices; //!< Temp data buffer for the vertices to be drawn.
Shader m_perPixelMeshShader; //!< Special shader which calculates the per-pixel UV coordinates.
std::unique_ptr<MilkdropShader> m_warpShader; //!< The warp shader. Either preset-defined or a default shader.

View File

@ -71,12 +71,12 @@ void Framebuffer::Unbind()
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
}
void Framebuffer::SetSize(int width, int height)
bool Framebuffer::SetSize(int width, int height)
{
if (width == 0 || height == 0 ||
(width == m_width && height == m_height))
{
return;
return false;
}
m_width = width;
@ -92,16 +92,23 @@ void Framebuffer::SetSize(int width, int height)
}
}
glBindFramebuffer(GL_FRAMEBUFFER, 0);
return true;
}
void Framebuffer::CreateColorAttachment(int framebufferIndex, int attachmentIndex)
{
CreateColorAttachment(framebufferIndex, attachmentIndex, GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE);
}
void Framebuffer::CreateColorAttachment(int framebufferIndex, int attachmentIndex, GLint internalFormat, GLenum format, GLenum type)
{
if (framebufferIndex < 0 || framebufferIndex >= static_cast<int>(m_framebufferIds.size()))
{
return;
}
TextureAttachment textureAttachment{TextureAttachment::AttachmentType::Color, m_width, m_height};
TextureAttachment textureAttachment{internalFormat, format, type, m_width, m_height };
const auto texture = textureAttachment.Texture();
m_attachments.at(framebufferIndex).insert({GL_COLOR_ATTACHMENT0 + attachmentIndex, std::move(textureAttachment)});
@ -190,6 +197,13 @@ void Framebuffer::CreateDepthStencilAttachment(int framebufferIndex)
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
void Framebuffer::MaskDrawBuffer(int bufferIndex, bool masked)
{
// Invert the flag, as "true" means the color channel *will* be written.
auto glMasked = static_cast<GLboolean>(!masked);
glColorMaski(bufferIndex, glMasked, glMasked, glMasked, glMasked);
}
void Framebuffer::UpdateDrawBuffers(int framebufferIndex)
{
if (framebufferIndex < 0 || framebufferIndex >= static_cast<int>(m_framebufferIds.size()))

View File

@ -89,8 +89,9 @@ public:
* won't be changed.
* @param width The width of the framebuffer.
* @param height The height of the framebuffer.
* @return true if the framebuffer was resized, false if it's contents remain unchanged.
*/
void SetSize(int width, int height);
bool SetSize(int width, int height);
/**
* @brief Adds a new color attachment to the framebuffer.
@ -100,6 +101,17 @@ public:
*/
void CreateColorAttachment(int framebufferIndex, int attachmentIndex);
/**
* @brief Adds a new color attachment to the framebuffer with the specified format.
* @param framebufferIndex The framebuffer index.
* @param index The index of the attachment, at least indices 0-7 are guaranteed to be available.
* @param internalFormat OpenGL internal format, e.g. GL_RGBA8
* @param format OpenGL color format, e.g. GL_RGBA
* @param type OpenGL component storage type, e.g. GL_UNSIGNED _BYTE
*/
void CreateColorAttachment(int framebufferIndex, int attachmentIndex,
GLint internalFormat, GLenum format, GLenum type);
/**
* @brief Returns the texture ID of the given framebuffer and color attachment.
* @param framebufferIndex The framebuffer index.
@ -126,6 +138,14 @@ public:
*/
void CreateDepthStencilAttachment(int framebufferIndex);
/**
* @brief Sets the masked flag for a specific draw buffer.
* This can be used to enable or disable rendering to specific color attachments.
* @param bufferIndex The index of the buffer to set the mask flag on.
* @param masked true if the attachment should be masked, false if not.
*/
void MaskDrawBuffer(int bufferIndex, bool masked);
private:
/**
* @brief Updates the draw buffer list for the fragment shader outputs of the given framebuffer.

View File

@ -8,6 +8,9 @@
namespace {
// Variants of shaders for GLSL1.2
const std::string kPresetMotionVectorsVertexShaderGlsl120 = R"(
)";
const std::string kPresetWarpVertexShaderGlsl120 = R"(
#define pos vertex_position.xy
#define radius vertex_position.z
@ -444,6 +447,63 @@ void main(){
)";
// Variants of shaders for GLSL3.3
const std::string kPresetMotionVectorsVertexShaderGlsl330 = R"(
layout(location = 0) in vec2 vertex_position;
layout(location = 1) in vec4 vertex_color;
layout(location = 2) in int vertex_index;
uniform mat4 vertex_transformation;
uniform float length_multiplier;
uniform float minimum_length;
uniform sampler2D warp_coordinates;
out vec4 fragment_color;
void main() {
// Input positions are given in texture coordinates (0...1), not the usual
// screen coordinates.
vec2 pos = vertex_position;
if (vertex_index % 2 == 1)
{
// Reverse propagation using the u/v texture written in the previous frame.
// Milkdrop's original code did a simple bilinear interpolation, but here it was already
// done by the fragment shader during the warp mesh drawing. We just need to look up the
// motion vector coordinate.
vec2 oldUV = texture(warp_coordinates, pos.xy).xy;
// Enforce minimum trail length
vec2 dist = oldUV - pos;
dist *= length_multiplier;
float len = length(dist);
if (len > minimum_length)
{}
else if (len > 0.00000001f)
{
len = minimum_length / len;
dist *= len;
}
else
{
dist = vec2(minimum_length);
}
pos += dist;
}
// Transform positions from 0...1 to -1...1 in each direction.
pos = pos * 2.0 - 1.0;
// Flip final Y, as we draw this top-down, which is bottom-up in OpenGL.
pos.y = -pos.y;
// Now we've got the usual coordinates, apply our orthogonal transformation.
gl_Position = vertex_transformation * vec4(pos, 0.0, 1.0);
fragment_color = vertex_color;
}
)";
const std::string kPresetWarpVertexShaderGlsl330 = R"(
#define pos vertex_position.xy
#define radius vertex_position.z
@ -529,10 +589,14 @@ in vec2 frag_TEXCOORD1;
uniform sampler2D texture_sampler;
out vec4 color;
layout(location = 0) out vec4 color;
layout(location = 1) out vec2 texCoords;
void main() {
// Main image
color = frag_COLOR * texture(texture_sampler, frag_TEXCOORD0.xy);
// Motion vector grid u/v coords for the next frame
texCoords = frag_TEXCOORD0.xy;
}
)";
@ -575,6 +639,7 @@ const std::string kV2fC4fFragmentShaderGlsl330 = R"(
precision mediump float;
in vec4 fragment_color;
out vec4 color;
void main(){
@ -1024,6 +1089,7 @@ std::string StaticGlShaders::AddVersionHeader(std::string shader_text) {
return k##name##Glsl330; \
}
DECLARE_SHADER_ACCESSOR(PresetMotionVectorsVertexShader);
DECLARE_SHADER_ACCESSOR(PresetWarpVertexShader);
DECLARE_SHADER_ACCESSOR(PresetWarpFragmentShader);
DECLARE_SHADER_ACCESSOR(PresetCompVertexShader);

View File

@ -26,6 +26,7 @@ class StaticGlShaders {
}
// Returns the named static GL shader resource.
std::string GetPresetMotionVectorsVertexShader();
std::string GetPresetWarpVertexShader();
std::string GetPresetWarpFragmentShader();
std::string GetPresetCompVertexShader();

View File

@ -3,23 +3,36 @@
#include <utility>
Texture::Texture(std::string name, const int width, const int height, const bool isUserTexture)
: m_type(GL_TEXTURE_2D)
: m_target(GL_TEXTURE_2D)
, m_name(std::move(name))
, m_width(width)
, m_height(height)
, m_isUserTexture(isUserTexture)
, m_internalFormat(GL_RGB)
, m_format(GL_RGB)
, m_type(GL_UNSIGNED_BYTE)
{
glGenTextures(1, &m_textureId);
glBindTexture(GL_TEXTURE_2D, m_textureId);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, nullptr);
glBindTexture(GL_TEXTURE_2D, 0);
CreateEmptyTexture();
}
Texture::Texture(std::string name, const GLuint texID, const GLenum type,
const int width, const int height,
const bool isUserTexture)
: m_textureId(texID)
Texture::Texture(std::string name, int width, int height,
GLint internalFormat, GLenum format, GLenum type, bool isUserTexture)
: m_target(GL_TEXTURE_2D)
, m_name(std::move(name))
, m_width(width)
, m_height(height)
, m_isUserTexture(isUserTexture)
, m_internalFormat(internalFormat)
, m_format(format)
, m_type(type)
{
CreateEmptyTexture();
}
Texture::Texture(std::string name, const GLuint texID, const GLenum target,
const int width, const int height, const bool isUserTexture)
: m_textureId(texID)
, m_target(target)
, m_name(std::move(name))
, m_width(width)
, m_height(height)
@ -39,7 +52,7 @@ Texture::~Texture()
void Texture::Bind(GLint slot, const Sampler::Ptr& sampler) const
{
glActiveTexture(GL_TEXTURE0 + slot);
glBindTexture(m_type, m_textureId);
glBindTexture(m_target, m_textureId);
if (sampler)
{
@ -50,7 +63,7 @@ void Texture::Bind(GLint slot, const Sampler::Ptr& sampler) const
void Texture::Unbind(GLint slot) const
{
glActiveTexture(GL_TEXTURE0 + slot);
glBindTexture(m_type, 0);
glBindTexture(m_target, 0);
}
auto Texture::TextureID() const -> GLuint
@ -65,7 +78,7 @@ auto Texture::Name() const -> const std::string&
auto Texture::Type() const -> GLenum
{
return m_type;
return m_target;
}
auto Texture::Width() const -> int
@ -82,3 +95,11 @@ auto Texture::IsUserTexture() const -> bool
{
return m_isUserTexture;
}
void Texture::CreateEmptyTexture()
{
glGenTextures(1, &m_textureId);
glBindTexture(m_target, m_textureId);
glTexImage2D(m_target, 0, m_internalFormat, m_width, m_height, 0, m_format, m_type, nullptr);
glBindTexture(m_target, 0);
}

View File

@ -36,16 +36,26 @@ public:
explicit Texture(std::string name, int width, int height, bool isUserTexture);
/**
* @brief Constructor. Creates a new texture instance from an existing OpenGL texture.
* The class will take ownership of the texture, e.g. freeing it when destroyed!
* @brief Constructor. Allocates a new, empty texture with the given size and format.
* @param name Optional name of the texture for referencing in Milkdrop shaders.
* @param texID The OpenGL texture name (ID).
* @param type The texture type, e.g. GL_TEXTURE_2D.
* @param width Width in pixels.
* @param height Height in pixels.
* @param isUserTexture true if the texture is an externally-loaded image, false if it's an internal texture.
*/
explicit Texture(std::string name, GLuint texID, GLenum type,
explicit Texture(std::string name, int width, int height,
GLint internalFormat, GLenum format, GLenum type, bool isUserTexture);
/**
* @brief Constructor. Creates a new texture instance from an existing OpenGL texture.
* The class will take ownership of the texture, e.g. freeing it when destroyed!
* @param name Optional name of the texture for referencing in Milkdrop shaders.
* @param texID The OpenGL texture name (ID).
* @param target The texture target type, e.g. GL_TEXTURE_2D.
* @param width Width in pixels.
* @param height Height in pixels.
* @param isUserTexture true if the texture is an externally-loaded image, false if it's an internal texture.
*/
explicit Texture(std::string name, GLuint texID, GLenum target,
int width, int height,
bool isUserTexture);
@ -105,11 +115,17 @@ public:
auto IsUserTexture() const -> bool;
private:
GLuint m_textureId{0}; //!< The OpenGL texture name/ID.
GLenum m_type{GL_NONE}; //!< The OpenGL texture type, e.g. GL_TEXTURE_2D.
void CreateEmptyTexture();
GLuint m_textureId{0}; //!< The OpenGL texture name/ID.
GLenum m_target{GL_NONE}; //!< The OpenGL texture target, e.g. GL_TEXTURE_2D.
std::string m_name; //!< The texture name for identifying it in shaders.
int m_width{0}; //!< Texture width in pixels.
int m_height{0}; //!< Texture height in pixels.
bool m_isUserTexture{false}; //!< true if it's a user texture, false if an internal one.
GLint m_internalFormat{}; //!< OpenGL internal format, e.g. GL_RGBA8
GLenum m_format{}; //!< OpenGL color format, e.g. GL_RGBA
GLenum m_type{}; //!< OpenGL component storage type, e.g. GL_UNSIGNED _BYTE
};

View File

@ -9,6 +9,18 @@ TextureAttachment::TextureAttachment(AttachmentType attachment, int width, int h
}
}
TextureAttachment::TextureAttachment(GLint internalFormat, GLenum format, GLenum type, int width, int height)
: m_attachmentType(AttachmentType::Color)
, m_internalFormat(internalFormat)
, m_format(format)
, m_type(type)
{
if (width > 0 && height > 0)
{
ReplaceTexture(width, height);
}
}
auto TextureAttachment::Type() const -> TextureAttachment::AttachmentType
{
return m_attachmentType;
@ -40,9 +52,18 @@ void TextureAttachment::ReplaceTexture(int width, int height)
switch(m_attachmentType)
{
case AttachmentType::Color:
internalFormat = GL_RGBA;
textureFormat = GL_RGBA;
pixelFormat = GL_UNSIGNED_BYTE;
if (m_internalFormat == 0)
{
internalFormat = GL_RGBA;
textureFormat = GL_RGBA;
pixelFormat = GL_UNSIGNED_BYTE;
}
else
{
internalFormat = m_internalFormat;
textureFormat = m_format;
pixelFormat = m_type;
}
break;
case AttachmentType::Depth:
internalFormat = GL_DEPTH_COMPONENT16;
@ -79,4 +100,4 @@ void TextureAttachment::ReplaceTexture(int width, int height)
glBindTexture(GL_TEXTURE_2D, 0);
m_texture = std::make_shared<class Texture>("", textureId, GL_TEXTURE_2D, width, height, false);
}
}

View File

@ -39,6 +39,17 @@ public:
*/
explicit TextureAttachment(AttachmentType attachmentType, int width, int height);
/**
* @brief Creates a new 2D color texture attachment with the given format and size.
* Must have the same size as the framebuffer.
* @param internalFormat OpenGL internal format, e.g. GL_RGBA8
* @param format OpenGL color format, e.g. GL_RGBA
* @param type OpenGL component storage type, e.g. GL_UNSIGNED _BYTE
* @param width The width of the texture in pixels.
* @param height The height of the texture in pixels.
*/
explicit TextureAttachment(GLint internalFormat, GLenum format, GLenum type, int width, int height);
TextureAttachment(TextureAttachment&& other) = default;
auto operator=(TextureAttachment&& other) -> TextureAttachment& = default;
@ -74,4 +85,8 @@ private:
AttachmentType m_attachmentType{AttachmentType::Color}; //!< Attachment type of this texture.
std::shared_ptr<class Texture> m_texture{std::make_shared<class Texture>()}; //!< The texture.
GLint m_internalFormat{}; //!< OpenGL internal format, e.g. GL_RGBA8
GLenum m_format{}; //!< OpenGL color format, e.g. GL_RGBA
GLenum m_type{}; //!< OpenGL component storage type, e.g. GL_UNSIGNED _BYTE
};