Archived
1
Fork 0
This repository has been archived on 2025-04-12. You can view files and clone it, but cannot push or open issues or pull requests.
shaderboy/main.cpp
2024-01-03 15:56:52 -05:00

672 lines
19 KiB
C++

#include <GLFW/glfw3.h>
#include <cstdio>
#include <string>
#include <vector>
#include <curl/curl.h>
#include "json.hpp"
#include "imgui.h"
#include "imgui_impl_glfw_gl3.h"
#include "glad/glad.h"
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
static void error_callback(int, const char* description)
{
fprintf(stderr, "Error: %s\n", description);
}
GLFWwindow* window;
std::map<std::string, GLuint> external_resources;
struct MemoryStruct {
char *memory;
size_t size;
};
static size_t WriteMemoryCallback(void *contents, size_t size, size_t nmemb, void *userp)
{
size_t realsize = size * nmemb;
struct MemoryStruct *mem = (struct MemoryStruct *)userp;
mem->memory = reinterpret_cast<char*>(realloc(mem->memory, mem->size + realsize + 1));
if(mem->memory == NULL) {
printf("not enough memory!\n");
return 0;
}
memcpy(&(mem->memory[mem->size]), contents, realsize);
mem->size += realsize;
mem->memory[mem->size] = 0;
return realsize;
}
void grab_external(const std::string& resource)
{
CURL *curl_handle;
CURLcode res;
struct MemoryStruct chunk;
chunk.memory = reinterpret_cast<char*>(malloc(1));
chunk.size = 0;
curl_handle = curl_easy_init();
std::string url = "http://www.shadertoy.com" + resource;
std::cout << "requesting external resource " << url << std::endl;
curl_easy_setopt(curl_handle, CURLOPT_URL, url.c_str());
curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, WriteMemoryCallback);
curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, (void *)&chunk);
curl_easy_setopt(curl_handle, CURLOPT_USERAGENT, "libcurl-agent/1.0");
res = curl_easy_perform(curl_handle);
if(res != CURLE_OK) {
fprintf(stderr, "curl_easy_perform() failed: %s\n",
curl_easy_strerror(res));
}
else
{
printf("%lu bytes retrieved\n", (long)chunk.size);
}
curl_easy_cleanup(curl_handle);
int width, height, channels;
stbi_uc* pixels = stbi_load_from_memory(reinterpret_cast<const stbi_uc*>(chunk.memory), chunk.size, &width, &height, &channels, STBI_rgb_alpha);
GLuint tex;
glGenTextures(1, &tex);
glBindTexture(GL_TEXTURE_2D, tex);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 2, 2, 0, GL_RGBA, GL_FLOAT, pixels);
glBindTexture(GL_TEXTURE_2D, 0);
external_resources.emplace(resource, tex);
free(chunk.memory);
}
const char* vertex_source = "in vec3 inPosition;\n"
"\n"
"out vec2 fragCoord;\n"
"\n"
"void main() {\n"
" gl_Position = vec4(inPosition, 1.0);\n"
" fragCoord = inPosition.xy;\n"
"}";
GLuint m_quadVAO, m_quadVBO;
struct input
{
int channel = 0;
int id = 0;
std::string type, src;
};
struct output
{
int channel = 0;
int id = 0;
};
struct renderpass
{
std::vector<input> inputs;
std::vector<output> outputs;
std::string name;
std::string type;
char* shader_source = new char[100000];
GLuint program = 0;
GLuint framebuffer, color;
};
std::map<int, GLuint> resources;
std::vector<renderpass> renderpasses;
void RetrackResources()
{
resources.clear();
for(auto pass : renderpasses)
{
for(auto out : pass.outputs)
{
resources.emplace(out.id, pass.color);
}
for(auto in : pass.inputs)
{
if(in.type == "texture")
{
resources.emplace(in.id, external_resources.at(in.src));
}
}
}
}
void draw_quad()
{
if(m_quadVAO == 0)
{
GLfloat quadVertices[] = {
-1.0f, 1.0f, 0.0f, 1.0f,
-1.0f, -1.0f, 0.0f, 0.0f,
1.0f, -1.0f, 1.0f, 0.0f,
-1.0f, 1.0f, 0.0f, 1.0f,
1.0f, -1.0f, 1.0f, 0.0f,
1.0f, 1.0f, 1.0f, 1.0f
};
glGenVertexArrays(1, &m_quadVAO);
glBindVertexArray(m_quadVAO);
glGenBuffers(1, &m_quadVBO);
glBindBuffer(GL_ARRAY_BUFFER, m_quadVBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(quadVertices), &quadVertices, GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), 0);
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), reinterpret_cast<void*>(2 * sizeof(GLfloat)));
}
glBindVertexArray(m_quadVAO);
glDrawArrays(GL_TRIANGLES, 0, 6);
glBindVertexArray(0);
}
void createShader(renderpass& pass, const std::string& vertex, const std::string& fragment)
{
pass.program = glCreateProgram();
GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
std::vector<GLchar const*> shaders = { vertex.c_str(), fragment.c_str() };
glShaderSource(vertexShader, 1, &shaders[0], NULL);
glShaderSource(fragmentShader, 1, &shaders[1], NULL);
glCompileShader(vertexShader);
GLint logLength;
glGetShaderiv(vertexShader, GL_INFO_LOG_LENGTH, &logLength);
if(logLength > 0)
{
std::vector<char> ShaderErrorMessage(static_cast<unsigned int>(logLength) + 1);
glGetShaderInfoLog(vertexShader, logLength, nullptr, &ShaderErrorMessage[0]);
if(ShaderErrorMessage.size() > 1)
printf("failed to compile vertex shader: %s", &ShaderErrorMessage[0]);
}
glCompileShader(fragmentShader);
glGetShaderiv(fragmentShader, GL_INFO_LOG_LENGTH, &logLength);
if(logLength > 0)
{
std::vector<char> ShaderErrorMessage(static_cast<unsigned int>(logLength) + 1);
glGetShaderInfoLog(fragmentShader, logLength, nullptr, &ShaderErrorMessage[0]);
if(ShaderErrorMessage.size() > 1)
printf("failed to compile fragment shader: %s", &ShaderErrorMessage[0]);
}
glAttachShader(pass.program, vertexShader);
glAttachShader(pass.program, fragmentShader);
glLinkProgram(pass.program);
glGetProgramiv(pass.program, GL_INFO_LOG_LENGTH, &logLength);
if(logLength > 0)
{
std::vector<char> ProgramErrorMessage(static_cast<unsigned int>(logLength) + 1);
glGetProgramInfoLog(pass.program, logLength, nullptr, &ProgramErrorMessage[0]);
if(ProgramErrorMessage.size() > 1)
{
printf("failed to link program: %s", &ProgramErrorMessage[0]);
pass.program = 0;
}
}
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
}
void createBuffer(renderpass& pass)
{
std::string shader_prefix = "#version 330\n#extension GL_ARB_separate_shader_objects : enable\n";
std::string fragment_prefix = "out vec4 outColor;\nuniform float iGlobalTime = 0.2;\nuniform vec3 iResolution = vec3(640.0, 480.0, 1.0);\nuniform float iTimeDelta = 1.0 / 6.0f;\nuniform vec4 iMouse;\n";
std::string fragment_suffix = "void main() { vec4 color = vec4(0.0,0.0,0.0,1.0); mainImage(color, gl_FragCoord.xy); outColor = color; }\n";
fragment_prefix += "uniform sampler2D iChannel0;\nuniform sampler2D iChannel1;\nuniform sampler2D iChannel2;\nuniform sampler2D iChannel3;\nuniform int iFrame;\n";
std::string vertex = shader_prefix + vertex_source;
std::string fragment = shader_prefix + fragment_prefix + pass.shader_source + fragment_suffix;
createShader(pass, vertex, fragment);
glGenFramebuffers(1, &pass.framebuffer);
glBindFramebuffer(GL_FRAMEBUFFER, pass.framebuffer);
glGenTextures(1, &pass.color);
glBindTexture(GL_TEXTURE_2D, pass.color);
int display_w, display_h;
glfwGetFramebufferSize(window, &display_w, &display_h);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, display_w, display_h, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glFramebufferTexture2D(
GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, pass.color, 0
);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
void recompile_shaders() {
for(auto& pass : renderpasses)
{
createBuffer(pass);
}
}
int edit_callback(ImGuiTextEditCallbackData* data)
{
recompile_shaders();
}
struct curl_fetch_st {
char *payload;
size_t size;
};
size_t curl_callback (void *contents, size_t size, size_t nmemb, void *userp) {
size_t realsize = size * nmemb;
struct curl_fetch_st *p = (struct curl_fetch_st *) userp;
p->payload = (char *) realloc(p->payload, p->size + realsize + 1);
if (p->payload == NULL) {
fprintf(stderr, "ERROR: Failed to expand buffer in curl_callback");
free(p->payload);
return -1;
}
memcpy(&(p->payload[p->size]), contents, realsize);
p->size += realsize;
p->payload[p->size] = 0;
return realsize;
}
CURLcode curl_fetch_url(CURL *ch, const char *url, struct curl_fetch_st *fetch) {
CURLcode rcode;
fetch->payload = (char *) calloc(1, sizeof(fetch->payload));
if (fetch->payload == NULL) {
fprintf(stderr, "ERROR: Failed to allocate payload");
return CURLE_FAILED_INIT;
}
fetch->size = 0;
curl_easy_setopt(ch, CURLOPT_URL, url);
curl_easy_setopt(ch, CURLOPT_WRITEFUNCTION, curl_callback);
curl_easy_setopt(ch, CURLOPT_WRITEDATA, (void *) fetch);
curl_easy_setopt(ch, CURLOPT_USERAGENT, "libcurl-agent/1.0");
curl_easy_setopt(ch, CURLOPT_TIMEOUT, 5);
curl_easy_setopt(ch, CURLOPT_FOLLOWLOCATION, 1);
curl_easy_setopt(ch, CURLOPT_MAXREDIRS, 1);
rcode = curl_easy_perform(ch);
return rcode;
}
int GetChannel(renderpass pass, int c)
{
RetrackResources();
for(auto in : pass.inputs)
{
if(in.channel == c)
{
if(resources.count(pass.inputs[c].id))
return resources.at(pass.inputs[c].id);
}
}
return 0;
}
int GetFinalOutput()
{
for(auto pass : renderpasses)
{
if(pass.type == "image")
return pass.color;
}
return 0;
}
void window_size_callback(GLFWwindow* window, int width, int height)
{
recompile_shaders();
}
int main(void)
{
glfwSetErrorCallback(error_callback);
if (!glfwInit())
return -1;
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
window = glfwCreateWindow(640, 480, "shaderboy", NULL, NULL);
if (!window)
glfwTerminate();
glfwMakeContextCurrent(window);
glfwSwapInterval(0);
gladLoadGL();
ImGui_ImplGlfwGL3_Init(window, true);
glfwSetWindowSizeCallback(window, window_size_callback);
char* example_code =
"void mainImage( out vec4 fragColor, in vec2 fragCoord )\n"
"{\n"
"\tvec2 uv = fragCoord.xy / iResolution.xy;\n"
"\tfragColor = vec4(uv,0.5+0.5*sin(iGlobalTime),1.0);\n"
"}";
renderpass defaultpass;
defaultpass.type = "image";
defaultpass.name = "Image";
defaultpass.shader_source = example_code;
output defaultout;
defaultout.channel = 0;
defaultout.id = 37;
defaultpass.outputs.push_back(defaultout);
renderpasses.push_back(defaultpass);
recompile_shaders();
char* shader_url;
shader_url = new char[512];
memset(shader_url, 0, 512);
recompile_shaders();
curl_global_init(CURL_GLOBAL_ALL);
int frames = 0;
bool paused = false;
while (!glfwWindowShouldClose(window))
{
ImGui_ImplGlfwGL3_NewFrame();
ImGui::Begin("shaderboy");
ImGui::Text("FPS: %1.f", ImGui::GetIO().Framerate);
if(ImGui::Button("Pause/Resume"))
paused = !paused;
ImGui::InputText("Shader URL", shader_url, 512);
if(ImGui::Button("Download"))
{
CURL *ch;
CURLcode rcode;
curl_fetch_st curl_fetch;
curl_fetch_st *cf = &curl_fetch;
curl_slist *headers = nullptr;
const char *url = "https://www.shadertoy.com/shadertoy";
if ((ch = curl_easy_init()) == nullptr) {
fprintf(stderr, "ERROR: Failed to create curl handle!");
return 1;
}
headers = curl_slist_append(headers, "Accept: application/json");
headers = curl_slist_append(headers, "Referer: https://www.shadertoy.com/");
headers = curl_slist_append(headers, "Content-Type: application/x-www-form-urlencoded");
std::string u = std::string(shader_url);
std::string shaderID = u.substr(u.find_last_of("/") + 1, u.length());
std::cout << shaderID << std::endl;
curl_easy_setopt(ch, CURLOPT_CUSTOMREQUEST, "POST");
curl_easy_setopt(ch, CURLOPT_HTTPHEADER, headers);
std::string tmp = "s=%7B%20%22shaders%22%20%3A%20%5B%22" + shaderID + "%22%5D%20%7D";
curl_easy_setopt(ch, CURLOPT_POSTFIELDS, tmp.c_str());
rcode = curl_fetch_url(ch, url, cf);
curl_easy_cleanup(ch);
curl_slist_free_all(headers);
if (rcode != CURLE_OK || cf->size < 1) {
fprintf(stderr, "ERROR: Failed to fetch url (%s) - curl said: %s", url, curl_easy_strerror(rcode));
return 2;
}
if (cf->payload != NULL) {
try
{
std::string test = cf->payload;
test = test.substr(1, test.length() - 2);
nlohmann::json json = nlohmann::json::parse(test);
renderpasses.clear();
for(auto pass : json["renderpass"])
{
renderpass renderpass;
renderpass.name = pass["name"];
renderpass.type = pass["type"];
std::string code = pass["code"];
strcpy(renderpass.shader_source, code.c_str());
for(auto in : pass["inputs"])
{
input input;
input.channel = in["channel"];
input.id = in["id"];
input.type = in["ctype"];
input.src = in["src"];
if(input.type == "texture")
{
if(!external_resources.count(input.src))
grab_external(input.src);
}
renderpass.inputs.push_back(input);
}
for(auto ou : pass["outputs"])
{
output output;
output.id = ou["id"];
output.channel = ou["channel"];
renderpass.outputs.push_back(output);
}
renderpasses.push_back(renderpass);
}
recompile_shaders();
free(cf->payload);
}
catch(std::exception)
{}
} else {
/* error */
fprintf(stderr, "ERROR: Failed to populate payload");
/* free payload */
free(cf->payload);
/* return */
return 3;
}
}
//TODO: add back shader editing
//ImGui::InputTextMultiline("Shader", shader_source, 100000, ImVec2(-1, -1), ImGuiInputTextFlags_CallbackAlways, edit_callback);
for(auto pass : renderpasses)
{
ImGui::Separator();
ImGui::Text("Name: %s", pass.name.c_str());
ImGui::Text("Type: %s", pass.type.c_str());
ImGui::Text("Inputs:");
ImGui::Indent();
for(auto input : pass.inputs)
{
ImGui::Text("Id: %i", input.id);
ImGui::Text("Channel: %i", input.channel);
ImGui::Text("Type: %s", input.type.c_str());
ImGui::Text("Src: %s", input.src.c_str());
}
ImGui::Unindent();
ImGui::Spacing();
ImGui::Text("Outputs:");
ImGui::Indent();
for(auto output : pass.outputs)
{
ImGui::Text("Id: %i", output.id);
ImGui::Text("Channel: %i", output.channel);
}
ImGui::Unindent();
}
ImGui::Text("Resources:");
for(auto res : resources)
{
ImGui::Text("%i", res.first);
}
if(ImGui::Button("Recompile"))
recompile_shaders();
ImGui::End();
int display_w, display_h;
glfwGetFramebufferSize(window, &display_w, &display_h);
glViewport(0, 0, display_w, display_h);
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
//render out passes
if(!paused)
{
for(auto pass : renderpasses)
{
if(pass.program != 0)
{
glBindFramebuffer(GL_FRAMEBUFFER, pass.framebuffer);
glUseProgram(pass.program);
glUniform1i(glGetUniformLocation(pass.program, "iChannel0"), 0);
glUniform1i(glGetUniformLocation(pass.program, "iChannel1"), 1);
glUniform1i(glGetUniformLocation(pass.program, "iChannel2"), 2);
glUniform1i(glGetUniformLocation(pass.program, "iChannel3"), 3);
glUniform1i(glGetUniformLocation(pass.program, "iFrame"), 0);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, GetChannel(pass, 0));
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, GetChannel(pass, 1));
glActiveTexture(GL_TEXTURE2);
glBindTexture(GL_TEXTURE_2D, GetChannel(pass, 2));
glActiveTexture(GL_TEXTURE3);
glBindTexture(GL_TEXTURE_2D, GetChannel(pass, 3));
glUniform1f(glGetUniformLocation(pass.program, "iGlobalTime"), glfwGetTime());
glUniform3f(glGetUniformLocation(pass.program, "iResolution"), display_w, display_h, 1.0);
int state = glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_LEFT);
if(state == GLFW_PRESS)
{
double xpos, ypos;
glfwGetCursorPos(window, &xpos, &ypos);
glUniform4f(glGetUniformLocation(pass.program, "iMouse"), xpos, ypos, 0.0, 0.0);
}
draw_quad();
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
}
}
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, GetFinalOutput());
draw_quad();
ImGui::Render();
glfwSwapBuffers(window);
glfwPollEvents();
frames++;
}
curl_global_cleanup();
glfwDestroyWindow(window);
glfwTerminate();
}