Skip to content

Instantly share code, notes, and snippets.

@mstange
Created June 5, 2019 17:48
Show Gist options
  • Save mstange/41e89dba2030e43511ee1784ccc44f56 to your computer and use it in GitHub Desktop.
Save mstange/41e89dba2030e43511ee1784ccc44f56 to your computer and use it in GitHub Desktop.
Example program that renders feTurbulence using OpenGL shaders
// Produces rendering that looks like http://tests.themasta.com/turbulence.svg
//
// Save to main.mm, and then compile and run using:
// $ clang++ main.mm -Wall -O3 -framework Cocoa -framework OpenGL -o test && ./test
//
// Equivalent SVG code:
//
// <svg xmlns="http://www.w3.org/2000/svg">
// <defs>
// <filter id="turb" color-interpolation-filters="sRGB">
// <feTurbulence type="fractalNoise" baseFrequency="0.01 .01" numOctaves="5"/>
// </filter>
// </defs>
// <rect width="100%" height="100%" filter="url(#turb)"/>
// </svg>
#import <Cocoa/Cocoa.h>
#include <OpenGL/gl.h>
#define checkError()
class ShaderProgram
{
public:
ShaderProgram(const char* vertexShader, const char* fragmentShader);
virtual ~ShaderProgram() {}
virtual void Use() { glUseProgram(mProgramID); }
virtual void DrawQuad(GLuint aQuad);
protected:
GLuint mProgramID;
GLuint mPosAttribute;
};
static GLuint
CompileShaders(const char* vertexShader, const char* fragmentShader)
{
// Create the shaders
GLuint vertexShaderID = glCreateShader(GL_VERTEX_SHADER);
GLuint fragmentShaderID = glCreateShader(GL_FRAGMENT_SHADER);
GLint result = GL_FALSE;
int infoLogLength;
// Compile Vertex Shader
glShaderSource(vertexShaderID, 1, &vertexShader , NULL);
glCompileShader(vertexShaderID);
// Check Vertex Shader
glGetShaderiv(vertexShaderID, GL_COMPILE_STATUS, &result);
glGetShaderiv(vertexShaderID, GL_INFO_LOG_LENGTH, &infoLogLength);
if (infoLogLength > 0) {
char* vertexShaderErrorMessage = new char[infoLogLength+1];
glGetShaderInfoLog(vertexShaderID, infoLogLength, NULL, vertexShaderErrorMessage);
printf("%s\n", vertexShaderErrorMessage);
delete[] vertexShaderErrorMessage;
}
// Compile Fragment Shader
glShaderSource(fragmentShaderID, 1, &fragmentShader , NULL);
glCompileShader(fragmentShaderID);
// Check Fragment Shader
glGetShaderiv(fragmentShaderID, GL_COMPILE_STATUS, &result);
glGetShaderiv(fragmentShaderID, GL_INFO_LOG_LENGTH, &infoLogLength);
if (infoLogLength > 0) {
char* fragmentShaderErrorMessage = new char[infoLogLength+1];
glGetShaderInfoLog(fragmentShaderID, infoLogLength, NULL, fragmentShaderErrorMessage);
printf("%s\n", fragmentShaderErrorMessage);
delete[] fragmentShaderErrorMessage;
}
// Link the program
GLuint programID = glCreateProgram();
glAttachShader(programID, vertexShaderID);
glAttachShader(programID, fragmentShaderID);
glLinkProgram(programID);
// Check the program
glGetProgramiv(programID, GL_LINK_STATUS, &result);
glGetProgramiv(programID, GL_INFO_LOG_LENGTH, &infoLogLength);
if (infoLogLength > 0) {
char* programErrorMessage = new char[infoLogLength+1];
glGetProgramInfoLog(programID, infoLogLength, NULL, programErrorMessage);
printf("%s\n", programErrorMessage);
delete[] programErrorMessage;
}
glDeleteShader(vertexShaderID);
glDeleteShader(fragmentShaderID);
return programID;
}
ShaderProgram::ShaderProgram(const char* vertexShader, const char* fragmentShader)
{
mProgramID = CompileShaders(vertexShader, fragmentShader);
mPosAttribute = glGetAttribLocation(mProgramID, "aPos");
}
void
ShaderProgram::DrawQuad(GLuint aQuad)
{
glEnableVertexAttribArray(mPosAttribute);
glBindBuffer(GL_ARRAY_BUFFER, aQuad);
glVertexAttribPointer(
mPosAttribute, // The attribute we want to configure
2, // size
GL_FLOAT, // type
GL_FALSE, // normalized?
0, // stride
(void*)0 // array buffer offset
);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glDisableVertexAttribArray(mPosAttribute);
}
class TurbulenceProgram : public ShaderProgram
{
public:
TurbulenceProgram();
virtual ~TurbulenceProgram();
void SetSeed(int32_t aSeed);
void SetBaseFrequency(CGSize aBaseFreq);
void SetOffset(CGPoint aOffset);
protected:
GLuint mLatticeSelectorTexture;
GLuint mLatticeSelectorUniform;
GLuint mGradientTexture;
GLuint mGradientUniform;
GLuint mBaseFrequencyUniform;
GLuint mOffsetUniform;
int32_t mSeed;
};
static const char* sCoverBufferWith01aPosVertexShader =
"#version 120\n"
"// Input vertex data, different for all executions of this shader.\n"
"attribute vec2 aPos;\n"
"varying vec2 vPos;\n"
"void main(){\n"
" vPos = aPos;\n"
" gl_Position = vec4(aPos * 2.0 - vec2(1.0), 0.0, 1.0);\n"
"}\n";
static const char* sTurbulenceFragmentShader =
"#version 120\n"
"varying vec2 vPos;\n"
"uniform sampler1D uLatticeSelector;\n"
"uniform sampler1D uGradient;\n"
"uniform vec2 uBaseFrequency;\n"
"uniform vec2 uOffset;\n"
"vec2 SCurve(vec2 t)\n"
"{\n"
" return t * t * (3 - 2 * t);\n"
"}\n"
"\n"
"vec4 BiLerp(vec2 t, vec4 aa, vec4 ab, vec4 ba, vec4 bb)\n"
"{\n"
" return mix(mix(aa, ab, t.x), mix(ba, bb, t.x), t.y);\n"
"}\n"
"\n"
"\n"
"vec4 Interpolate(vec2 r, vec4 qua0, vec4 qua1, vec4 qub0, vec4 qub1, vec4 qva0, vec4 qva1, vec4 qvb0, vec4 qvb1)\n"
"{\n"
" return BiLerp(SCurve(r),\n"
" qua0 * r.x + qua1 * r.y,\n"
" qva0 * (r.x - 1) + qva1 * r.y,\n"
" qub0 * r.x + qub1 * (r.y - 1),\n"
" qvb0 * (r.x - 1) + qvb1 * (r.y - 1));\n"
"}\n"
"\n"
"vec4 Noise2(vec2 pos)\n"
"{\n"
" vec2 nearestLatticePoint = floor(pos);\n"
" vec2 fractionalOffset = pos - nearestLatticePoint;\n"
" vec2 b0 = nearestLatticePoint; // + 4096\n;"
" vec2 b1 = b0 + 1;\n"
" float i = texture1D(uLatticeSelector, b0.x / 256.0).b * 255;\n"
" float j = texture1D(uLatticeSelector, b1.x / 256.0).b * 255;\n"
" vec4 qua0 = texture1D(uGradient, (i + b0.y) / 256);\n"
" vec4 qua1 = texture1D(uGradient, (i + b0.y) / 256 + 1.0 / 512);\n"
" vec4 qub0 = texture1D(uGradient, (i + b1.y) / 256);\n"
" vec4 qub1 = texture1D(uGradient, (i + b1.y) / 256 + 1.0 / 512);\n"
" vec4 qva0 = texture1D(uGradient, (j + b0.y) / 256);\n"
" vec4 qva1 = texture1D(uGradient, (j + b0.y) / 256 + 1.0 / 512);\n"
" vec4 qvb0 = texture1D(uGradient, (j + b1.y) / 256);\n"
" vec4 qvb1 = texture1D(uGradient, (j + b1.y) / 256 + 1.0 / 512);\n"
" return Interpolate(fractionalOffset, qua0, qua1, qub0, qub1, qva0, qva1, qvb0, qvb1);\n"
"}\n"
"\n"
"void main()\n"
"{\n"
" int numOctaves = 6;\n"
" vec2 pos = (vec2(vPos.x, 1 - vPos.y) - uOffset) * uBaseFrequency;\n"
" float ratio = 1.0;\n"
" vec4 result = vec4(0, 0, 0, 0);\n"
" for (int i = 0; i < numOctaves; i++) {\n"
" result += Noise2(pos) / ratio;\n"
" pos *= 2;\n"
" ratio *= 2;\n"
" }\n"
" gl_FragColor = (result + vec4(1)) / 2;\n"
"}\n";
TurbulenceProgram::TurbulenceProgram()
: ShaderProgram(sCoverBufferWith01aPosVertexShader, sTurbulenceFragmentShader)
, mLatticeSelectorTexture(0)
, mGradientTexture(0)
, mSeed(0)
{
mLatticeSelectorUniform = glGetUniformLocation(mProgramID, "uLatticeSelector");
mGradientUniform = glGetUniformLocation(mProgramID, "uGradient");
mBaseFrequencyUniform = glGetUniformLocation(mProgramID, "uBaseFrequency");
mOffsetUniform = glGetUniformLocation(mProgramID, "uOffset");
checkError();
}
TurbulenceProgram::~TurbulenceProgram()
{
glDeleteTextures(1, &mLatticeSelectorTexture);
glDeleteTextures(1, &mGradientTexture);
checkError();
}
namespace {
struct RandomNumberSource
{
RandomNumberSource(int32_t aSeed) : mLast(SetupSeed(aSeed)) {}
int32_t Next() { mLast = Random(mLast); return mLast; }
private:
static const int32_t RAND_M = 2147483647; /* 2**31 - 1 */
static const int32_t RAND_A = 16807; /* 7**5; primitive root of m */
static const int32_t RAND_Q = 127773; /* m / a */
static const int32_t RAND_R = 2836; /* m % a */
/* Produces results in the range [1, 2**31 - 2].
Algorithm is: r = (a * r) mod m
where a = 16807 and m = 2**31 - 1 = 2147483647
See [Park & Miller], CACM vol. 31 no. 10 p. 1195, Oct. 1988
To test: the algorithm should produce the result 1043618065
as the 10,000th generated number if the original seed is 1.
*/
static int32_t
SetupSeed(int32_t aSeed) {
if (aSeed <= 0)
aSeed = -(aSeed % (RAND_M - 1)) + 1;
if (aSeed > RAND_M - 1)
aSeed = RAND_M - 1;
return aSeed;
}
static int32_t
Random(int32_t aSeed)
{
int32_t result = RAND_A * (aSeed % RAND_Q) - RAND_R * (aSeed / RAND_Q);
if (result <= 0)
result += RAND_M;
return result;
}
int32_t mLast;
};
} // unnamed namespace
const static int sBSize = 0x100;
template<typename T>
static void
Swap(T& a, T& b) {
T c = a;
a = b;
b = c;
}
void
TurbulenceProgram::SetSeed(int32_t aSeed)
{
if (aSeed != mSeed || !mLatticeSelectorTexture || !mGradientTexture) {
RandomNumberSource rand(aSeed);
uint32_t mLatticeSelector[sBSize];
float mGradient[sBSize][2][4];
float gradient[4][sBSize][2];
for (int32_t k = 0; k < 4; k++) {
for (int32_t i = 0; i < sBSize; i++) {
float a = float((rand.Next() % (sBSize + sBSize)) - sBSize) / sBSize;
float b = float((rand.Next() % (sBSize + sBSize)) - sBSize) / sBSize;
float s = sqrt(a * a + b * b);
gradient[k][i][0] = a / s;
gradient[k][i][1] = b / s;
}
}
for (int32_t i = 0; i < sBSize; i++) {
mLatticeSelector[i] = i;
}
for (int32_t i1 = sBSize - 1; i1 > 0; i1--) {
int32_t i2 = rand.Next() % sBSize;
Swap(mLatticeSelector[i1], mLatticeSelector[i2]);
}
for (int32_t i = 0; i < sBSize; i++) {
uint8_t j = mLatticeSelector[i];
mGradient[i][0][0] = gradient[0][j][0];
mGradient[i][0][1] = gradient[1][j][0];
mGradient[i][0][2] = gradient[2][j][0];
mGradient[i][0][3] = gradient[3][j][0];
mGradient[i][1][0] = gradient[0][j][1];
mGradient[i][1][1] = gradient[1][j][1];
mGradient[i][1][2] = gradient[2][j][1];
mGradient[i][1][3] = gradient[3][j][1];
}
glActiveTexture(GL_TEXTURE0);
if (!mLatticeSelectorTexture) {
glGenTextures(1, &mLatticeSelectorTexture);
}
glBindTexture(GL_TEXTURE_1D, mLatticeSelectorTexture);
glTexImage1D(GL_TEXTURE_1D, 0, GL_RGBA, 256, 0, GL_BGRA, GL_UNSIGNED_BYTE, mLatticeSelector);
glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
checkError();
glActiveTexture(GL_TEXTURE1);
if (!mGradientTexture) {
glGenTextures(1, &mGradientTexture);
}
glBindTexture(GL_TEXTURE_1D, mGradientTexture);
glTexImage1D(GL_TEXTURE_1D, 0, GL_RGBA32F_ARB, 512, 0, GL_RGBA, GL_FLOAT, mGradient);
glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
checkError();
}
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_1D, mLatticeSelectorTexture);
glUniform1i(mLatticeSelectorUniform, 0);
checkError();
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_1D, mGradientTexture);
glUniform1i(mGradientUniform, 1);
checkError();
}
void
TurbulenceProgram::SetBaseFrequency(CGSize aBaseFreq)
{
glUniform2f(mBaseFrequencyUniform, aBaseFreq.width, aBaseFreq.height);
}
void
TurbulenceProgram::SetOffset(CGPoint aOffset)
{
glUniform2f(mOffsetUniform, aOffset.x, aOffset.y);
}
@interface TestView: NSView
{
NSOpenGLContext* mContext;
TurbulenceProgram* mTurbulenceProgram;
GLuint mQuad;
NSOpenGLPixelFormat* mPixelFormat;
CGPoint mScrollOffset;
}
@end
@implementation TestView
- (id)initWithFrame:(NSRect)aFrame
{
if (self = [super initWithFrame:aFrame]) {
NSOpenGLPixelFormatAttribute attribs[] = {
NSOpenGLPFAAccelerated,
NSOpenGLPFADoubleBuffer,
(NSOpenGLPixelFormatAttribute)nil
};
mPixelFormat = [[NSOpenGLPixelFormat alloc] initWithAttributes:attribs];
mContext = [[NSOpenGLContext alloc] initWithFormat:mPixelFormat shareContext:nil];
GLint swapInt = 1;
[mContext setValues:&swapInt forParameter:NSOpenGLCPSwapInterval];
GLint opaque = 0;
[mContext setValues:&opaque forParameter:NSOpenGLCPSurfaceOpacity];
[mContext makeCurrentContext];
[self _initGL];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(_surfaceNeedsUpdate:)
name:NSViewGlobalFrameDidChangeNotification
object:self];
}
return self;
}
- (void)dealloc
{
[self _cleanupGL];
[mPixelFormat release];
[mContext release];
[super dealloc];
}
- (void)_initGL
{
mTurbulenceProgram = new TurbulenceProgram();
mQuad = [self _createQuad];
}
- (BOOL)isFlipped
{
return YES;
}
- (GLuint)_createQuad
{
static const GLfloat data[] = {
0.0f, 0.0f,
1.0f, 0.0f,
0.0f, 1.0f,
1.0f, 1.0f
};
GLuint quad;
glGenBuffers(1, &quad);
glBindBuffer(GL_ARRAY_BUFFER, quad);
glBufferData(GL_ARRAY_BUFFER, sizeof(data), data, GL_STATIC_DRAW);
checkError();
return quad;
}
- (void)_cleanupGL
{
glDeleteBuffers(1, &mQuad);
delete mTurbulenceProgram;
}
- (void)_surfaceNeedsUpdate:(NSNotification*)notification
{
[mContext update];
}
- (void)drawRect:(NSRect)aRect
{
GLdouble pointWidth = [self bounds].size.width;
GLdouble pointHeight = [self bounds].size.height;
NSSize backingSize = [self convertSizeToBacking:[self bounds].size];
GLdouble width = backingSize.width;
GLdouble height = backingSize.height;
[mContext setView:self];
[mContext makeCurrentContext];
checkError();
glViewport(0, 0, width, height);
checkError();
glClearColor(1.0, 1.0, 1.0, 1.0);
glClear(GL_COLOR_BUFFER_BIT);
checkError();
// Turbulence
checkError();
glEnable(GL_BLEND);
// Unpremultiplied operator source over
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA,
GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
mTurbulenceProgram->Use();
mTurbulenceProgram->SetSeed(0);
mTurbulenceProgram->SetBaseFrequency(CGSizeMake(pointWidth / 100, pointHeight / 100));
mTurbulenceProgram->SetOffset(CGPointMake(mScrollOffset.x / pointWidth, mScrollOffset.y / pointHeight));
mTurbulenceProgram->DrawQuad(mQuad);
glDisable(GL_BLEND);
[mContext flushBuffer];
}
- (void)scrollWheel:(NSEvent*)event
{
mScrollOffset.x += [event scrollingDeltaX];
mScrollOffset.y += [event scrollingDeltaY];
[self setNeedsDisplay:YES];
}
- (BOOL)wantsBestResolutionOpenGLSurface
{
return YES;
}
@end
@interface TerminateOnClose : NSObject<NSWindowDelegate>
@end
@implementation TerminateOnClose
- (void)windowWillClose:(NSNotification*)notification
{
[NSApp terminate:self];
}
@end
int
main (int argc, char **argv)
{
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
[NSApplication sharedApplication];
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
int style =
NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskResizable | NSWindowStyleMaskMiniaturizable;
NSRect contentRect = NSMakeRect(200, 200, 1000, 625);
NSWindow* window = [[NSWindow alloc] initWithContentRect:contentRect
styleMask:style
backing:NSBackingStoreBuffered
defer:NO];
NSView* view = [[TestView alloc] initWithFrame:NSMakeRect(0, 0, contentRect.size.width, contentRect.size.height)];
[window setContentView:view];
[window setDelegate:[[TerminateOnClose alloc] autorelease]];
[window setCollectionBehavior:[window collectionBehavior] | NSWindowCollectionBehaviorFullScreenPrimary];
[NSApp activateIgnoringOtherApps:YES];
[window makeKeyAndOrderFront:window];
[NSApp run];
[pool release];
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment