mirror of
https://github.com/thorvg/thorvg.git
synced 2025-06-08 05:33:36 +00:00
gl_engine: Introduce gaussian blur effect
issue: https://github.com/thorvg/thorvg/issues/3054
This commit is contained in:
parent
d3afebfdec
commit
595769257e
7 changed files with 268 additions and 12 deletions
|
@ -200,4 +200,12 @@ struct GlCompositor : RenderCompositor
|
||||||
GlCompositor(const RenderRegion& box) : bbox(box) {}
|
GlCompositor(const RenderRegion& box) : bbox(box) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#define GL_GAUSSIAN_MAX_LEVEL 3
|
||||||
|
struct GlGaussianBlur {
|
||||||
|
int level{};
|
||||||
|
float sigma{};
|
||||||
|
float scale{};
|
||||||
|
float extend{};
|
||||||
|
};
|
||||||
|
|
||||||
#endif /* _TVG_GL_COMMON_H_ */
|
#endif /* _TVG_GL_COMMON_H_ */
|
||||||
|
|
|
@ -173,12 +173,12 @@ GlComposeTask::~GlComposeTask()
|
||||||
void GlComposeTask::run()
|
void GlComposeTask::run()
|
||||||
{
|
{
|
||||||
GL_CHECK(glBindFramebuffer(GL_FRAMEBUFFER, getSelfFbo()));
|
GL_CHECK(glBindFramebuffer(GL_FRAMEBUFFER, getSelfFbo()));
|
||||||
GL_CHECK(glViewport(0, 0, mRenderWidth, mRenderHeight));
|
|
||||||
|
|
||||||
GL_CHECK(glScissor(0, 0, mRenderWidth, mRenderHeight));
|
|
||||||
|
|
||||||
// clear this fbo
|
// clear this fbo
|
||||||
if (mClearBuffer) {
|
if (mClearBuffer) {
|
||||||
|
// we must clear all area of fbo
|
||||||
|
GL_CHECK(glViewport(0, 0, mFbo->getWidth(), mFbo->getHeight()));
|
||||||
|
GL_CHECK(glScissor(0, 0, mFbo->getWidth(), mFbo->getHeight()));
|
||||||
GL_CHECK(glClearColor(0, 0, 0, 0));
|
GL_CHECK(glClearColor(0, 0, 0, 0));
|
||||||
GL_CHECK(glClearStencil(0));
|
GL_CHECK(glClearStencil(0));
|
||||||
GL_CHECK(glClearDepthf(0.0));
|
GL_CHECK(glClearDepthf(0.0));
|
||||||
|
@ -188,6 +188,9 @@ void GlComposeTask::run()
|
||||||
GL_CHECK(glDepthMask(0));
|
GL_CHECK(glDepthMask(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
GL_CHECK(glViewport(0, 0, mRenderWidth, mRenderHeight));
|
||||||
|
GL_CHECK(glScissor(0, 0, mRenderWidth, mRenderHeight));
|
||||||
|
|
||||||
ARRAY_FOREACH(p, mTasks) {
|
ARRAY_FOREACH(p, mTasks) {
|
||||||
(*p)->run();
|
(*p)->run();
|
||||||
}
|
}
|
||||||
|
@ -379,3 +382,55 @@ void GlComplexBlendTask::normalizeDrawDepth(int32_t maxDepth)
|
||||||
mStencilTask->normalizeDrawDepth(maxDepth);
|
mStencilTask->normalizeDrawDepth(maxDepth);
|
||||||
GlRenderTask::normalizeDrawDepth(maxDepth);
|
GlRenderTask::normalizeDrawDepth(maxDepth);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GlGaussianBlurTask::run()
|
||||||
|
{
|
||||||
|
const auto vp = getViewport();
|
||||||
|
const auto width = mDstFbo->getWidth();
|
||||||
|
const auto height = mDstFbo->getHeight();
|
||||||
|
|
||||||
|
// get targets handles
|
||||||
|
GLuint dstCopyTexId0 = mDstCopyFbo0->getColorTexture();
|
||||||
|
GLuint dstCopyTexId1 = mDstCopyFbo1->getColorTexture();
|
||||||
|
// get programs properties
|
||||||
|
GlProgram* programHorz = horzTask->getProgram();
|
||||||
|
GlProgram* programVert = vertTask->getProgram();
|
||||||
|
GLint horzSrcTextureLoc = programHorz->getUniformLocation("uSrcTexture");
|
||||||
|
GLint vertSrcTextureLoc = programVert->getUniformLocation("uSrcTexture");
|
||||||
|
|
||||||
|
GL_CHECK(glViewport(0, 0, width, height));
|
||||||
|
GL_CHECK(glScissor(0, 0, width, height));
|
||||||
|
// we need to make a full copy of dst to intermediate buffers to be sure that they don’t contain prev data.
|
||||||
|
GL_CHECK(glBindFramebuffer(GL_READ_FRAMEBUFFER, mDstFbo->getFboId()));
|
||||||
|
GL_CHECK(glBindFramebuffer(GL_DRAW_FRAMEBUFFER, mDstCopyFbo0->getResolveFboId()));
|
||||||
|
GL_CHECK(glBlitFramebuffer(0, 0, width, height, 0, 0, width, height, GL_COLOR_BUFFER_BIT, GL_NEAREST));
|
||||||
|
GL_CHECK(glBindFramebuffer(GL_FRAMEBUFFER, mDstFbo->getFboId()));
|
||||||
|
|
||||||
|
GL_CHECK(glDisable(GL_BLEND));
|
||||||
|
if (effect->direction == 0) {
|
||||||
|
GL_CHECK(glBindFramebuffer(GL_READ_FRAMEBUFFER, mDstFbo->getFboId()));
|
||||||
|
GL_CHECK(glBindFramebuffer(GL_DRAW_FRAMEBUFFER, mDstCopyFbo1->getResolveFboId()));
|
||||||
|
GL_CHECK(glBlitFramebuffer(0, 0, width, height, 0, 0, width, height, GL_COLOR_BUFFER_BIT, GL_NEAREST));
|
||||||
|
// horizontal blur
|
||||||
|
GL_CHECK(glBindFramebuffer(GL_FRAMEBUFFER, mDstCopyFbo1->getResolveFboId()));
|
||||||
|
horzTask->setViewport(vp);
|
||||||
|
horzTask->addBindResource({ 0, dstCopyTexId0, horzSrcTextureLoc });
|
||||||
|
horzTask->run();
|
||||||
|
// vertical blur
|
||||||
|
GL_CHECK(glBindFramebuffer(GL_FRAMEBUFFER, mDstFbo->getFboId()));
|
||||||
|
vertTask->setViewport(vp);
|
||||||
|
vertTask->addBindResource({ 0, dstCopyTexId1, vertSrcTextureLoc });
|
||||||
|
vertTask->run();
|
||||||
|
} // horizontal
|
||||||
|
else if (effect->direction == 1) {
|
||||||
|
horzTask->setViewport(vp);
|
||||||
|
horzTask->addBindResource({ 0, dstCopyTexId0, horzSrcTextureLoc });
|
||||||
|
horzTask->run();
|
||||||
|
} // vertical
|
||||||
|
else if (effect->direction == 2) {
|
||||||
|
vertTask->setViewport(vp);
|
||||||
|
vertTask->addBindResource({ 0, dstCopyTexId0, vertSrcTextureLoc });
|
||||||
|
vertTask->run();
|
||||||
|
}
|
||||||
|
GL_CHECK(glEnable(GL_BLEND));
|
||||||
|
}
|
||||||
|
|
|
@ -219,4 +219,22 @@ private:
|
||||||
GlComposeTask* mComposeTask;
|
GlComposeTask* mComposeTask;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class GlGaussianBlurTask: public GlRenderTask
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
GlGaussianBlurTask(GlRenderTarget* dstFbo, GlRenderTarget* dstCopyFbo0, GlRenderTarget* dstCopyFbo1):
|
||||||
|
GlRenderTask(nullptr), mDstFbo(dstFbo), mDstCopyFbo0(dstCopyFbo0), mDstCopyFbo1(dstCopyFbo1) {};
|
||||||
|
~GlGaussianBlurTask(){ delete horzTask; delete vertTask; };
|
||||||
|
|
||||||
|
void run() override;
|
||||||
|
|
||||||
|
GlRenderTask* horzTask;
|
||||||
|
GlRenderTask* vertTask;
|
||||||
|
RenderEffectGaussianBlur* effect;
|
||||||
|
private:
|
||||||
|
GlRenderTarget* mDstFbo;
|
||||||
|
GlRenderTarget* mDstCopyFbo0;
|
||||||
|
GlRenderTarget* mDstCopyFbo1;
|
||||||
|
};
|
||||||
|
|
||||||
#endif /* _TVG_GL_RENDER_TASK_H_ */
|
#endif /* _TVG_GL_RENDER_TASK_H_ */
|
||||||
|
|
|
@ -140,6 +140,10 @@ void GlRenderer::initShaders()
|
||||||
mPrograms.push(new GlProgram(MASK_VERT_SHADER, SOFT_LIGHT_BLEND_FRAG));
|
mPrograms.push(new GlProgram(MASK_VERT_SHADER, SOFT_LIGHT_BLEND_FRAG));
|
||||||
mPrograms.push(new GlProgram(MASK_VERT_SHADER, DIFFERENCE_BLEND_FRAG));
|
mPrograms.push(new GlProgram(MASK_VERT_SHADER, DIFFERENCE_BLEND_FRAG));
|
||||||
mPrograms.push(new GlProgram(MASK_VERT_SHADER, EXCLUSION_BLEND_FRAG));
|
mPrograms.push(new GlProgram(MASK_VERT_SHADER, EXCLUSION_BLEND_FRAG));
|
||||||
|
|
||||||
|
// effects
|
||||||
|
mPrograms.push(new GlProgram(EFFECT_VERTEX, GAUSSIAN_VERTICAL));
|
||||||
|
mPrograms.push(new GlProgram(EFFECT_VERTEX, GAUSSIAN_HORIZONTAL));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -944,29 +948,108 @@ bool GlRenderer::endComposite(RenderCompositor* cmp)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void GlRenderer::prepare(TVG_UNUSED RenderEffect* effect, TVG_UNUSED const Matrix& transform)
|
void GlRenderer::effectGaussianBlurUpdate(RenderEffectGaussianBlur* effect, const Matrix& transform)
|
||||||
{
|
{
|
||||||
//TODO: prepare the effect
|
GlGaussianBlur* blur = (GlGaussianBlur*)effect->rd;
|
||||||
|
if (!blur) blur = tvg::malloc<GlGaussianBlur*>(sizeof(GlGaussianBlur));
|
||||||
|
blur->sigma = effect->sigma;
|
||||||
|
blur->scale = std::sqrt(transform.e11 * transform.e11 + transform.e12 * transform.e12);
|
||||||
|
blur->extend = 2 * blur->sigma * blur->scale;
|
||||||
|
blur->level = int(GL_GAUSSIAN_MAX_LEVEL * ((effect->quality - 1) * 0.01f)) + 1;
|
||||||
|
effect->rd = blur;
|
||||||
|
effect->valid = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool GlRenderer::region(TVG_UNUSED RenderEffect* effect)
|
bool GlRenderer::effectGaussianBlurRegion(RenderEffectGaussianBlur* effect)
|
||||||
{
|
{
|
||||||
//TODO: Return if the current post effect requires the region expansion
|
auto gaussianBlur = (GlGaussianBlur*)effect->rd;
|
||||||
|
if (effect->direction != 2) {
|
||||||
|
effect->extend.x = -gaussianBlur->extend;
|
||||||
|
effect->extend.w = +gaussianBlur->extend * 2;
|
||||||
|
}
|
||||||
|
if (effect->direction != 1) {
|
||||||
|
effect->extend.y = -gaussianBlur->extend;
|
||||||
|
effect->extend.h = +gaussianBlur->extend * 2;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
void GlRenderer::prepare(RenderEffect* effect, const Matrix& transform)
|
||||||
|
{
|
||||||
|
// we must be sure, that we have intermidiate FBOs
|
||||||
|
if (mBlendPool.count < 1) mBlendPool.push(new GlRenderTargetPool(surface.w, surface.h));
|
||||||
|
if (mBlendPool.count < 2) mBlendPool.push(new GlRenderTargetPool(surface.w, surface.h));
|
||||||
|
|
||||||
|
switch (effect->type) {
|
||||||
|
case SceneEffect::GaussianBlur: effectGaussianBlurUpdate(static_cast<RenderEffectGaussianBlur*>(effect), transform); break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
effect->valid = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool GlRenderer::region(RenderEffect* effect)
|
||||||
|
{
|
||||||
|
switch (effect->type) {
|
||||||
|
case SceneEffect::GaussianBlur: return effectGaussianBlurRegion(static_cast<RenderEffectGaussianBlur*>(effect));
|
||||||
|
default: return false;
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool GlRenderer::render(TVG_UNUSED RenderCompositor* cmp, TVG_UNUSED const RenderEffect* effect, TVG_UNUSED bool direct)
|
bool GlRenderer::render(TVG_UNUSED RenderCompositor* cmp, const RenderEffect* effect, bool direct)
|
||||||
{
|
{
|
||||||
TVGLOG("GL_ENGINE", "SceneEffect(%d) is not supported", (int)effect->type);
|
// get current pass and properties
|
||||||
|
auto pass = currentPass();
|
||||||
|
if (pass->isEmpty()) return false;
|
||||||
|
auto vp = pass->getViewport();
|
||||||
|
|
||||||
|
// add render geometry
|
||||||
|
const float vdata[] = {-1.0f, +1.0f, +1.0f, +1.0f, +1.0f, -1.0f, -1.0f, -1.0f};
|
||||||
|
const uint32_t idata[] = { 0, 1, 2, 0, 2, 3 };
|
||||||
|
auto voffset = mGpuBuffer.push((void*)vdata, sizeof(vdata));
|
||||||
|
auto ioffset = mGpuBuffer.pushIndex((void*)idata, sizeof(idata));
|
||||||
|
|
||||||
|
if (effect->type == SceneEffect::GaussianBlur) {
|
||||||
|
// get gaussian programs
|
||||||
|
GlProgram* programHorz = mPrograms[RT_GaussianHorz];
|
||||||
|
GlProgram* programVert = mPrograms[RT_GaussianVert];
|
||||||
|
// get current and intermidiate framebuffers
|
||||||
|
auto dstFbo = pass->getFbo();
|
||||||
|
auto dstCopyFbo0 = mBlendPool[0]->getRenderTarget(vp);
|
||||||
|
auto dstCopyFbo1 = mBlendPool[1]->getRenderTarget(vp);
|
||||||
|
// add uniform data
|
||||||
|
GlGaussianBlur* blur = (GlGaussianBlur*)(effect->rd);
|
||||||
|
auto blurOffset = mGpuBuffer.push(blur, sizeof(GlGaussianBlur), true);
|
||||||
|
|
||||||
|
// create gaussian blur tasks
|
||||||
|
auto gaussianTask = new GlGaussianBlurTask(dstFbo, dstCopyFbo0, dstCopyFbo1);
|
||||||
|
gaussianTask->effect = (RenderEffectGaussianBlur*)effect;
|
||||||
|
gaussianTask->setViewport({0, 0, vp.w, vp.h});
|
||||||
|
// horizontal blur task and geometry
|
||||||
|
gaussianTask->horzTask = new GlRenderTask(programHorz);
|
||||||
|
gaussianTask->horzTask->addBindResource(GlBindingResource{0, programHorz->getUniformBlockIndex("Gaussian"), mGpuBuffer.getBufferId(), blurOffset, sizeof(GlGaussianBlur)});
|
||||||
|
gaussianTask->horzTask->addVertexLayout(GlVertexLayout{0, 2, 2 * sizeof(float), voffset});
|
||||||
|
gaussianTask->horzTask->setDrawRange(ioffset, 6);
|
||||||
|
// vertical blur task and geometry
|
||||||
|
gaussianTask->vertTask = new GlRenderTask(programVert);
|
||||||
|
gaussianTask->vertTask->addBindResource(GlBindingResource{0, programVert->getUniformBlockIndex("Gaussian"), mGpuBuffer.getBufferId(), blurOffset, sizeof(GlGaussianBlur)});
|
||||||
|
gaussianTask->vertTask->addVertexLayout(GlVertexLayout{0, 2, 2 * sizeof(float), voffset});
|
||||||
|
gaussianTask->vertTask->setDrawRange(ioffset, 6);
|
||||||
|
// add task to render pipeline
|
||||||
|
pass->addRenderTask(gaussianTask);
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void GlRenderer::dispose(TVG_UNUSED RenderEffect* effect)
|
void GlRenderer::dispose(RenderEffect* effect)
|
||||||
{
|
{
|
||||||
//TODO: dispose the effect
|
tvg::free(effect->rd);
|
||||||
|
effect->rd = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -60,6 +60,9 @@ public:
|
||||||
RT_DifferenceBlend,
|
RT_DifferenceBlend,
|
||||||
RT_ExclusionBlend,
|
RT_ExclusionBlend,
|
||||||
|
|
||||||
|
RT_GaussianVert,
|
||||||
|
RT_GaussianHorz,
|
||||||
|
|
||||||
RT_None,
|
RT_None,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -90,7 +93,7 @@ public:
|
||||||
void prepare(RenderEffect* effect, const Matrix& transform) override;
|
void prepare(RenderEffect* effect, const Matrix& transform) override;
|
||||||
bool region(RenderEffect* effect) override;
|
bool region(RenderEffect* effect) override;
|
||||||
bool render(RenderCompositor* cmp, const RenderEffect* effect, bool direct) override;
|
bool render(RenderCompositor* cmp, const RenderEffect* effect, bool direct) override;
|
||||||
void dispose(TVG_UNUSED RenderEffect* effect) override;
|
void dispose(RenderEffect* effect) override;
|
||||||
|
|
||||||
static GlRenderer* gen();
|
static GlRenderer* gen();
|
||||||
static bool init(TVG_UNUSED uint32_t threads);
|
static bool init(TVG_UNUSED uint32_t threads);
|
||||||
|
@ -116,6 +119,9 @@ private:
|
||||||
void prepareCmpTask(GlRenderTask* task, const RenderRegion& vp, uint32_t cmpWidth, uint32_t cmpHeight);
|
void prepareCmpTask(GlRenderTask* task, const RenderRegion& vp, uint32_t cmpWidth, uint32_t cmpHeight);
|
||||||
void endRenderPass(RenderCompositor* cmp);
|
void endRenderPass(RenderCompositor* cmp);
|
||||||
|
|
||||||
|
void effectGaussianBlurUpdate(RenderEffectGaussianBlur* effect, const Matrix& transform);
|
||||||
|
bool effectGaussianBlurRegion(RenderEffectGaussianBlur* effect);
|
||||||
|
|
||||||
void flush();
|
void flush();
|
||||||
void clearDisposes();
|
void clearDisposes();
|
||||||
void currentContext();
|
void currentContext();
|
||||||
|
|
|
@ -737,3 +737,86 @@ const char* EXCLUSION_BLEND_FRAG = COMPLEX_BLEND_HEADER R"(
|
||||||
FragColor = dstColor + srcColor - (2.0 * dstColor * srcColor);
|
FragColor = dstColor + srcColor - (2.0 * dstColor * srcColor);
|
||||||
}
|
}
|
||||||
)";
|
)";
|
||||||
|
|
||||||
|
const char* EFFECT_VERTEX = R"(
|
||||||
|
layout(location = 0) in vec2 aLocation;
|
||||||
|
out vec2 vUV;
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
vUV = aLocation * 0.5 + 0.5;
|
||||||
|
gl_Position = vec4(aLocation, 0.0, 1.0);
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
|
||||||
|
const char* GAUSSIAN_VERTICAL = R"(
|
||||||
|
uniform sampler2D uSrcTexture;
|
||||||
|
layout(std140) uniform Gaussian {
|
||||||
|
int level;
|
||||||
|
float sigma;
|
||||||
|
float scale;
|
||||||
|
float extend;
|
||||||
|
} uGaussian;
|
||||||
|
|
||||||
|
in vec2 vUV;
|
||||||
|
out vec4 FragColor;
|
||||||
|
|
||||||
|
float gaussian(float x, float sigma) {
|
||||||
|
float exponent = -x * x / (2.0 * sigma * sigma);
|
||||||
|
return exp(exponent) / (sqrt(2.0 * 3.141592) * sigma);
|
||||||
|
}
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
vec2 texelSize = 1.0 / vec2(textureSize(uSrcTexture, 0));
|
||||||
|
vec4 colorSum = vec4(0.0);
|
||||||
|
float sigma = uGaussian.sigma * uGaussian.scale;
|
||||||
|
float weightSum = 0.0;
|
||||||
|
int radius = int(uGaussian.extend);
|
||||||
|
|
||||||
|
for (int y = -radius; y <= radius; ++y) {
|
||||||
|
float weight = gaussian(float(y), sigma);
|
||||||
|
vec2 offset = vec2(0.0, float(y) * texelSize.y);
|
||||||
|
colorSum += texture(uSrcTexture, vUV + offset) * weight;
|
||||||
|
weightSum += weight;
|
||||||
|
}
|
||||||
|
|
||||||
|
FragColor = colorSum / weightSum;
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
|
||||||
|
const char* GAUSSIAN_HORIZONTAL = R"(
|
||||||
|
uniform sampler2D uSrcTexture;
|
||||||
|
layout(std140) uniform Gaussian {
|
||||||
|
int level;
|
||||||
|
float sigma;
|
||||||
|
float scale;
|
||||||
|
float extend;
|
||||||
|
} uGaussian;
|
||||||
|
|
||||||
|
in vec2 vUV;
|
||||||
|
out vec4 FragColor;
|
||||||
|
|
||||||
|
float gaussian(float x, float sigma) {
|
||||||
|
float exponent = -x * x / (2.0 * sigma * sigma);
|
||||||
|
return exp(exponent) / (sqrt(2.0 * 3.141592) * sigma);
|
||||||
|
}
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
vec2 texelSize = 1.0 / vec2(textureSize(uSrcTexture, 0));
|
||||||
|
vec4 colorSum = vec4(0.0);
|
||||||
|
float sigma = uGaussian.sigma * uGaussian.scale;
|
||||||
|
float weightSum = 0.0;
|
||||||
|
int radius = int(uGaussian.extend);
|
||||||
|
|
||||||
|
for (int y = -radius; y <= radius; ++y) {
|
||||||
|
float weight = gaussian(float(y), sigma);
|
||||||
|
vec2 offset = vec2(float(y) * texelSize.x, 0.0);
|
||||||
|
colorSum += texture(uSrcTexture, vUV + offset) * weight;
|
||||||
|
weightSum += weight;
|
||||||
|
}
|
||||||
|
|
||||||
|
FragColor = colorSum / weightSum;
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
|
|
@ -54,5 +54,8 @@ extern const char* HARD_LIGHT_BLEND_FRAG;
|
||||||
extern const char* SOFT_LIGHT_BLEND_FRAG;
|
extern const char* SOFT_LIGHT_BLEND_FRAG;
|
||||||
extern const char* DIFFERENCE_BLEND_FRAG;
|
extern const char* DIFFERENCE_BLEND_FRAG;
|
||||||
extern const char* EXCLUSION_BLEND_FRAG;
|
extern const char* EXCLUSION_BLEND_FRAG;
|
||||||
|
extern const char* EFFECT_VERTEX;
|
||||||
|
extern const char* GAUSSIAN_VERTICAL;
|
||||||
|
extern const char* GAUSSIAN_HORIZONTAL;
|
||||||
|
|
||||||
#endif /* _TVG_GL_SHADERSRC_H_ */
|
#endif /* _TVG_GL_SHADERSRC_H_ */
|
||||||
|
|
Loading…
Add table
Reference in a new issue