#include #include #include #include #include #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 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(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(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(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 inputs; std::vector outputs; std::string name; std::string type; char* shader_source = new char[100000]; GLuint program = 0; GLuint framebuffer, color; }; std::map resources; std::vector 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(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 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 ShaderErrorMessage(static_cast(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 ShaderErrorMessage(static_cast(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 ProgramErrorMessage(static_cast(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(); }