├── .gitignore
├── LearnJUCEOpenGL.jucer
├── README.md
├── Source
├── Main.cpp
├── MainComponent.cpp
├── MainComponent.h
└── Shaders
│ ├── Frag.frag
│ └── Vert.vert
└── screenshot.png
/.gitignore:
--------------------------------------------------------------------------------
1 | Builds
2 | JuceLibraryCode
3 | .DS_Store
--------------------------------------------------------------------------------
/LearnJUCEOpenGL.jucer:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # LearnJUCEOpenGL
2 | ### Simple beginner's reference app on the absolute basics of programming 2D animations in JUCE using OpenGL.
3 | 
4 |
5 | #### Thanks to Marius Metzger (CrushedPixel) for his help with this project.
6 |
--------------------------------------------------------------------------------
/Source/Main.cpp:
--------------------------------------------------------------------------------
1 | /*
2 | ==============================================================================
3 |
4 | This file was auto-generated!
5 |
6 | It contains the basic startup code for a JUCE application.
7 |
8 | ==============================================================================
9 | */
10 |
11 | #include "../JuceLibraryCode/JuceHeader.h"
12 | #include "MainComponent.h"
13 |
14 | //==============================================================================
15 | class LearnJUCEOpenGLApplication : public JUCEApplication
16 | {
17 | public:
18 | //==============================================================================
19 | LearnJUCEOpenGLApplication() {}
20 |
21 | const String getApplicationName() override { return ProjectInfo::projectName; }
22 | const String getApplicationVersion() override { return ProjectInfo::versionString; }
23 | bool moreThanOneInstanceAllowed() override { return true; }
24 |
25 | //==============================================================================
26 | void initialise (const String& commandLine) override
27 | {
28 | // This method is where you should put your application's initialisation code..
29 |
30 | mainWindow.reset (new MainWindow (getApplicationName()));
31 | }
32 |
33 | void shutdown() override
34 | {
35 | // Add your application's shutdown code here..
36 |
37 | mainWindow = nullptr; // (deletes our window)
38 | }
39 |
40 | //==============================================================================
41 | void systemRequestedQuit() override
42 | {
43 | // This is called when the app is being asked to quit: you can ignore this
44 | // request and let the app carry on running, or call quit() to allow the app to close.
45 | quit();
46 | }
47 |
48 | void anotherInstanceStarted (const String& commandLine) override
49 | {
50 | // When another instance of the app is launched while this one is running,
51 | // this method is invoked, and the commandLine parameter tells you what
52 | // the other instance's command-line arguments were.
53 | }
54 |
55 | //==============================================================================
56 | /*
57 | This class implements the desktop window that contains an instance of
58 | our MainComponent class.
59 | */
60 | class MainWindow : public DocumentWindow
61 | {
62 | public:
63 | MainWindow (String name) : DocumentWindow (name,
64 | Desktop::getInstance().getDefaultLookAndFeel()
65 | .findColour (ResizableWindow::backgroundColourId),
66 | DocumentWindow::allButtons)
67 | {
68 | setUsingNativeTitleBar (true);
69 | setContentOwned (new MainComponent(), true);
70 |
71 | #if JUCE_IOS || JUCE_ANDROID
72 | setFullScreen (true);
73 | #else
74 | setResizable (true, true);
75 | centreWithSize (getWidth(), getHeight());
76 | #endif
77 |
78 | setVisible (true);
79 | }
80 |
81 | void closeButtonPressed() override
82 | {
83 | // This is called when the user tries to close this window. Here, we'll just
84 | // ask the app to quit when this happens, but you can change this to do
85 | // whatever you need.
86 | JUCEApplication::getInstance()->systemRequestedQuit();
87 | }
88 |
89 | /* Note: Be careful if you override any DocumentWindow methods - the base
90 | class uses a lot of them, so by overriding you might break its functionality.
91 | It's best to do all your work in your content component instead, but if
92 | you really have to override any DocumentWindow methods, make sure your
93 | subclass also calls the superclass's method.
94 | */
95 |
96 | private:
97 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainWindow)
98 | };
99 |
100 | private:
101 | std::unique_ptr mainWindow;
102 | };
103 |
104 | //==============================================================================
105 | // This macro generates the main() routine that launches the app.
106 | START_JUCE_APPLICATION (LearnJUCEOpenGLApplication)
107 |
--------------------------------------------------------------------------------
/Source/MainComponent.cpp:
--------------------------------------------------------------------------------
1 | /*
2 | ==============================================================================
3 |
4 | This file was auto-generated!
5 |
6 | ==============================================================================
7 | */
8 |
9 | #include "MainComponent.h"
10 | //==============================================================================
11 | MainComponent::MainComponent()
12 | {
13 | shader_program_source = parse_shaders();
14 | openGL_context.setOpenGLVersionRequired(juce::OpenGLContext::openGL3_2);
15 | openGL_context.setRenderer(this);
16 | openGL_context.setContinuousRepainting(true);
17 |
18 | // Benchmarking-only
19 | setPaintingIsUnclipped(true);
20 | setOpaque(true);
21 | init_button();
22 | startTimerHz(software_fps_requested);
23 |
24 | setSize(screen_resolution.x, screen_resolution.y);
25 | }
26 | MainComponent::~MainComponent()
27 | {
28 | openGL_context.detach();
29 | }
30 | void MainComponent::newOpenGLContextCreated()
31 | {
32 | // Create the shader program and specify the CPU-computed variables (uniforms) required for animation.
33 | shader_prog_ID = create_program(shader_program_source);
34 | GL::glUseProgram(shader_prog_ID);
35 |
36 | // The resolution is static, computed at app initialization, and thus can afford to be sent just once here.
37 | uf_resolution = GL::glGetUniformLocation(shader_prog_ID, "resolution");
38 | GL::glUniform2f(uf_resolution, screen_resolution.x, screen_resolution.y);
39 |
40 | // The distance variable is dynamic, and thus is only declared here.
41 | // Sending it to the GPU is deferred as it must be computed and sent at each draw call.
42 | uf_distance = GL::glGetUniformLocation(shader_prog_ID, "distance");
43 |
44 | // Explicitly generate a vertex array (rather than use the default created one) to specify the vertex buffer layout in each draw call.
45 | // Otherwise, using the default requires that each draw call must bind the vertex buffer, enable the attrib array, and specify the attrib pointers.
46 | GL::glGenVertexArrays(1, &vertex_arr_ID);
47 | GL::glBindVertexArray(vertex_arr_ID);
48 |
49 | // Load the (x, y) positions (which are just the corners of the screen), into a static area of GPU memory.
50 | GL::glGenBuffers(1, &vertex_buff_ID);
51 | GL::glBindBuffer(GL_ARRAY_BUFFER, vertex_buff_ID);
52 | GL::glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * positions_count, positions, GL_STATIC_DRAW);
53 |
54 | // Specify the attribute layout of the currently bound vertex buffer object (there's only one attribute - position).
55 | // The currently instantiated vertex array object will use this specification for each draw call.
56 | GL::glEnableVertexAttribArray(pos_attrib_id);
57 | GL::glVertexAttribPointer(pos_attrib_id, num_floats_per_pos_attrib, GL_FLOAT, GL_FALSE,
58 | sizeof(GLfloat) * num_floats_per_pos_attrib, (const void*)0); // For more that one attrib, can use a struct with a C++ macro to determine this void* offset.
59 |
60 | // Specify which (x, y) positions are used to draw two triangles in the shape of a rectangle (the screen) using the positions in the currently bound vertex buffer object.
61 | GL::glGenBuffers(1, &index_buff_ID);
62 | GL::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, index_buff_ID);
63 | GL::glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(GLuint) * elements_count, elements, GL_STATIC_DRAW);
64 |
65 | // No need to unbind the array, buffer, or shader program thanks to JUCE.
66 | }
67 | void MainComponent::renderOpenGL()
68 | {
69 | // Bind shader program and send the CPU-computed distance variable (it needs for this frame) for animation.
70 | GL::glUseProgram(shader_prog_ID);
71 | const auto distance = std::abs(std::sin(Time::currentTimeMillis() / 1000.) * screen_resolution.y * 2);
72 | GL::glUniform1f(uf_distance, distance);
73 |
74 | // Binding the array simultaneously tells the GPU which vertex buffer to use as well as the specification of the buffer's memory layout.
75 | GL::glBindVertexArray(vertex_arr_ID);
76 |
77 | // The element (or index) buffer is still required to be bound at each draw call to specify how to use the positions in the buffer to draw the correct shape.
78 | GL::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, index_buff_ID);
79 | glDrawElements(GL_TRIANGLES, elements_count, GL_UNSIGNED_INT, nullptr);
80 |
81 | // Benchmark the software renderer against the OpenGL renderer
82 | time_frames();
83 | }
84 | void MainComponent::openGLContextClosing()
85 | { // Destroying GL objects require that the openGL_context is not yet destroyed, i.e. cannot delete in the destructor.
86 | GL::glDeleteProgram(shader_prog_ID);
87 | GL::glDeleteBuffers(1, &vertex_buff_ID);
88 | GL::glDeleteBuffers(1, &index_buff_ID);
89 | }
90 | void MainComponent::paint(Graphics& g)
91 | {
92 | if (! openGL_context.isAttached()) {
93 | const auto bounds = getLocalBounds().toFloat();
94 | g.setColour(Colours::black);
95 | g.fillRect(bounds);
96 | g.setColour(Colour::fromFloatRGBA(1.f, 0.f, 1.f, 0.8f));
97 | const auto d = std::abs(std::sin(Time::currentTimeMillis() / 1000.) * getHeight());
98 | g.drawEllipse(bounds.withSizeKeepingCentre(d, d), d / 12.f);
99 |
100 | // Benchmark the software renderer against the OpenGL renderer
101 | time_frames();
102 | }
103 | }
104 | void MainComponent::resized()
105 | {
106 | openGL_button.setBoundsRelative(0.f, 0.f, 0.1f, 0.1f);
107 | }
108 | void MainComponent::timerCallback()
109 | {
110 | repaint();
111 | }
112 | MainComponent::ShaderProgramSource MainComponent::parse_shaders()
113 | {
114 | auto shader_folder = File::getCurrentWorkingDirectory();
115 | while (!shader_folder.isRoot()) {
116 | if (shader_folder.getFileName() == JUCEApplication::getInstance()->getApplicationName()) {
117 | shader_folder = shader_folder.getChildFile("Source/Shaders/");
118 | break;
119 | }
120 | shader_folder = shader_folder.getParentDirectory();
121 | }
122 | if (! shader_folder.exists()) {
123 | jassertfalse;
124 | }
125 | const auto vertex_file = shader_folder.getChildFile("Vert.vert");
126 | const auto fragment_file = shader_folder.getChildFile("Frag.frag");
127 | return { vertex_file.loadFileAsString(), fragment_file.loadFileAsString() };
128 | }
129 | GLuint MainComponent::create_shader(const GLenum type, const GLchar* source, const GLint source_length)
130 | {
131 | const auto shID = GL::glCreateShader(type);
132 | GL::glShaderSource(shID, 1, &source, &source_length);
133 | GL::glCompileShader(shID);
134 |
135 | // Check shader compilation success
136 | GLint success;
137 | GL::glGetShaderiv(shID, GL_COMPILE_STATUS, &success);
138 | if (!success) {
139 | char infoLog[512];
140 | GL::glGetShaderInfoLog(shID, 512, nullptr, infoLog);
141 | DBG(infoLog);
142 | jassertfalse;
143 | }
144 | return shID;
145 | }
146 | GLuint MainComponent::create_program(const ShaderProgramSource& source)
147 | {
148 | const auto vxID = create_shader(GL_VERTEX_SHADER, source.VertexSource.getCharPointer(),
149 | sizeof(GLchar) * source.VertexSource.length());
150 | const auto fsID = create_shader(GL_FRAGMENT_SHADER, source.FragmentSource.getCharPointer(),
151 | sizeof(GLchar) * source.FragmentSource.length());
152 | const auto spID = GL::glCreateProgram();
153 | GL::glAttachShader(spID, vxID);
154 | GL::glAttachShader(spID, fsID);
155 | GL::glLinkProgram(spID);
156 |
157 | // Check program linking success
158 | GLint success;
159 | GL::glGetProgramiv(spID, GL_LINK_STATUS, &success);
160 | if (!success) {
161 | char infoLog[512];
162 | GL::glGetProgramInfoLog(spID, 512, nullptr, infoLog);
163 | DBG(infoLog);
164 | jassertfalse;
165 | }
166 | GL::glDeleteShader(vxID);
167 | GL::glDeleteShader(fsID);
168 | return spID;
169 | }
170 | void MainComponent::init_button()
171 | {
172 | addAndMakeVisible(openGL_button);
173 | auto&& laf = getLookAndFeel();
174 | laf.setColour(TextButton::buttonColourId, Colours::black);
175 | laf.setColour(TextButton::buttonOnColourId, Colours::black);
176 | laf.setColour(TextButton::textColourOnId, Colours::limegreen);
177 | laf.setColour(TextButton::textColourOffId, Colours::white);
178 |
179 | openGL_button.setClickingTogglesState(true);
180 | openGL_button.onClick = [this] {
181 | if (openGL_button.getToggleState()) {
182 | stopTimer();
183 | openGL_context.attachTo(*this);
184 | }
185 | else {
186 | openGL_context.detach();
187 | startTimerHz(software_fps_requested);
188 | }
189 | };
190 | }
191 | void MainComponent::time_frames()
192 | {
193 | const auto current_time = Time::currentTimeMillis();
194 | frame_count++;
195 | if (current_time - prev_time >= 1000. ){
196 | frame_time = 1000. / frame_count;
197 | DBG(String::formatted("%f ms/frame", frame_time));
198 | frame_count = 0;
199 | prev_time = current_time;
200 | }
201 | }
202 |
--------------------------------------------------------------------------------
/Source/MainComponent.h:
--------------------------------------------------------------------------------
1 | /*
2 | ==============================================================================
3 |
4 | This file was auto-generated!
5 |
6 | ==============================================================================
7 | */
8 |
9 | #pragma once
10 | #include "../JuceLibraryCode/JuceHeader.h"
11 | //==============================================================================
12 | /*
13 | This component lives inside our window, and this is where you should put all
14 | your controls and content.
15 | */
16 | using GL = juce::OpenGLExtensionFunctions;
17 | class MainComponent : public Component, public OpenGLRenderer, public Timer
18 | {
19 | public:
20 | //==============================================================================
21 | MainComponent();
22 | ~MainComponent();
23 |
24 | //==============================================================================
25 | void newOpenGLContextCreated() override;
26 | void renderOpenGL() override;
27 | void openGLContextClosing() override;
28 | void paint(Graphics& g) override;
29 | void resized() override;
30 | void timerCallback() override;
31 |
32 | private:
33 | struct ShaderProgramSource
34 | {
35 | String VertexSource;
36 | String FragmentSource;
37 | };
38 | //==============================================================================
39 |
40 | // OpenGL
41 | OpenGLContext openGL_context;
42 | ShaderProgramSource shader_program_source;
43 | Point screen_resolution { 800, 600 };
44 |
45 | GLuint vertex_arr_ID, vertex_buff_ID, index_buff_ID, shader_prog_ID;
46 | GLint uf_distance, uf_resolution;
47 |
48 | static constexpr int positions_count = 8;
49 | GLfloat positions[positions_count] = {
50 | -1.0f, -1.0f,
51 | 1.0f, -1.0f,
52 | 1.0f, 1.0f,
53 | -1.0f, 1.0f
54 | };
55 | static constexpr int elements_count = 6;
56 | GLuint elements[elements_count] = {
57 | 0, 1, 2,
58 | 0, 2, 3
59 | };
60 | // "Position" vertex attribute parameters
61 | static constexpr int pos_attrib_id = 0;
62 | static constexpr int num_floats_per_pos_attrib = 2;
63 |
64 | // Benchmark-only
65 | TextButton openGL_button{ "OpenGL" };
66 | double prev_time{}, frame_time{};
67 | int frame_count{}, software_fps_requested = 200; // Try to max out repaint speed.
68 |
69 | //==============================================================================
70 |
71 | static ShaderProgramSource parse_shaders();
72 | static GLuint create_shader(const GLenum type, const GLchar* source, const GLint source_length);
73 | static GLuint create_program(const ShaderProgramSource& source);
74 | void init_button();
75 | void time_frames();
76 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainComponent)
77 | };
78 |
--------------------------------------------------------------------------------
/Source/Shaders/Frag.frag:
--------------------------------------------------------------------------------
1 | //#version 330 core
2 | // On macOS, only shader version 150 works, 330 isn’t supported
3 | #version 150
4 |
5 | uniform float distance;
6 | uniform vec2 resolution;
7 | out vec4 out_color;
8 |
9 | void main()
10 | {
11 | vec2 p = (gl_FragCoord.xy - resolution) / distance;
12 |
13 | float thickness = 0.02;
14 | float radius = 0.5;
15 | float intensity = thickness/abs(radius - length(p));
16 |
17 | out_color = vec4(intensity, 0., intensity, .5);
18 | }
19 |
--------------------------------------------------------------------------------
/Source/Shaders/Vert.vert:
--------------------------------------------------------------------------------
1 | //#version 330 core
2 | // On macOS, only shader version 150 works, 330 (and thus "layout") isn’t supported
3 | // layout(location = 0) in vec4 position;
4 | #version 150
5 |
6 | in vec4 position;
7 |
8 | void main()
9 | {
10 | gl_Position = position;
11 | }
12 |
--------------------------------------------------------------------------------
/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ianacaburian/LearnJUCEOpenGL/2a8c1ea98b60a07af58c7c887b5d495ec12f6ce5/screenshot.png
--------------------------------------------------------------------------------