--- /dev/null
+#include <random>
+#include <msp/gl/renderer.h>
+#include <msp/gl/texture2d.h>
+#include "filmgrain.h"
+#include "resources.h"
+
+using namespace std;
+
+namespace Msp {
+namespace DemoScene {
+
+FilmGrain::FilmGrain(unsigned s, unsigned l):
+ mesh(get_fullscreen_quad()),
+ shprog(Resources::get_builtins().get<GL::Program>("filmgrain.glsl")),
+ size(s),
+ layers(l),
+ current_layer(0)
+{
+ grain.set_min_filter(GL::LINEAR);
+ grain.set_mag_filter(GL::LINEAR);
+ grain.set_wrap(GL::REPEAT);
+ grain.storage(GL::R8, size, size, layers, 1);
+
+ generate_grain();
+
+ texturing.attach(1, grain);
+ shdata.uniform("grain", 1);
+
+ set_coarseness(2.0f);
+ set_strength(0.1f);
+}
+
+void FilmGrain::generate_grain()
+{
+ UInt8 *data = new UInt8[size*size*layers];
+
+ unsigned noise_size = size/2;
+ UInt8 *noise = new UInt8[noise_size*noise_size];
+ Int16 *dbuf = new Int16[3*noise_size*noise_size];
+ minstd_rand random;
+
+ for(unsigned i=0; i<layers; ++i)
+ {
+ for(unsigned j=0; j<noise_size*noise_size; ++j)
+ noise[j] = random();
+
+ bicubic2x(noise, data+i*size*size, dbuf);
+ }
+
+ delete[] noise;
+ delete[] dbuf;
+
+ grain.image(0, GL::RED, GL::UNSIGNED_BYTE, data);
+ delete[] data;
+}
+
+void FilmGrain::bicubic2x(const UInt8 *src, UInt8 *target, Int16 *dbuf)
+{
+ unsigned src_size = size/2;
+
+ // dx and dy will have an implicit scaling factor of 2
+ Int16 *dx = dbuf;
+ Int16 *dy = dbuf+src_size*src_size;
+ for(unsigned y=0; y<src_size; ++y)
+ for(unsigned x=0; x<src_size; ++x)
+ {
+ unsigned i = x+y*src_size;
+ dx[i] = src[(x+1)%src_size + y*src_size] - src[(x+src_size-1)%src_size + y*src_size];
+ dy[i] = src[x + (y+1)%src_size*src_size] - src[x + (y+src_size-1)%src_size*src_size];
+ }
+
+ // dxy will have an implicit scaling factor of 4
+ Int16 *dxy = dbuf+2*src_size*src_size;
+ for(unsigned y=0; y<src_size; ++y)
+ for(unsigned x=0; x<src_size; ++x)
+ dxy[x+y*src_size] = dy[(x+1)%src_size + y*src_size] - dy[(x+src_size-1)%src_size + y*src_size];
+
+ for(unsigned y=0; y<src_size; ++y)
+ {
+ unsigned y2 = (y+1)%src_size;
+
+ for(unsigned x=0; x<src_size; ++x)
+ {
+ unsigned x2 = (x+1)%src_size;
+
+ unsigned i00 = x+y*src_size;
+ unsigned i10 = x2+y*src_size;
+ unsigned i01 = x+y2*src_size;
+ unsigned i11 = x2+y2*src_size;
+
+ // Cubic hermite at t=0.5: 0.5*p0 + 0.125*m0 + 0.5*p1 - 0.125*m1
+ unsigned i = x*2+y*2*size;
+ target[i] = src[i00];
+ target[i+1] = clamp(((src[i00]+src[i10])*8+dx[i00]-dx[i10])/16);
+ target[i+size] = clamp(((src[i00]+src[i01])*8+dy[i00]-dy[i01])/16);
+ // dyh0 and dyh1 will have an implicit scaling factor of 32
+ Int16 dyh0 = (dy[i00]+dy[i10])*8+dxy[i00]-dxy[i10];
+ Int16 dyh1 = (dy[i01]+dy[i11])*8+dxy[i01]-dxy[i11];
+ target[i+1+size] = clamp(((src[i00]+src[i10]+src[i01]+src[i11])*64+dyh0-dyh1)/256);
+ }
+ }
+}
+
+UInt8 FilmGrain::clamp(int v)
+{
+ return min(max(v, 0), 255);
+}
+
+void FilmGrain::set_coarseness(float c)
+{
+ shdata.uniform("coarseness", c/2.0f);
+}
+
+void FilmGrain::set_strength(float s)
+{
+ shdata.uniform("strength", s);
+}
+
+void FilmGrain::render(GL::Renderer &renderer, const GL::Texture2D &color, const GL::Texture2D &)
+{
+ current_layer = (current_layer+1)%layers;
+ shdata.uniform("grain_layer", static_cast<int>(current_layer));
+ texturing.attach(0, color);
+ renderer.set_texturing(&texturing);
+ renderer.set_shader_program(&shprog, &shdata);
+ mesh.draw(renderer);
+}
+
+
+FilmGrain::Template::Template():
+ size(256),
+ layers(16),
+ coarseness(2.0f),
+ strength(0.1f)
+{ }
+
+FilmGrain *FilmGrain::Template::create(unsigned, unsigned) const
+{
+ FilmGrain *grain = new FilmGrain(size, layers);
+ grain->set_coarseness(coarseness);
+ grain->set_strength(strength);
+ return grain;
+}
+
+
+FilmGrain::Template::Loader::Loader(Template &t):
+ DerivedObjectLoader<Template, PostProcessor::Template::Loader>(t)
+{
+ add("coarseness", &Template::coarseness);
+ add("layers", &Template::layers);
+ add("size", &Template::size);
+ add("strength", &Template::strength);
+}
+
+} // namespace DemoScene
+} // namespace Msp
--- /dev/null
+#ifndef MSP_DEMOSCENE_FILMGRAIN_H_
+#define MSP_DEMOSCENE_FILMGRAIN_H_
+
+#include <msp/gl/mesh.h>
+#include <msp/gl/postprocessor.h>
+#include <msp/gl/program.h>
+#include <msp/gl/programdata.h>
+#include <msp/gl/texture2darray.h>
+#include <msp/gl/texturing.h>
+
+namespace Msp {
+namespace DemoScene {
+
+class FilmGrain: public GL::PostProcessor
+{
+public:
+ struct Template: public PostProcessor::Template
+ {
+ class Loader: public DataFile::DerivedObjectLoader<Template, PostProcessor::Template::Loader>
+ {
+ public:
+ Loader(Template &t);
+ };
+
+ unsigned size;
+ unsigned layers;
+ float coarseness;
+ float strength;
+
+ Template();
+
+ virtual FilmGrain *create(unsigned, unsigned) const;
+ };
+
+private:
+ const Msp::GL::Mesh &mesh;
+ const Msp::GL::Program &shprog;
+ Msp::GL::ProgramData shdata;
+ Msp::GL::Texturing texturing;
+ unsigned size;
+ unsigned layers;
+ Msp::GL::Texture2DArray grain;
+ unsigned current_layer;
+
+public:
+ FilmGrain(unsigned = 256, unsigned = 16);
+
+private:
+ void generate_grain();
+ void bicubic2x(const UInt8 *, UInt8 *, Int16 *);
+ static UInt8 clamp(int);
+
+public:
+ void set_coarseness(float);
+ void set_strength(float);
+
+ virtual void render(GL::Renderer &, const GL::Texture2D &, const GL::Texture2D &);
+};
+
+} // namespace DemoScene
+} // namespace Msp
+
+#endif