/* * Copyright (c) 2020 The Khronos Group Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include // OpenCL SDK includes #include // OpenCL Utils includes #include // STL includes #include #include #include #include #include #include // std::make_tuple // OpenGL includes #include #include template struct DoubleBuffer { T front, back; void swap() { std::swap(front, back); } }; class Conway : public cl::sdk::InteropWindow { public: explicit Conway(int width, int height, bool fullscreen, cl_uint platform_id = 0, cl_uint device_id = 0, cl_bitfield device_type = CL_DEVICE_TYPE_DEFAULT) : InteropWindow{ sf::VideoMode(width, height), "Conway's Game of Life", fullscreen ? sf::Style::Fullscreen : sf::Style::Default, sf::ContextSettings{ 0, 0, 0, // Depth, Stencil, AA 3, 3, // OpenGL version sf::ContextSettings::Attribute::Core }, platform_id, device_id, device_type }, animating(true) {} protected: virtual void initializeGL() override; // Function that initializes all OpenGL assets // needed to draw a scene virtual void initializeCL() override; // Function that initializes all OpenCL assets // needed to draw a scene virtual void updateScene() override; // Function that holds scene update guaranteed not // to conflict with drawing virtual void render() override; // Function that does the native rendering virtual void event(const sf::Event& e) override; // Function that handles render area resize private: // OpenGL objects cl_GLuint vertex_shader, fragment_shader, gl_program; cl_GLuint vertex_buffer, vertex_array; DoubleBuffer gl_images; // OpenCL objects cl::Device device; cl::CommandQueue queue; cl::Program cl_program; cl::Kernel kernel; cl::Sampler sampler; DoubleBuffer cl_images; cl::vector interop_resources; bool animating; }; inline bool checkError(const char* Title) { int Error; if ((Error = glGetError()) != GL_NO_ERROR) { std::string ErrorString; switch (Error) { case GL_INVALID_ENUM: ErrorString = "GL_INVALID_ENUM"; break; case GL_INVALID_VALUE: ErrorString = "GL_INVALID_VALUE"; break; case GL_INVALID_OPERATION: ErrorString = "GL_INVALID_OPERATION"; break; case GL_INVALID_FRAMEBUFFER_OPERATION: ErrorString = "GL_INVALID_FRAMEBUFFER_OPERATION"; break; case GL_OUT_OF_MEMORY: ErrorString = "GL_OUT_OF_MEMORY"; break; default: ErrorString = "UNKNOWN"; break; } std::cerr << "OpenGL Error(" << ErrorString.c_str() << "): " << Title << std::endl; } return Error == GL_NO_ERROR; } void Conway::initializeGL() { if (glewInit() != GLEW_OK) std::exit(EXIT_FAILURE); auto create_shader = [](std::string file_path, cl_GLenum shader_stage) { std::ifstream shader_stream(file_path); std::string shader_string{ std::istreambuf_iterator{ shader_stream }, std::istreambuf_iterator{} }; auto pshader_string = shader_string.c_str(); GLuint shader = glCreateShader(shader_stage); glShaderSource(shader, 1, &pshader_string, NULL); checkError("glShaderSource(shader, 1, &pshader_string, NULL)"); glCompileShader(shader); checkError("glCompileShader(shader)"); GLint status = GL_FALSE; glGetShaderiv(shader, GL_COMPILE_STATUS, &status); checkError("glGetShaderiv(shader, GL_COMPILE_STATUS, &status)"); if (status != GL_TRUE) { int log_length = 0; glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &log_length); checkError( "glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &log_length)"); std::vector shader_log(log_length); glGetShaderInfoLog(shader, log_length, NULL, shader_log.data()); checkError("glGetShaderInfoLog(shader, log_length, NULL, " "shader_log.data())"); std::cerr << std::string(shader_log.cbegin(), shader_log.cend()) << std::endl; } return shader; }; auto create_program = [](std::initializer_list shader_stages) { GLuint program = glCreateProgram(); checkError("glCreateProgram()"); for (auto shader_stage : shader_stages) { glAttachShader(program, shader_stage); checkError("glAttachShader(program, shader_stage)"); } glLinkProgram(program); checkError("glLinkProgram(program)"); GLint status = GL_FALSE; glGetProgramiv(program, GL_LINK_STATUS, &status); checkError("glGetProgramiv(program, GL_LINK_STATUS, &status)"); if (status != GL_TRUE) { int log_length = 0; glGetProgramiv(program, GL_INFO_LOG_LENGTH, &log_length); checkError( "glGetProgramiv(program, GL_INFO_LOG_LENGTH, &log_length)"); std::vector program_log(log_length); glGetProgramInfoLog(program, log_length, NULL, program_log.data()); checkError("glGetProgramInfoLog(program, log_length, NULL, " "program_log.data())"); std::cerr << std::string(program_log.cbegin(), program_log.cend()) << std::endl; } return program; }; vertex_shader = create_shader("./conway.vert.glsl", GL_VERTEX_SHADER); fragment_shader = create_shader("./conway.frag.glsl", GL_FRAGMENT_SHADER); gl_program = create_program({ vertex_shader, fragment_shader }); std::vector quad = // vertices , tex coords // x , y , u , v { -1.0f, -1.0f, 0.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 }; glGenBuffers(1, &vertex_buffer); checkError("glGenBuffers(1, &vertex_buffer)"); glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer); checkError("glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer)"); glBufferData(GL_ARRAY_BUFFER, quad.size() * sizeof(float), quad.data(), GL_STATIC_DRAW); checkError("glBufferData(GL_ARRAY_BUFFER, quad.size() * sizeof(float), " "quad.data(), GL_STATIC_DRAW)"); glBindBuffer(GL_ARRAY_BUFFER, 0); checkError("glBindBuffer(GL_ARRAY_BUFFER, 0)"); glGenVertexArrays(1, &vertex_array); checkError("glGenVertexArrays(1, &vertex_array)"); glBindVertexArray(vertex_array); checkError("glBindVertexArray(vertex_array)"); glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer); checkError("glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer)"); glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (GLvoid*)(NULL)); checkError("glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, " "4*sizeof(float), (GLvoid *)(NULL))"); glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (GLvoid*)(0 + 2 * sizeof(float))); checkError("glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, " "4*sizeof(float), (GLvoid *)(0 + 2 * sizeof(float)))"); glEnableVertexAttribArray(0); checkError("glEnableVertexAttribArray(0)"); glEnableVertexAttribArray(1); checkError("glEnableVertexAttribArray(1)"); glBindVertexArray(0); checkError("glBindVertexArray(0)"); std::vector texels; std::generate_n( std::back_inserter(texels), getSize().x * getSize().y, [prng = std::ranlux48{ std::random_device{}() }, dist = std::uniform_int_distribution{ 0, 1 }]() mutable { return static_cast(dist(prng)); }); glUseProgram(gl_program); checkError(""); for (auto image : { &gl_images.front, &gl_images.back }) { glGenTextures(1, image); checkError("glGenTextures(1, image);"); glActiveTexture(GL_TEXTURE0); checkError("glActiveTexture(GL_TEXTURE0);"); glBindTexture(GL_TEXTURE_2D, *image); checkError("glBindTexture(GL_TEXTURE_2D, *image);"); glTexImage2D(GL_TEXTURE_2D, 0, GL_R8UI, getSize().x, getSize().y, 0, GL_RED_INTEGER, GL_UNSIGNED_BYTE, texels.data()); checkError( "glTexImage2D(GL_TEXTURE_2D, 0, GL_R8UI, getSize().x, getSize().y, " "0, GL_RED_INTEGER, GL_UNSIGNED_BYTE, texels.data())"); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); checkError( "glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);"); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); checkError( "glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);"); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); checkError("glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, " "GL_NEAREST);"); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); checkError("glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, " "GL_NEAREST);"); glBindTexture(GL_TEXTURE_2D, 0); checkError("glBindTexture(GL_TEXTURE_2D, 0);"); } glUseProgram(0); checkError("glUseProgram(0);"); glViewport(0, 0, getSize().x, getSize().y); checkError("glViewport(0, 0, getSize().x, getSize().y)"); glDisable(GL_DEPTH_TEST); checkError("glDisable(GL_DEPTH_TEST)"); } void Conway::initializeCL() { device = opencl_context.getInfo().at(0); queue = cl::CommandQueue{ opencl_context, device }; // Compile kernel const char* kernel_location = "./conway.cl"; std::ifstream kernel_stream{ kernel_location }; if (!kernel_stream.is_open()) throw std::runtime_error{ std::string{ "Cannot open kernel source: " } + kernel_location }; cl_program = cl::Program{ opencl_context, std::string{ std::istreambuf_iterator{ kernel_stream }, std::istreambuf_iterator{} } }; cl_program.build(device); kernel = cl::Kernel{ cl_program, "conway" }; // Translate OpenGL object handles into OpenCL handles cl_images.front = cl::ImageGL{ opencl_context, CL_MEM_READ_WRITE, GL_TEXTURE_2D, 0, gl_images.front }; cl_images.back = cl::ImageGL{ opencl_context, CL_MEM_READ_WRITE, GL_TEXTURE_2D, 0, gl_images.back }; // Translate interop_resources = cl::vector{ cl_images.front, cl_images.back }; } void Conway::updateScene() { if (animating) { auto conway = cl::KernelFunctor{ cl_program, "conway" }; cl::Event acquire, release; queue.enqueueAcquireGLObjects(&interop_resources, nullptr, &acquire); conway( cl::EnqueueArgs{ queue, cl::NDRange{ getSize().x, getSize().y } }, cl_images.front, cl_images.back, cl_float2{ 1.f / getSize().x, 1.f / getSize().y }); queue.enqueueReleaseGLObjects(&interop_resources, nullptr, &release); // Wait for all OpenCL commands to finish if (!cl_khr_gl_event_supported) cl::finish(); else release.wait(); // Swap front and back buffer handles std::swap(cl_images.front, cl_images.back); std::swap(gl_images.front, gl_images.back); } } void Conway::render() { glClear(GL_COLOR_BUFFER_BIT); glUseProgram(gl_program); glBindVertexArray(vertex_array); glBindTexture(GL_TEXTURE_2D, gl_images.front); glDrawArrays(GL_TRIANGLE_STRIP, 0, static_cast(4)); glBindTexture(GL_TEXTURE_2D, 0); glBindVertexArray(0); glUseProgram(0); // Wait for all drawing commands to finish if (!cl_khr_gl_event_supported) glFinish(); else glFlush(); } void Conway::event(const sf::Event& event) { switch (event.type) { case sf::Event::Closed: close(); break; case sf::Event::KeyPressed: if (event.key.code == sf::Keyboard::Key::Space) { animating = !animating; } break; } } int main(int argc, char* argv[]) { try { // Parse command-line options auto opts = cl::sdk::parse_cli(argc, argv); const auto& dev_opts = std::get<1>(opts).triplet; const auto& win_opts = std::get<2>(opts); Conway window{ win_opts.width, win_opts.height, win_opts.fullscreen, dev_opts.plat_index, dev_opts.dev_index, dev_opts.dev_type }; window.run(); } catch (cl::util::Error& e) { std::cerr << "OpenCL Utils error: " << e.what() << std::endl; std::exit(e.err()); } catch (cl::BuildError& e) { std::cerr << "OpenCL runtime error: " << e.what() << std::endl; for (auto& build_log : e.getBuildLog()) { std::cerr << "\tBuild log for device: " << build_log.first.getInfo() << "\n" << std::endl; std::cerr << build_log.second << "\n" << std::endl; } std::exit(e.err()); } catch (cl::Error& e) { std::cerr << "OpenCL rutnime error: " << e.what() << std::endl; std::exit(e.err()); } catch (std::exception& e) { std::cerr << "Error: " << e.what() << std::endl; std::exit(EXIT_FAILURE); } return 0; }