From a4fbbe80a580394e856898e7339a7944fab20571 Mon Sep 17 00:00:00 2001 From: Steven Johnson Date: Fri, 8 Jan 2021 17:21:29 -0800 Subject: [PATCH 01/12] Remove OpenGL support (part 1) Fixes #5475 This removes the OpenGL backend (but *not* the OpenGLCompute backend) from public use: - Remove Target::OpenGL - remove DeviceAPI::GLSL - remove Func::glsl() and Func::shader() - remove all OpenGL-specific apps and tests - remove HalideRuntimeOpenGL.h - remove some internal code that is OpenGL-only Note that there is still internal code that needs trimming; since the OpenGLCompute backend uses some of the same code, and some of the same build deps, and some of the same runtime shared-library loading, I tried to err on the side of leaving code/buildrules/etc in place for now, with the plan to clean that up in subsequent PRs. Note also that feature Target::EGL is still present, as I believe it is still useful in conjunction with OpenGLCompute. --- .github/workflows/presubmit.yml | 1 - .github/workflows/test.yml | 18 +- Makefile | 30 - README.md | 137 +- README_cmake.md | 4 +- apps/CMakeLists.txt | 3 - apps/HelloAndroidGL/AndroidManifest.xml | 21 - apps/HelloAndroidGL/ant.properties | 17 - apps/HelloAndroidGL/build.sh | 12 - apps/HelloAndroidGL/build.xml | 92 - apps/HelloAndroidGL/jni/Android.mk | 15 - apps/HelloAndroidGL/jni/Application.mk | 3 - .../jni/android_halide_gl_native.cpp | 51 - apps/HelloAndroidGL/jni/halide_gl_filter.cpp | 40 - apps/HelloAndroidGL/project.properties | 14 - .../res/drawable-hdpi/ic_launcher.png | Bin 9397 -> 0 bytes .../res/drawable-ldpi/ic_launcher.png | Bin 2729 -> 0 bytes .../res/drawable-mdpi/ic_launcher.png | Bin 5237 -> 0 bytes .../res/drawable-xhdpi/ic_launcher.png | Bin 14383 -> 0 bytes apps/HelloAndroidGL/res/layout/main.xml | 15 - apps/HelloAndroidGL/res/values/strings.xml | 4 - .../hellohalidegl/HelloHalideGL.java | 208 -- apps/bgu/Makefile | 2 +- apps/glsl/CMakeLists.txt | 41 - apps/glsl/Makefile | 36 - apps/glsl/halide_blur_glsl_generator.cpp | 33 - apps/glsl/halide_ycc_glsl_generator.cpp | 39 - apps/glsl/opengl_test.cpp | 58 - apps/harris/Makefile | 2 +- apps/hist/Makefile | 2 +- apps/iir_blur/Makefile | 2 +- apps/interpolate/Makefile | 2 +- apps/lens_blur/Makefile | 2 +- apps/local_laplacian/Makefile | 4 +- apps/max_filter/Makefile | 2 +- apps/nl_means/Makefile | 2 +- apps/opengl_demo/Makefile | 102 - apps/opengl_demo/README.md | 104 - apps/opengl_demo/glfw_helpers.cpp | 63 - apps/opengl_demo/glfw_helpers.h | 15 - apps/opengl_demo/image.png | Bin 166992 -> 0 bytes apps/opengl_demo/layout.cpp | 59 - apps/opengl_demo/layout.h | 28 - apps/opengl_demo/main.cpp | 172 -- apps/opengl_demo/opengl_helpers.cpp | 67 - apps/opengl_demo/opengl_helpers.h | 20 - apps/opengl_demo/png_helpers.cpp | 73 - apps/opengl_demo/png_helpers.h | 15 - apps/opengl_demo/sample_filter_generator.cpp | 24 - apps/opengl_demo/timer.cpp | 24 - apps/opengl_demo/timer.h | 16 - apps/resnet_50/Makefile | 4 +- apps/stencil_chain/Makefile | 4 +- apps/support/Makefile.inc | 12 - apps/unsharp/Makefile | 2 +- cmake/HalideGeneratorHelpers.cmake | 3 +- dependencies/CMakeLists.txt | 3 +- python_bindings/correctness/target.py | 4 +- python_bindings/src/PyEnums.cpp | 2 - python_bindings/src/PyFunc.cpp | 4 - src/CMakeLists.txt | 9 - src/CodeGen_C.cpp | 4 - src/CodeGen_GPU_Host.cpp | 91 +- src/CodeGen_Internal.cpp | 2 - src/CodeGen_LLVM.cpp | 1 - src/CodeGen_OpenGLCompute_Dev.cpp | 1 - src/CodeGen_OpenGL_Dev.cpp | 1 - src/DeviceAPI.h | 2 - src/DeviceInterface.cpp | 7 - src/Func.cpp | 29 - src/Func.h | 10 - src/FuseGPUThreadLoops.cpp | 4 - src/Generator.h | 2 - src/IRPrinter.cpp | 3 - src/InjectOpenGLIntrinsics.cpp | 111 - src/InjectOpenGLIntrinsics.h | 22 - src/JITModule.cpp | 21 - src/LICM.cpp | 3 - src/LLVM_Runtime_Linker.cpp | 16 - src/Lower.cpp | 25 +- src/Module.cpp | 4 - src/PartitionLoops.cpp | 11 - src/Pipeline.cpp | 4 - src/StorageFlattening.cpp | 6 +- src/Target.cpp | 9 +- src/Target.h | 1 - src/VaryingAttributes.cpp | 1389 ----------- src/VaryingAttributes.h | 33 - src/runtime/CMakeLists.txt | 1 - src/runtime/HalideRuntimeOpenGL.h | 105 - src/runtime/opengl.cpp | 2101 ----------------- src/runtime/runtime_api.cpp | 21 +- test/correctness/gpu_multi_device.cpp | 10 - test/correctness/plain_c_includes.c | 1 - test/correctness/target.cpp | 2 +- test/opengl/CMakeLists.txt | 28 - test/opengl/conv_select.cpp | 50 - test/opengl/copy_pixels.cpp | 33 - test/opengl/copy_to_device.cpp | 38 - test/opengl/copy_to_host.cpp | 39 - test/opengl/float_texture.cpp | 37 - test/opengl/inline_reduction.cpp | 27 - test/opengl/internal.cpp | 10 - test/opengl/lut.cpp | 76 - test/opengl/multiple_stages.cpp | 44 - test/opengl/produce.cpp | 70 - test/opengl/rewrap_texture.cpp | 67 - test/opengl/save_state.cpp | 314 --- test/opengl/select.cpp | 214 -- test/opengl/set_pixels.cpp | 28 - test/opengl/shifted_domains.cpp | 66 - test/opengl/special_funcs.cpp | 150 -- test/opengl/sum_reduction.cpp | 46 - test/opengl/sumcolor_reduction.cpp | 40 - test/opengl/testing.h | 86 - test/opengl/tuples.cpp | 40 - test/opengl/vagrant/.gitignore | 1 - test/opengl/vagrant/README.md | 136 -- test/opengl/vagrant/Vagrantfile | 118 - test/opengl/vagrant/build_tests.sh | 6 - test/opengl/vagrant/provision/etc/environment | 7 - .../vagrant/provision/etc/init/xdummy.conf | 7 - .../etc/systemd/system/xdummy.service | 6 - .../usr/share/X11/xorg.conf.d/xdummy.conf | 137 -- test/opengl/varying.cpp | 218 -- 125 files changed, 61 insertions(+), 7872 deletions(-) delete mode 100644 apps/HelloAndroidGL/AndroidManifest.xml delete mode 100644 apps/HelloAndroidGL/ant.properties delete mode 100755 apps/HelloAndroidGL/build.sh delete mode 100644 apps/HelloAndroidGL/build.xml delete mode 100644 apps/HelloAndroidGL/jni/Android.mk delete mode 100644 apps/HelloAndroidGL/jni/Application.mk delete mode 100644 apps/HelloAndroidGL/jni/android_halide_gl_native.cpp delete mode 100644 apps/HelloAndroidGL/jni/halide_gl_filter.cpp delete mode 100644 apps/HelloAndroidGL/project.properties delete mode 100644 apps/HelloAndroidGL/res/drawable-hdpi/ic_launcher.png delete mode 100644 apps/HelloAndroidGL/res/drawable-ldpi/ic_launcher.png delete mode 100644 apps/HelloAndroidGL/res/drawable-mdpi/ic_launcher.png delete mode 100644 apps/HelloAndroidGL/res/drawable-xhdpi/ic_launcher.png delete mode 100644 apps/HelloAndroidGL/res/layout/main.xml delete mode 100644 apps/HelloAndroidGL/res/values/strings.xml delete mode 100644 apps/HelloAndroidGL/src/org/halide_lang/hellohalidegl/HelloHalideGL.java delete mode 100644 apps/glsl/CMakeLists.txt delete mode 100644 apps/glsl/Makefile delete mode 100644 apps/glsl/halide_blur_glsl_generator.cpp delete mode 100644 apps/glsl/halide_ycc_glsl_generator.cpp delete mode 100644 apps/glsl/opengl_test.cpp delete mode 100644 apps/opengl_demo/Makefile delete mode 100644 apps/opengl_demo/README.md delete mode 100644 apps/opengl_demo/glfw_helpers.cpp delete mode 100644 apps/opengl_demo/glfw_helpers.h delete mode 100644 apps/opengl_demo/image.png delete mode 100644 apps/opengl_demo/layout.cpp delete mode 100644 apps/opengl_demo/layout.h delete mode 100644 apps/opengl_demo/main.cpp delete mode 100644 apps/opengl_demo/opengl_helpers.cpp delete mode 100644 apps/opengl_demo/opengl_helpers.h delete mode 100644 apps/opengl_demo/png_helpers.cpp delete mode 100644 apps/opengl_demo/png_helpers.h delete mode 100644 apps/opengl_demo/sample_filter_generator.cpp delete mode 100644 apps/opengl_demo/timer.cpp delete mode 100644 apps/opengl_demo/timer.h delete mode 100644 src/InjectOpenGLIntrinsics.cpp delete mode 100644 src/InjectOpenGLIntrinsics.h delete mode 100644 src/VaryingAttributes.cpp delete mode 100644 src/VaryingAttributes.h delete mode 100644 src/runtime/HalideRuntimeOpenGL.h delete mode 100644 src/runtime/opengl.cpp delete mode 100644 test/opengl/CMakeLists.txt delete mode 100644 test/opengl/conv_select.cpp delete mode 100644 test/opengl/copy_pixels.cpp delete mode 100644 test/opengl/copy_to_device.cpp delete mode 100644 test/opengl/copy_to_host.cpp delete mode 100644 test/opengl/float_texture.cpp delete mode 100644 test/opengl/inline_reduction.cpp delete mode 100644 test/opengl/internal.cpp delete mode 100644 test/opengl/lut.cpp delete mode 100644 test/opengl/multiple_stages.cpp delete mode 100644 test/opengl/produce.cpp delete mode 100644 test/opengl/rewrap_texture.cpp delete mode 100644 test/opengl/save_state.cpp delete mode 100644 test/opengl/select.cpp delete mode 100644 test/opengl/set_pixels.cpp delete mode 100644 test/opengl/shifted_domains.cpp delete mode 100644 test/opengl/special_funcs.cpp delete mode 100644 test/opengl/sum_reduction.cpp delete mode 100644 test/opengl/sumcolor_reduction.cpp delete mode 100644 test/opengl/testing.h delete mode 100644 test/opengl/tuples.cpp delete mode 100644 test/opengl/vagrant/.gitignore delete mode 100644 test/opengl/vagrant/README.md delete mode 100644 test/opengl/vagrant/Vagrantfile delete mode 100755 test/opengl/vagrant/build_tests.sh delete mode 100644 test/opengl/vagrant/provision/etc/environment delete mode 100644 test/opengl/vagrant/provision/etc/init/xdummy.conf delete mode 100644 test/opengl/vagrant/provision/etc/systemd/system/xdummy.service delete mode 100644 test/opengl/vagrant/provision/usr/share/X11/xorg.conf.d/xdummy.conf delete mode 100644 test/opengl/varying.cpp diff --git a/.github/workflows/presubmit.yml b/.github/workflows/presubmit.yml index e7d50e4d2891..ec763c9b0935 100644 --- a/.github/workflows/presubmit.yml +++ b/.github/workflows/presubmit.yml @@ -49,6 +49,5 @@ jobs: (cd test/error && comm -23 <(ls *.{c,cpp} | sort) <(grep -P '^\s*#?\s*[A-Za-z0-9_.]+$' CMakeLists.txt | tr -d '# ' | sort) | tee missing_files && [ ! -s missing_files ]) (cd test/generator && comm -23 <(ls *.{c,cpp} | sort) <(grep -P '^\s*#?\s*[A-Za-z0-9_.]+$' CMakeLists.txt | tr -d '# ' | sort) | tee missing_files && [ ! -s missing_files ]) (cd test/failing_with_issue && comm -23 <(ls *.{c,cpp} | sort) <(grep -P '^\s*#?\s*[A-Za-z0-9_.]+$' CMakeLists.txt | tr -d '# ' | sort) | tee missing_files && [ ! -s missing_files ]) - (cd test/opengl && comm -23 <(ls *.{c,cpp} | sort) <(grep -P '^\s*#?\s*[A-Za-z0-9_.]+$' CMakeLists.txt | tr -d '# ' | sort) | tee missing_files && [ ! -s missing_files ]) (cd test/performance && comm -23 <(ls *.{c,cpp} | sort) <(grep -P '^\s*#?\s*[A-Za-z0-9_.]+$' CMakeLists.txt | tr -d '# ' | sort) | tee missing_files && [ ! -s missing_files ]) (cd test/warning && comm -23 <(ls *.{c,cpp} | sort) <(grep -P '^\s*#?\s*[A-Za-z0-9_.]+$' CMakeLists.txt | tr -d '# ' | sort) | tee missing_files && [ ! -s missing_files ]) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9e288dd687d9..f7e1dbea8c97 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -148,12 +148,6 @@ jobs: libpng-dev \ ninja-build - # TODO(srj): OpenGL is only needed to build the opengl tests (which we don't even run)... - sudo apt-get install \ - freeglut3-dev \ - libglu1-mesa-dev \ - mesa-common-dev - - name: Configure MacOS Host if: startsWith(matrix.host_os, 'macos') shell: bash @@ -193,12 +187,6 @@ jobs: libjpeg-dev:i386 \ libpng-dev:i386 \ - # TODO(srj): OpenGL is only needed to build the opengl tests (which we don't even run)... - sudo apt-get install \ - freeglut3-dev:i386 \ - libglu1-mesa-dev:i386 \ - mesa-common-dev:i386 - - name: Configure Arm32 Crosscompilation if: matrix.target_os == 'linux' && matrix.target_arch == 'arm' && matrix.target_bits == 32 shell: bash @@ -449,9 +437,8 @@ jobs: TEST_GROUPS_SERIAL="tutorial" # performance is never going to be reliable on VMs. - # opengl won't work on the buildbots. # auto_schedule is just flaky. - TEST_GROUPS_BROKEN="performance opengl auto_schedule" + TEST_GROUPS_BROKEN="performance auto_schedule" if [[ ${{matrix.target_bits}} == 32 ]]; then # TODO: Skip testing apps on 32-bit systems for now; @@ -487,9 +474,8 @@ jobs: TEST_GROUPS_SERIAL="tutorial" # performance is never going to be reliable on VMs. - # opengl won't work on the buildbots. # auto_schedule is just flaky. - TEST_GROUPS_BROKEN="performance|opengl|auto_schedule" + TEST_GROUPS_BROKEN="performance|auto_schedule" export TEST_TMPDIR="${HALIDE_TEMP_DIR}" cd ${HALIDE_BUILD_DIR} diff --git a/Makefile b/Makefile index 8ad0a3970a0a..921f8e388e3b 100644 --- a/Makefile +++ b/Makefile @@ -466,7 +466,6 @@ SOURCE_FILES = \ ImageParam.cpp \ InferArguments.cpp \ InjectHostDevBufferCopies.cpp \ - InjectOpenGLIntrinsics.cpp \ Inline.cpp \ InlineReductions.cpp \ IntegerDivisionTable.cpp \ @@ -560,7 +559,6 @@ SOURCE_FILES = \ UnsafePromises.cpp \ Util.cpp \ Var.cpp \ - VaryingAttributes.cpp \ VectorizeLoops.cpp \ WasmExecutor.cpp \ WrapCalls.cpp @@ -645,7 +643,6 @@ HEADER_FILES = \ ImageParam.h \ InferArguments.h \ InjectHostDevBufferCopies.h \ - InjectOpenGLIntrinsics.h \ Inline.h \ InlineReductions.h \ IntegerDivisionTable.h \ @@ -728,7 +725,6 @@ HEADER_FILES = \ UnsafePromises.h \ Util.h \ Var.h \ - VaryingAttributes.h \ VectorizeLoops.h \ WrapCalls.h @@ -779,7 +775,6 @@ RUNTIME_CPP_COMPONENTS = \ msan \ msan_stubs \ opencl \ - opengl \ openglcompute \ opengl_egl_context \ opengl_glx_context \ @@ -851,7 +846,6 @@ RUNTIME_EXPORTED_INCLUDES = $(INCLUDE_DIR)/HalideRuntime.h \ $(INCLUDE_DIR)/HalideRuntimeHexagonDma.h \ $(INCLUDE_DIR)/HalideRuntimeHexagonHost.h \ $(INCLUDE_DIR)/HalideRuntimeOpenCL.h \ - $(INCLUDE_DIR)/HalideRuntimeOpenGL.h \ $(INCLUDE_DIR)/HalideRuntimeOpenGLCompute.h \ $(INCLUDE_DIR)/HalideRuntimeMetal.h \ $(INCLUDE_DIR)/HalideRuntimeQurt.h \ @@ -1110,14 +1104,11 @@ CORRECTNESS_TESTS = $(shell ls $(ROOT_DIR)/test/correctness/*.cpp) $(shell ls $( PERFORMANCE_TESTS = $(shell ls $(ROOT_DIR)/test/performance/*.cpp) ERROR_TESTS = $(shell ls $(ROOT_DIR)/test/error/*.cpp) WARNING_TESTS = $(shell ls $(ROOT_DIR)/test/warning/*.cpp) -OPENGL_TESTS := $(shell ls $(ROOT_DIR)/test/opengl/*.cpp) GENERATOR_EXTERNAL_TESTS := $(shell ls $(ROOT_DIR)/test/generator/*test.cpp) GENERATOR_EXTERNAL_TEST_GENERATOR := $(shell ls $(ROOT_DIR)/test/generator/*_generator.cpp) TUTORIALS = $(filter-out %_generate.cpp, $(shell ls $(ROOT_DIR)/tutorial/*.cpp)) AUTO_SCHEDULE_TESTS = $(shell ls $(ROOT_DIR)/test/auto_schedule/*.cpp) --include $(OPENGL_TESTS:$(ROOT_DIR)/test/opengl/%.cpp=$(BUILD_DIR)/test_opengl_%.d) - test_correctness: $(CORRECTNESS_TESTS:$(ROOT_DIR)/test/correctness/%.cpp=quiet_correctness_%) $(CORRECTNESS_TESTS:$(ROOT_DIR)/test/correctness/%.c=quiet_correctness_%) test_performance: $(PERFORMANCE_TESTS:$(ROOT_DIR)/test/performance/%.cpp=performance_%) test_error: $(ERROR_TESTS:$(ROOT_DIR)/test/error/%.cpp=error_%) @@ -1125,7 +1116,6 @@ test_warning: $(WARNING_TESTS:$(ROOT_DIR)/test/warning/%.cpp=warning_%) test_tutorial: $(TUTORIALS:$(ROOT_DIR)/tutorial/%.cpp=tutorial_%) test_valgrind: $(CORRECTNESS_TESTS:$(ROOT_DIR)/test/correctness/%.cpp=valgrind_%) test_avx512: $(CORRECTNESS_TESTS:$(ROOT_DIR)/test/correctness/%.cpp=avx512_%) -test_opengl: $(OPENGL_TESTS:$(ROOT_DIR)/test/opengl/%.cpp=opengl_%) test_auto_schedule: test_mullapudi2016 test_li2018 test_adams2019 .PHONY: test_correctness_multi_gpu @@ -1230,7 +1220,6 @@ ALL_TESTS = test_internal test_correctness test_error test_tutorial test_warning # For generator tests they time the compile time only. The times are recorded in CSV files. time_compilation_correctness: init_time_compilation_correctness $(CORRECTNESS_TESTS:$(ROOT_DIR)/test/correctness/%.cpp=time_compilation_test_%) time_compilation_performance: init_time_compilation_performance $(PERFORMANCE_TESTS:$(ROOT_DIR)/test/performance/%.cpp=time_compilation_performance_%) -time_compilation_opengl: init_time_compilation_opengl $(OPENGL_TESTS:$(ROOT_DIR)/test/opengl/%.cpp=time_compilation_opengl_%) time_compilation_generator: init_time_compilation_generator $(GENERATOR_TESTS:$(ROOT_DIR)/test/generator/%_aottest.cpp=time_compilation_generator_%) init_time_compilation_%: @@ -1250,14 +1239,6 @@ build_tests: $(CORRECTNESS_TESTS:$(ROOT_DIR)/test/correctness/%.cpp=$(BIN_DIR)/c $(GENERATOR_EXTERNAL_TESTS:$(ROOT_DIR)/test/generator/%_jittest.cpp=$(BIN_DIR)/generator_jit_%) \ $(AUTO_SCHEDULE_TESTS:$(ROOT_DIR)/test/auto_schedule/%.cpp=$(BIN_DIR)/auto_schedule_%) -# OpenGL doesn't build on every host platform we support (eg. ARM). -.PHONY: build_opengl_tests -build_opengl_tests: $(OPENGL_TESTS:$(ROOT_DIR)/test/opengl/%.cpp=$(BIN_DIR)/opengl_%) - -ifneq ($(WITH_OPENGL),) -build_tests: build_opengl_tests -endif - clean_generator: rm -rf $(BIN_DIR)/*.generator rm -rf $(BIN_DIR)/*/runtime.a @@ -1321,9 +1302,6 @@ $(BIN_DIR)/error_%: $(ROOT_DIR)/test/error/%.cpp $(BIN_DIR)/libHalide.$(SHARED_E $(BIN_DIR)/warning_%: $(ROOT_DIR)/test/warning/%.cpp $(BIN_DIR)/libHalide.$(SHARED_EXT) $(INCLUDE_DIR)/Halide.h $(CXX) $(TEST_CXX_FLAGS) $(OPTIMIZE_FOR_BUILD_TIME) $< -I$(INCLUDE_DIR) $(TEST_LD_FLAGS) -o $@ -$(BIN_DIR)/opengl_%: $(ROOT_DIR)/test/opengl/%.cpp $(BIN_DIR)/libHalide.$(SHARED_EXT) $(INCLUDE_DIR)/Halide.h $(INCLUDE_DIR)/HalideRuntime.h $(INCLUDE_DIR)/HalideRuntimeOpenGL.h - $(CXX) $(TEST_CXX_FLAGS) $(OPTIMIZE_FOR_BUILD_TIME) $< -I$(INCLUDE_DIR) -I$(SRC_DIR) $(TEST_LD_FLAGS) $(OPENGL_LD_FLAGS) -o $@ -MMD -MF $(BUILD_DIR)/test_opengl_$*.d - # Auto schedule tests that link against libHalide $(BIN_DIR)/auto_schedule_%: $(ROOT_DIR)/test/auto_schedule/%.cpp $(BIN_DIR)/libHalide.$(SHARED_EXT) $(INCLUDE_DIR)/Halide.h $(CXX) $(TEST_CXX_FLAGS) $(OPTIMIZE_FOR_BUILD_TIME) $< -I$(INCLUDE_DIR) $(TEST_LD_FLAGS) -o $@ @@ -1874,11 +1852,6 @@ warning_%: $(BIN_DIR)/warning_% cd $(TMP_DIR) ; $(CURDIR)/$< 2>&1 | egrep --q "^Warning" @-echo -opengl_%: $(BIN_DIR)/opengl_% - @-mkdir -p $(TMP_DIR) - cd $(TMP_DIR) ; $(CURDIR)/$< 2>&1 - @-echo - generator_jit_%: $(BIN_DIR)/generator_jit_% @-mkdir -p $(TMP_DIR) cd $(TMP_DIR) ; $(CURDIR)/$< @@ -1928,9 +1901,6 @@ time_compilation_test_%: $(BIN_DIR)/test_% time_compilation_performance_%: $(BIN_DIR)/performance_% $(TIME_COMPILATION) compile_times_performance.csv make -f $(THIS_MAKEFILE) $(@:time_compilation_performance_%=performance_%) -time_compilation_opengl_%: $(BIN_DIR)/opengl_% - $(TIME_COMPILATION) compile_times_opengl.csv make -f $(THIS_MAKEFILE) $(@:time_compilation_opengl_%=opengl_%) - time_compilation_generator_%: $(BIN_DIR)/%.generator $(TIME_COMPILATION) compile_times_generator.csv make -f $(THIS_MAKEFILE) $(@:time_compilation_generator_%=$(FILTERS_DIR)/%.a) diff --git a/README.md b/README.md index 6fe672968347..370da822df33 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ currently targets: - CPU architectures: X86, ARM, MIPS, Hexagon, PowerPC - Operating systems: Linux, Windows, Mac OS X, Android, iOS, Qualcomm QuRT -- GPU Compute APIs: CUDA, OpenCL, OpenGL, OpenGL Compute Shaders, Apple Metal, +- GPU Compute APIs: CUDA, OpenCL, OpenGL Compute Shaders, Apple Metal, Microsoft Direct X 12 Rather than being a standalone programming language, Halide is embedded in C++. @@ -336,140 +336,7 @@ an older XCode which does not default to libc++. # Halide OpenGL/GLSL backend -Halide's OpenGL backend offloads image processing operations to the GPU by -generating GLSL-based fragment shaders. - -Compared to other GPU-based processing options such as CUDA and OpenCL, OpenGL -has two main advantages: it is available on basically every desktop computer and -mobile device, and it is generally well supported across different hardware -vendors. - -The main disadvantage of OpenGL as an image processing framework is that the -computational capabilities of fragment shaders are quite restricted. In general, -the processing model provided by OpenGL is most suitable for filters where each -output pixel can be expressed as a simple function of the input pixels. This -covers a wide range of interesting operations like point-wise filters and -convolutions; but a few common image processing operations such as histograms or -recursive filters are notoriously hard to express in GLSL. - -#### Writing OpenGL-Based Filters - -To enable code generation for OpenGL, include `opengl` in the target specifier -passed to Halide. Since OpenGL shaders are limited in their computational power, -you must also specify a CPU target for those parts of the filter that cannot or -should not be computed on the GPU. Examples of valid target specifiers are - -``` -host-opengl -x86-opengl-debug -``` - -Adding `debug`, as in the second example, adds additional logging output and is -highly recommended during development. - -By default, filters compiled for OpenGL targets run completely on the CPU. -Execution on the GPU must be enabled for individual Funcs by appropriate -scheduling calls. - -GLSL fragment shaders implicitly iterate over two spatial dimensions x,y and the -color channel. Due to the way color channels handled in GLSL, only filters for -which the color index is a compile-time constant can be scheduled. The main -consequence is that the range of color variables must be explicitly specified -for both input and output buffers before scheduling: - -``` -ImageParam input; -Func f; -Var x, y, c; -f(x, y, c) = ...; - -input.set_bounds(2, 0, 3); // specify color range for input -f.bound(c, 0, 3); // and output -f.glsl(x, y, c); -``` - -#### JIT Compilation - -For JIT compilation Halide attempts to load the system libraries for opengl and -creates a new context to use for each module. Windows is not yet supported. - -Examples for JIT execution of OpenGL-based filters can be found in test/opengl. - -#### AOT Compilation - -When AOT (ahead-of-time) compilation is used, Halide generates OpenGL-enabled -object files that can be linked to and called from a host application. In -general, this is fairly straightforward, but a few things must be taken care of. - -On Linux, OS X, and Android, Halide creates its own OpenGL context unless the -current thread already has an active context. On other platforms you have to -link implementations of the following two functions with your Halide code: - -``` -extern "C" int halide_opengl_create_context(void *) { - return 0; // if successful -} - -extern "C" void *halide_opengl_get_proc_addr(void *, const char *name) { - ... -} -``` - -Halide allocates and deletes textures as necessary. Applications may manage the -textures by hand by setting the `halide_buffer_t::device` field; this is most -useful for reusing image data that is already stored in textures. Some -rudimentary checks are performed to ensure that externally allocated textures -have the correct format, but in general that's the responsibility of the -application. - -It is possible to let render directly to the current framebuffer; to do this, -set the `dev` field of the output buffer to the value returned by -`halide_opengl_output_client_bound`. The example in apps/HelloAndroidGL -demonstrates this technique. - -Some operating systems can delete the OpenGL context of suspended applications. -If this happens, Halide needs to re-initialize itself with the new context after -the application resumes. Call `halide_opengl_context_lost` to reset Halide's -OpenGL state after this has happened. - -#### Limitations - -The current implementation of the OpenGL backend targets the common subset of -OpenGL 2.0 and OpenGL ES 2.0 which is widely available on both mobile devices -and traditional computers. As a consequence, only a subset of the Halide -language can be scheduled to run using OpenGL. Some important limitations are: - -- Reductions cannot be implemented in GLSL and must be run on the CPU. - -- OpenGL ES 2.0 only supports uint8 buffers. - - Support for floating point texture is available, but requires OpenGL (ES) 3.0 - or the texture_float extension, which may not work on all mobile devices. - -- OpenGL ES 2.0 has very limited support for integer arithmetic. For maximum - compatibility, consider doing all computations using floating point, even when - using integer textures. - -- Only 2D images with 3 or 4 color channels can be scheduled. Images with one or - two channels require OpenGL (ES) 3.0 or the texture_rg extension. - -- Not all builtin functions provided by Halide are currently supported, for - example `fast_log`, `fast_exp`, `fast_pow`, `reinterpret`, bit operations, - `random_float`, `random_int` cannot be used in GLSL code. - -The maximum texture size in OpenGL is `GL_MAX_TEXTURE_SIZE`, which is often -smaller than the image of interest; on mobile devices, for example, -`GL_MAX_TEXTURE_SIZE` is commonly 2048. Tiling must be used to process larger -images. - -Planned features: - -- Support for half-float textures and arithmetic - -- Support for integer textures and arithmetic - -(Note that OpenGL Compute Shaders are supported with a separate OpenGLCompute -backend.) +TODO: update this for OpenGLCompute, which is staying # Halide for Hexagon HVX diff --git a/README_cmake.md b/README_cmake.md index d078c41775b0..d7842b49f977 100644 --- a/README_cmake.md +++ b/README_cmake.md @@ -392,7 +392,6 @@ apply when `WITH_TESTS=ON`: | `WITH_TEST_ERROR` | `ON` | enable the expected-error tests | | `WITH_TEST_WARNING` | `ON` | enable the expected-warning tests | | `WITH_TEST_PERFORMANCE` | `ON` | enable performance testing | -| `WITH_TEST_OPENGL` | `OFF` | enable the OpenGL tests | | `WITH_TEST_GENERATOR` | `ON` | enable the AOT generator tests | The following options enable/disable various LLVM backends (they correspond to @@ -416,7 +415,6 @@ The following options enable/disable various Halide-specific backends: | Option | Default | Description | | --------------------- | ------- | -------------------------------------- | | `TARGET_OPENCL` | `ON` | Enable the OpenCL-C backend | -| `TARGET_OPENGL` | `ON` | Enable the OpenGL/GLSL backend | | `TARGET_METAL` | `ON` | Enable the Metal backend | | `TARGET_D3D12COMPUTE` | `ON` | Enable the Direct3D 12 Compute backend | @@ -466,6 +464,8 @@ If the CMake version is lower than 3.18, the deprecated [`FindCUDA`][findcuda] module will be used instead. It reads the variable `CUDA_TOOLKIT_ROOT_DIR` instead of `CUDAToolkit_ROOT` above. +TODO: update this section for OpenGLCompute, which needs some (but maybe not all) of this. + When targeting OpenGL, the [`FindOpenGL`][findopengl] and [`FindX11`][findx11] modules will be used to link AOT generated binaries. These modules can be overridden by setting the following variables: diff --git a/apps/CMakeLists.txt b/apps/CMakeLists.txt index 7e0ed08e4763..3effa0221125 100644 --- a/apps/CMakeLists.txt +++ b/apps/CMakeLists.txt @@ -9,7 +9,6 @@ enable_testing() # add_subdirectory(HelloAndroid) # TODO(#5374): missing CMake build # add_subdirectory(HelloAndroidCamera2) # TODO(#5374): missing CMake build -# add_subdirectory(HelloAndroidGL) # TODO(#5374): missing CMake build # add_subdirectory(HelloMatlab) # TODO(#5374): missing CMake build # add_subdirectory(HelloPyTorch) # TODO(#5374): missing CMake build # add_subdirectory(HelloWasm) # TODO(#5374): missing CMake build @@ -24,7 +23,6 @@ add_subdirectory(conv_layer) add_subdirectory(cuda_mat_mul) add_subdirectory(depthwise_separable_conv) add_subdirectory(fft) -add_subdirectory(glsl) add_subdirectory(harris) # add_subdirectory(hexagon_benchmarks) # TODO(#5374): missing CMake build # add_subdirectory(hexagon_dma) # TODO(#5374): missing CMake build @@ -39,7 +37,6 @@ add_subdirectory(max_filter) add_subdirectory(nl_means) # add_subdirectory(nn_ops) # TODO(#5374): missing CMake build # add_subdirectory(onnx) # TODO(#5374): missing CMake build -# add_subdirectory(opengl_demo) # TODO(#5374): missing CMake build # add_subdirectory(openglcompute) # TODO(#5374): missing CMake build add_subdirectory(resize) # add_subdirectory(resnet_50) # TODO(#5374): missing CMake build diff --git a/apps/HelloAndroidGL/AndroidManifest.xml b/apps/HelloAndroidGL/AndroidManifest.xml deleted file mode 100644 index de292f319f7f..000000000000 --- a/apps/HelloAndroidGL/AndroidManifest.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/apps/HelloAndroidGL/ant.properties b/apps/HelloAndroidGL/ant.properties deleted file mode 100644 index b0971e891efd..000000000000 --- a/apps/HelloAndroidGL/ant.properties +++ /dev/null @@ -1,17 +0,0 @@ -# This file is used to override default values used by the Ant build system. -# -# This file must be checked into Version Control Systems, as it is -# integral to the build system of your project. - -# This file is only used by the Ant script. - -# You can use this to override default values such as -# 'source.dir' for the location of your java source folder and -# 'out.dir' for the location of your output folder. - -# You can also use it define how the release builds are signed by declaring -# the following properties: -# 'key.store' for the location of your keystore and -# 'key.alias' for the name of the key to use. -# The password will be asked during the build when you use the 'release' target. - diff --git a/apps/HelloAndroidGL/build.sh b/apps/HelloAndroidGL/build.sh deleted file mode 100755 index d9b1f395dc12..000000000000 --- a/apps/HelloAndroidGL/build.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/bash -set -e -android update project -p . --target android-17 -cd jni -c++ -std=c++11 halide_gl_filter.cpp -L ../../../bin -lHalide -I ../../../include -ldl -lpthread -lz -HL_TARGET=arm-32-android-opengl-debug DYLD_LIBRARY_PATH=../../../bin LD_LIBRARY_PATH=../../../bin ./a.out -cd .. -pwd -ndk-build -ant debug -adb install -r bin/HelloAndroidGL-debug.apk -adb logcat diff --git a/apps/HelloAndroidGL/build.xml b/apps/HelloAndroidGL/build.xml deleted file mode 100644 index 1e79c7ee52fa..000000000000 --- a/apps/HelloAndroidGL/build.xml +++ /dev/null @@ -1,92 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/apps/HelloAndroidGL/jni/Android.mk b/apps/HelloAndroidGL/jni/Android.mk deleted file mode 100644 index c30cec7bf54b..000000000000 --- a/apps/HelloAndroidGL/jni/Android.mk +++ /dev/null @@ -1,15 +0,0 @@ -LOCAL_PATH := $(call my-dir) - -include $(CLEAR_VARS) - -LOCAL_MODULE := android_halide_gl_native -LOCAL_ARM_MODE := arm -LOCAL_SRC_FILES := android_halide_gl_native.cpp -LOCAL_LDFLAGS := -Ljni -LOCAL_LDLIBS := -lm -llog -landroid -lEGL -lGLESv2 jni/halide_gl_filter.o -LOCAL_STATIC_LIBRARIES := android_native_app_glue -LOCAL_C_INCLUDES := $(LOCAL_PATH)/../../../include - -include $(BUILD_SHARED_LIBRARY) - -$(call import-module,android/native_app_glue) diff --git a/apps/HelloAndroidGL/jni/Application.mk b/apps/HelloAndroidGL/jni/Application.mk deleted file mode 100644 index 56005dabf161..000000000000 --- a/apps/HelloAndroidGL/jni/Application.mk +++ /dev/null @@ -1,3 +0,0 @@ -# The ARMv7 is significanly faster due to the use of the hardware FPU -APP_ABI := armeabi-v7a -APP_PLATFORM := android-17 diff --git a/apps/HelloAndroidGL/jni/android_halide_gl_native.cpp b/apps/HelloAndroidGL/jni/android_halide_gl_native.cpp deleted file mode 100644 index 20e3de6bfe56..000000000000 --- a/apps/HelloAndroidGL/jni/android_halide_gl_native.cpp +++ /dev/null @@ -1,51 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include - -#include "HalideBuffer.h" -#include "HalideRuntimeOpenGL.h" -#include "halide_gl_filter.h" - -#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, "halide_native", __VA_ARGS__) -#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, "halide_native", __VA_ARGS__) - -void *const user_context = NULL; - -extern "C" JNIEXPORT void JNICALL Java_org_halide_1lang_hellohalidegl_HalideGLView_processTextureHalide( - JNIEnv *env, jobject obj, jint dst, jint width, jint height) { - - auto dstBuf = Halide::Runtime::Buffer::make_interleaved(NULL, width, height, 4); - // If dst == 0, let Halide render directly to the current render target. - if (dst == 0) { - int result = halide_opengl_wrap_render_target(user_context, dstBuf); - if (result != 0) { - halide_error(user_context, "halide_opengl_wrap_render_target failed"); - } - } else { - int result = halide_opengl_wrap_texture(user_context, dstBuf, dst); - if (result != 0) { - halide_error(user_context, "halide_opengl_wrap_texture failed"); - } - } - - static float time = 0.0f; - if (int err = halide_gl_filter(time, dstBuf)) { - LOGD("Halide filter failed with error code %d\n", err); - } - time += 1.0f / 16.0f; - - uintptr_t detached = halide_opengl_detach_texture(user_context, dstBuf); - if (detached != dst) { - halide_error(user_context, "halide_opengl_detach_texture failed"); - } -} - -extern "C" JNIEXPORT void JNICALL Java_org_halide_1lang_hellohalidegl_HalideGLView_halideContextLost( - JNIEnv *env, jobject obj) { - - halide_opengl_context_lost(NULL); -} diff --git a/apps/HelloAndroidGL/jni/halide_gl_filter.cpp b/apps/HelloAndroidGL/jni/halide_gl_filter.cpp deleted file mode 100644 index 15e949312539..000000000000 --- a/apps/HelloAndroidGL/jni/halide_gl_filter.cpp +++ /dev/null @@ -1,40 +0,0 @@ -#include "Halide.h" - -using namespace Halide; - -int main(int argc, char **argv) { - Param time; - - const float pi = 3.1415926536; - - Var x, y, c; - Func result; - - Expr kx, ky; - Expr xx, yy; - kx = x / 150.0f; - ky = y / 150.0f; - - xx = kx + sin(time / 3.0f); - yy = ky + sin(time / 2.0f); - - Expr angle; - angle = 2 * pi * sin(time / 20.0f); - kx = kx * cos(angle) - ky * sin(angle); - ky = kx * sin(angle) + ky * cos(angle); - - Expr v = 0.0f; - v += sin((ky + time) / 2.0f); - v += sin((kx + ky + time) / 2.0f); - v += sin(sqrt(xx * xx + yy * yy + 1.0f) + time); - - result(x, y, c) = cast(selecy_by_index(c, {32, cos(pi * v), sin(pi * v)}) * 80 + (255 - 80)); - - result.output_buffer().set_stride(0, 4); - result.bound(c, 0, 4); - result.glsl(x, y, c); - - result.compile_to_file("halide_gl_filter", {time}, "halide_gl_filter"); - - return 0; -} diff --git a/apps/HelloAndroidGL/project.properties b/apps/HelloAndroidGL/project.properties deleted file mode 100644 index a3ee5ab64f5e..000000000000 --- a/apps/HelloAndroidGL/project.properties +++ /dev/null @@ -1,14 +0,0 @@ -# This file is automatically generated by Android Tools. -# Do not modify this file -- YOUR CHANGES WILL BE ERASED! -# -# This file must be checked in Version Control Systems. -# -# To customize properties used by the Ant build system edit -# "ant.properties", and override values to adapt the script to your -# project structure. -# -# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): -#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt - -# Project target. -target=android-17 diff --git a/apps/HelloAndroidGL/res/drawable-hdpi/ic_launcher.png b/apps/HelloAndroidGL/res/drawable-hdpi/ic_launcher.png deleted file mode 100644 index 96a442e5b8e9394ccf50bab9988cb2316026245d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9397 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 diff --git a/apps/HelloAndroidGL/res/drawable-mdpi/ic_launcher.png b/apps/HelloAndroidGL/res/drawable-mdpi/ic_launcher.png deleted file mode 100644 index 359047dfa4ed206e41e2354f9c6b307e713efe32..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5237 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 diff --git a/apps/HelloAndroidGL/res/drawable-xhdpi/ic_launcher.png b/apps/HelloAndroidGL/res/drawable-xhdpi/ic_launcher.png deleted file mode 100644 index 71c6d760f05183ef8a47c614d8d13380c8528499..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14383 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 diff --git a/apps/HelloAndroidGL/res/layout/main.xml b/apps/HelloAndroidGL/res/layout/main.xml deleted file mode 100644 index 5a8da6d73556..000000000000 --- a/apps/HelloAndroidGL/res/layout/main.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/apps/HelloAndroidGL/res/values/strings.xml b/apps/HelloAndroidGL/res/values/strings.xml deleted file mode 100644 index 2673566b97f0..000000000000 --- a/apps/HelloAndroidGL/res/values/strings.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - Halide GL Demo - diff --git a/apps/HelloAndroidGL/src/org/halide_lang/hellohalidegl/HelloHalideGL.java b/apps/HelloAndroidGL/src/org/halide_lang/hellohalidegl/HelloHalideGL.java deleted file mode 100644 index 78843d3d498f..000000000000 --- a/apps/HelloAndroidGL/src/org/halide_lang/hellohalidegl/HelloHalideGL.java +++ /dev/null @@ -1,208 +0,0 @@ -package org.halide_lang.hellohalidegl; - -import android.app.Activity; -import android.content.Context; -import android.os.Bundle; -import android.hardware.Camera; -import android.util.Log; -import android.widget.FrameLayout; -import android.view.SurfaceView; -import android.view.Surface; -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.opengl.GLSurfaceView; -import javax.microedition.khronos.egl.EGLConfig; -import javax.microedition.khronos.opengles.GL10; -import java.nio.ByteBuffer; -import java.nio.FloatBuffer; -import java.nio.ByteOrder; - -class HalideGLView extends GLSurfaceView { - static { - System.loadLibrary("android_halide_gl_native"); - } - private static native void processTextureHalide(int dst, int width, int height); - private static native void halideContextLost(); - - private static final android.opengl.GLES20 gl = new android.opengl.GLES20(); - - // If set to true, let Halide render directly to the framebuffer. - // Otherwise, Halide renders to a texture which we then blit to the - // screen. - private boolean halideDirectRender = true; - - HalideGLView(Context context) { - super(context); - setEGLContextClientVersion(2); - setPreserveEGLContextOnPause(true); - setDebugFlags(DEBUG_CHECK_GL_ERROR); - setRenderer(new MyRenderer()); - } - - class MyRenderer implements GLSurfaceView.Renderer { - private int output; - private int surfaceWidth, surfaceHeight; - private int program; - - private FloatBuffer quad_vertices; - - final String vs_source = - "attribute vec2 position;\n" + - "varying vec2 texpos;\n" + - "void main(void) {\n" + - " gl_Position = vec4(position, 0.0, 1.0);\n" + - " texpos = position * 0.5 + 0.5;\n" + - "}\n"; - final String fs_source = - "uniform sampler2D tex;\n" + - "varying highp vec2 texpos;\n" + - "void main(void) {\n" + - " gl_FragColor = texture2D(tex, texpos.xy);\n" + - "}\n"; - - public MyRenderer() { - final float[] vertices = new float[] { - -1.0f, -1.0f, - 1.0f, -1.0f, - -1.0f, 1.0f, - 1.0f, 1.0f, - }; - quad_vertices = - ByteBuffer.allocateDirect(4 * vertices.length) - .order(ByteOrder.nativeOrder()) - .asFloatBuffer(); - quad_vertices.put(vertices); - } - - /** Compile a single vertex or fragment shader. */ - private int compileShader(int type, String source) { - int shader = gl.glCreateShader(type); - gl.glShaderSource(shader, source); - gl.glCompileShader(shader); - int[] status = new int[1]; - gl.glGetShaderiv(shader, gl.GL_COMPILE_STATUS, status, 0); - if (status[0] == 0) { - String log = gl.glGetShaderInfoLog(shader); - Log.e(HelloHalideGL.TAG, log); - throw new RuntimeException("Compiling shader failed"); - } - return shader; - } - - /** Compile and link simple vertex and fragment shader for rendering - * 2D graphics. */ - private void prepareShaders() { - int vertex_shader = compileShader(gl.GL_VERTEX_SHADER, - vs_source); - int fragment_shader = compileShader(gl.GL_FRAGMENT_SHADER, - fs_source); - - program = gl.glCreateProgram(); - if (program == 0) { - throw new RuntimeException("Invalid GLSL program"); - } - gl.glAttachShader(program, vertex_shader); - gl.glAttachShader(program, fragment_shader); - gl.glBindAttribLocation(program, 0, "position"); - gl.glLinkProgram(program); - - int[] status = new int[1]; - gl.glGetProgramiv(program, gl.GL_LINK_STATUS, status, 0); - if (status[0] == 0) { - String log = gl.glGetProgramInfoLog(program); - Log.e(HelloHalideGL.TAG, log); - throw new RuntimeException("Linking GLSL program failed"); - } - } - - private int createTexture(int w, int h) { - int[] id = new int[1]; - gl.glGenTextures(1, id, 0); - gl.glBindTexture(gl.GL_TEXTURE_2D, id[0]); - gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MIN_FILTER, gl.GL_NEAREST); - gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MAG_FILTER, gl.GL_NEAREST); - - ByteBuffer buf = ByteBuffer.allocate(w * h * 4); - gl.glTexImage2D(gl.GL_TEXTURE_2D, 0, gl.GL_RGBA, w, h, 0, - gl.GL_RGBA, gl.GL_UNSIGNED_BYTE, buf); - return id[0]; - } - - @Override - public void onSurfaceCreated(GL10 unused, EGLConfig config) { - Log.d("Hello", "onSurfaceCreated"); - prepareShaders(); - } - - @Override - public void onSurfaceChanged(GL10 unused, int w, int h) { - halideContextLost(); - int[] textures = { output }; - gl.glDeleteTextures(1, textures, 0); - output = createTexture(w, h); - surfaceWidth = w; - surfaceHeight = h; - } - - @Override - public void onDrawFrame(GL10 unused) { - Log.d("Hello", "onDrawFrame"); - - if (halideDirectRender) { - // Call Halide filter; 0 as the texture ID in this case - // indicates render to framebuffer. - processTextureHalide(0, surfaceWidth, surfaceHeight); - } else { - // Call Halide filter - processTextureHalide(output, surfaceWidth, surfaceHeight); - - // Draw result to screen - gl.glViewport(0, 0, surfaceWidth, surfaceHeight); - - gl.glUseProgram(program); - - int positionLoc = gl.glGetAttribLocation(program, "position"); - quad_vertices.position(0); - gl.glVertexAttribPointer(positionLoc, 2, gl.GL_FLOAT, false, 0, quad_vertices); - gl.glEnableVertexAttribArray(positionLoc); - - int texLoc = gl.glGetUniformLocation(program, "tex"); - gl.glUniform1i(texLoc, 0); - gl.glActiveTexture(gl.GL_TEXTURE0); - gl.glBindTexture(gl.GL_TEXTURE_2D, output); - - gl.glDrawArrays(gl.GL_TRIANGLE_STRIP, 0, 4); - - gl.glDisableVertexAttribArray(positionLoc); - gl.glBindTexture(gl.GL_TEXTURE_2D, 0); - gl.glUseProgram(0); - gl.glDisableVertexAttribArray(0); - } - } - } -} - -public class HelloHalideGL extends Activity { - static final String TAG = "HelloHalideGL"; - - private GLSurfaceView view; - - @Override - public void onCreate(Bundle b) { - super.onCreate(b); - view = new HalideGLView(this); - setContentView(view); - } - - @Override - public void onResume() { - super.onResume(); - view.onResume(); - } - - @Override - public void onPause() { - super.onPause(); - view.onPause(); - } -} diff --git a/apps/bgu/Makefile b/apps/bgu/Makefile index a05070a3039c..ab8f69baaae5 100644 --- a/apps/bgu/Makefile +++ b/apps/bgu/Makefile @@ -24,7 +24,7 @@ $(BIN)/%/runtime.a: $(GENERATOR_BIN)/bgu.generator $(BIN)/%/filter: filter.cpp $(BIN)/%/bgu.a $(BIN)/%/bgu_auto_schedule.a $(BIN)/%/runtime.a @mkdir -p $(@D) - $(CXX) $(CXXFLAGS) -I$(BIN)/$* -Wall -O3 $^ -o $@ $(LDFLAGS) $(IMAGE_IO_FLAGS) $(CUDA_LDFLAGS) $(OPENCL_LDFLAGS) $(OPENGL_LDFLAGS) + $(CXX) $(CXXFLAGS) -I$(BIN)/$* -Wall -O3 $^ -o $@ $(LDFLAGS) $(IMAGE_IO_FLAGS) $(CUDA_LDFLAGS) $(OPENCL_LDFLAGS) $(BIN)/%/out.png: $(BIN)/%/filter $< ../images/rgb.png $(BIN)/$*/out.png diff --git a/apps/glsl/CMakeLists.txt b/apps/glsl/CMakeLists.txt deleted file mode 100644 index e9a8a5f13765..000000000000 --- a/apps/glsl/CMakeLists.txt +++ /dev/null @@ -1,41 +0,0 @@ -if (WIN32) - # Halide OpenGL is broken on Windows. - return() -endif () - -cmake_minimum_required(VERSION 3.16) -project(glsl) - -enable_testing() - -# Set up language settings -set(CMAKE_CXX_STANDARD 11) -set(CMAKE_CXX_STANDARD_REQUIRED YES) -set(CMAKE_CXX_EXTENSIONS NO) - -# Find Halide -find_package(Halide REQUIRED) - -find_package(OpenGL REQUIRED) -set(opengl_features opengl) -if (TARGET OpenGL::OpenGL AND TARGET OpenGL::EGL) - # EGL requires GLVND (which is found iff ::OpenGL is present) - list(APPEND opengl_features egl) -endif () - -# Generators -add_executable(glsl_blur.generator halide_blur_glsl_generator.cpp) -target_link_libraries(glsl_blur.generator PRIVATE Halide::Generator) - -add_executable(ycc.generator halide_ycc_glsl_generator.cpp) -target_link_libraries(ycc.generator PRIVATE Halide::Generator) - -# Libraries -add_halide_library(halide_blur_glsl FROM glsl_blur.generator FEATURES ${opengl_features} debug) -add_halide_library(halide_ycc_glsl FROM ycc.generator FEATURES ${opengl_features} debug) - -# Final executable -add_executable(opengl_test opengl_test.cpp) -target_link_libraries(opengl_test PRIVATE halide_blur_glsl halide_ycc_glsl) - -add_test(NAME opengl_test COMMAND opengl_test) diff --git a/apps/glsl/Makefile b/apps/glsl/Makefile deleted file mode 100644 index dc12d94ae504..000000000000 --- a/apps/glsl/Makefile +++ /dev/null @@ -1,36 +0,0 @@ -include ../support/Makefile.inc - -# Note: using the -g flag in conjunction with the -debug Feature on OSX may -# produce "failed to insert symbol" warnings at link time; this is annoying but harmless. -CXXFLAGS += -g -O0 - -all: $(BIN)/$(HL_TARGET)/opengl_test - -$(GENERATOR_BIN)/halide_blur_glsl.generator: halide_blur_glsl_generator.cpp $(GENERATOR_DEPS) - @mkdir -p $(@D) - $(CXX) $(CXXFLAGS) $(filter %.cpp,$^) -o $@ $(LIBHALIDE_LDFLAGS) - -$(BIN)/%/halide_blur_glsl.a: $(GENERATOR_BIN)/halide_blur_glsl.generator - @mkdir -p $(@D) - $^ -g halide_blur_glsl -e $(GENERATOR_OUTPUTS) -o $(@D) target=$*-opengl-debug - -$(GENERATOR_BIN)/halide_ycc_glsl.generator: halide_ycc_glsl_generator.cpp $(GENERATOR_DEPS) - @mkdir -p $(@D) - $(CXX) $(CXXFLAGS) $(filter %.cpp,$^) -o $@ $(LIBHALIDE_LDFLAGS) - -$(BIN)/%/halide_ycc_glsl.a: $(GENERATOR_BIN)/halide_ycc_glsl.generator - @mkdir -p $(@D) - $^ -g halide_ycc_glsl -e $(GENERATOR_OUTPUTS) -o $(@D) target=$*-opengl-debug - -$(BIN)/%/opengl_test: opengl_test.cpp $(BIN)/%/halide_blur_glsl.a $(BIN)/%/halide_ycc_glsl.a - @mkdir -p $(@D) - $(CXX) $(CXXFLAGS) -I$(BIN)/$* $^ -o $@ $(LDFLAGS) -L$(TOP)/bin $(PLATFORM_OPENGL_LDFLAGS) - -run: $(BIN)/$(HL_TARGET)/opengl_test - LD_LIBRARY_PATH=../../bin $< - -test: run - -.PHONY: clean -clean: - rm -rf $(BIN) diff --git a/apps/glsl/halide_blur_glsl_generator.cpp b/apps/glsl/halide_blur_glsl_generator.cpp deleted file mode 100644 index 1d9a2eae47dc..000000000000 --- a/apps/glsl/halide_blur_glsl_generator.cpp +++ /dev/null @@ -1,33 +0,0 @@ -#include "Halide.h" - -namespace { - -class HalideBlurGLSL : public Halide::Generator { -public: - Input> input8{"input8", 3}; - Output> blur_filter{"blur_filter", 3}; - void generate() { - assert(get_target().has_feature(Target::OpenGL)); - - Func blur_x("blur_x"), blur_y("blur_y"); - Var x("x"), y("y"), c("c"); - - // The algorithm - Func input; - input(x, y, c) = cast(input8(clamp(x, input8.dim(0).min(), input8.dim(0).max()), - clamp(y, input8.dim(1).min(), input8.dim(1).max()), c)) / - 255.f; - blur_x(x, y, c) = (input(x, y, c) + input(x + 1, y, c) + input(x + 2, y, c)) / 3; - blur_y(x, y, c) = (blur_x(x, y, c) + blur_x(x, y + 1, c) + blur_x(x, y + 2, c)) / 3; - blur_filter(x, y, c) = cast(blur_y(x, y, c) * 255.f); - - // Schedule for GLSL - input8.dim(2).set_bounds(0, 3); - blur_filter.bound(c, 0, 3); - blur_filter.glsl(x, y, c); - } -}; - -} // namespace - -HALIDE_REGISTER_GENERATOR(HalideBlurGLSL, halide_blur_glsl) diff --git a/apps/glsl/halide_ycc_glsl_generator.cpp b/apps/glsl/halide_ycc_glsl_generator.cpp deleted file mode 100644 index 47c05e8f4f3a..000000000000 --- a/apps/glsl/halide_ycc_glsl_generator.cpp +++ /dev/null @@ -1,39 +0,0 @@ -#include "Halide.h" - -namespace { - -class RgbToYcc : public Halide::Generator { -public: - Input> input8{"input8", 3}; - Output> out{"out", 3}; - void generate() { - assert(get_target().has_feature(Target::OpenGL)); - Var x("x"), y("y"), c("c"); - - // The algorithm - Func input("input"); - input(x, y, c) = cast(input8(x, y, c)) / 255.0f; - - Func Y("Y"), Cb("Cb"), Cr("Cr"); - Y(x, y) = 16.f / 255.f + (0.257f * input(x, y, 0) + - 0.504f * input(x, y, 1) + - 0.098f * input(x, y, 2)); - Cb(x, y) = 128.f / 255.f + (0.439f * input(x, y, 0) + - -0.368f * input(x, y, 1) + - -0.071f * input(x, y, 2)); - Cr(x, y) = 128.f / 255.f + (-0.148f * input(x, y, 0) + - -0.291f * input(x, y, 1) + - 0.439f * input(x, y, 2)); - out(x, y, c) = cast( - mux(c, {Y(x, y), Cb(x, y), Cr(x, y), 0.0f}) * 255.f); - - // Schedule for GLSL - input8.dim(2).set_bounds(0, 3); - out.bound(c, 0, 3); - out.glsl(x, y, c); - } -}; - -} // namespace - -HALIDE_REGISTER_GENERATOR(RgbToYcc, halide_ycc_glsl) diff --git a/apps/glsl/opengl_test.cpp b/apps/glsl/opengl_test.cpp deleted file mode 100644 index 161805887daa..000000000000 --- a/apps/glsl/opengl_test.cpp +++ /dev/null @@ -1,58 +0,0 @@ -#include -#include -#include - -#include "HalideBuffer.h" -#include "HalideRuntime.h" -#include "HalideRuntimeOpenGL.h" - -using Halide::Runtime::Buffer; - -#include "halide_blur_glsl.h" -#include "halide_ycc_glsl.h" - -void test_blur() { - const int W = 12, H = 32, C = 3; - Buffer input(W, H, C); - Buffer output(W, H, C); - - fprintf(stderr, "test_blur\n"); - halide_blur_glsl(input, output); - fprintf(stderr, "test_blur complete\n"); -} - -void test_ycc() { - const int W = 12, H = 32, C = 3; - Buffer input(W, H, C); - Buffer output(W, H, C); - - fprintf(stderr, "test_ycc\n"); - halide_ycc_glsl(input, output); - fprintf(stderr, "Ycc complete\n"); -} - -void test_device_sync() { - const int W = 12, H = 32, C = 3; - Buffer temp(W, H, C); - - temp.set_host_dirty(); - int result = temp.copy_to_device(halide_opengl_device_interface()); - if (result != 0) { - fprintf(stderr, "halide_device_malloc failed with return %d.\n", result); - abort(); - } else { - result = temp.device_sync(); - if (result != 0) { - fprintf(stderr, "halide_device_sync failed with return %d.\n", result); - abort(); - } else { - fprintf(stderr, "Test device sync complete.\n"); - } - } -} - -int main(int argc, char *argv[]) { - test_blur(); - test_ycc(); - test_device_sync(); -} diff --git a/apps/harris/Makefile b/apps/harris/Makefile index 3fef49815a1c..713c11d0c2c7 100644 --- a/apps/harris/Makefile +++ b/apps/harris/Makefile @@ -22,7 +22,7 @@ $(BIN)/%/runtime.a: $(GENERATOR_BIN)/harris.generator $(BIN)/%/filter: filter.cpp $(BIN)/%/harris.a $(BIN)/%/harris_auto_schedule.a $(BIN)/%/runtime.a @mkdir -p $(@D) - $(CXX) $(CXXFLAGS) -I$(BIN)/$* -Wall -O3 $^ -o $@ $(LDFLAGS) $(IMAGE_IO_FLAGS) $(CUDA_LDFLAGS) $(OPENCL_LDFLAGS) $(OPENGL_LDFLAGS) + $(CXX) $(CXXFLAGS) -I$(BIN)/$* -Wall -O3 $^ -o $@ $(LDFLAGS) $(IMAGE_IO_FLAGS) $(CUDA_LDFLAGS) $(OPENCL_LDFLAGS) $(BIN)/%/out.png: $(BIN)/%/filter $< ../images/rgba.png $(BIN)/$*/out.png diff --git a/apps/hist/Makefile b/apps/hist/Makefile index 8ab3e5785407..5f4faa1b835a 100644 --- a/apps/hist/Makefile +++ b/apps/hist/Makefile @@ -24,7 +24,7 @@ $(BIN)/%/runtime.a: $(GENERATOR_BIN)/hist.generator $(BIN)/%/filter: filter.cpp $(BIN)/%/hist.a $(BIN)/%/hist_auto_schedule.a $(BIN)/%/runtime.a @mkdir -p $(@D) - $(CXX) $(CXXFLAGS) -I$(BIN)/$* -Wall -O3 $^ -o $@ $(LDFLAGS) $(IMAGE_IO_FLAGS) $(CUDA_LDFLAGS) $(OPENCL_LDFLAGS) $(OPENGL_LDFLAGS) + $(CXX) $(CXXFLAGS) -I$(BIN)/$* -Wall -O3 $^ -o $@ $(LDFLAGS) $(IMAGE_IO_FLAGS) $(CUDA_LDFLAGS) $(OPENCL_LDFLAGS) $(BIN)/%/out.png: $(BIN)/%/filter $< ../images/rgba.png $(BIN)/$*/out.png diff --git a/apps/iir_blur/Makefile b/apps/iir_blur/Makefile index d195ffa7caf5..8c9983c8fa14 100644 --- a/apps/iir_blur/Makefile +++ b/apps/iir_blur/Makefile @@ -22,7 +22,7 @@ $(BIN)/%/runtime.a: $(GENERATOR_BIN)/iir_blur.generator $(BIN)/%/filter: filter.cpp $(BIN)/%/iir_blur.a $(BIN)/%/iir_blur_auto_schedule.a $(BIN)/%/runtime.a @mkdir -p $(@D) - $(CXX) $(CXXFLAGS) -I$(BIN)/$* -Wall -O3 $^ -o $@ $(LDFLAGS) $(IMAGE_IO_FLAGS) $(CUDA_LDFLAGS) $(OPENCL_LDFLAGS) $(OPENGL_LDFLAGS) + $(CXX) $(CXXFLAGS) -I$(BIN)/$* -Wall -O3 $^ -o $@ $(LDFLAGS) $(IMAGE_IO_FLAGS) $(CUDA_LDFLAGS) $(OPENCL_LDFLAGS) $(BIN)/%/out.png: $(BIN)/%/filter $< ../images/rgba.png $(BIN)/$*/out.png diff --git a/apps/interpolate/Makefile b/apps/interpolate/Makefile index fc5d8d7609f1..9d0d5c41f434 100644 --- a/apps/interpolate/Makefile +++ b/apps/interpolate/Makefile @@ -24,7 +24,7 @@ $(BIN)/%/runtime.a: $(GENERATOR_BIN)/interpolate.generator $(BIN)/%/filter: filter.cpp $(BIN)/%/interpolate.a $(BIN)/%/interpolate_auto_schedule.a $(BIN)/%/runtime.a @mkdir -p $(@D) - $(CXX) $(CXXFLAGS) -I$(BIN)/$* -Wall -O3 $^ -o $@ $(LDFLAGS) $(IMAGE_IO_FLAGS) $(CUDA_LDFLAGS) $(OPENCL_LDFLAGS) $(OPENGL_LDFLAGS) + $(CXX) $(CXXFLAGS) -I$(BIN)/$* -Wall -O3 $^ -o $@ $(LDFLAGS) $(IMAGE_IO_FLAGS) $(CUDA_LDFLAGS) $(OPENCL_LDFLAGS) $(BIN)/%/out.png: $(BIN)/%/filter $< ../images/rgba.png $(BIN)/$*/out.png diff --git a/apps/lens_blur/Makefile b/apps/lens_blur/Makefile index ea403c1e5bcf..8ede6b797ffe 100644 --- a/apps/lens_blur/Makefile +++ b/apps/lens_blur/Makefile @@ -19,7 +19,7 @@ $(BIN)/%/lens_blur_auto_schedule.a: $(GENERATOR_BIN)/lens_blur.generator $(BIN)/%/process: process.cpp $(BIN)/%/lens_blur.a $(BIN)/%/lens_blur_auto_schedule.a @mkdir -p $(@D) - $(CXX) $(CXXFLAGS) -I$(BIN)/$* -Wall $^ -o $@ $(LDFLAGS) $(IMAGE_IO_FLAGS) $(CUDA_LDFLAGS) $(OPENCL_LDFLAGS) $(OPENGL_LDFLAGS) + $(CXX) $(CXXFLAGS) -I$(BIN)/$* -Wall $^ -o $@ $(LDFLAGS) $(IMAGE_IO_FLAGS) $(CUDA_LDFLAGS) $(OPENCL_LDFLAGS) $(BIN)/%/out.png: $(BIN)/%/process @mkdir -p $(@D) diff --git a/apps/local_laplacian/Makefile b/apps/local_laplacian/Makefile index e9a9b69fe53f..21fa7bf74f6b 100644 --- a/apps/local_laplacian/Makefile +++ b/apps/local_laplacian/Makefile @@ -18,7 +18,7 @@ $(BIN)/%/local_laplacian_auto_schedule.a: $(GENERATOR_BIN)/local_laplacian.gener $(BIN)/%/process: process.cpp $(BIN)/%/local_laplacian.a $(BIN)/%/local_laplacian_auto_schedule.a @mkdir -p $(@D) - $(CXX) $(CXXFLAGS) -I$(BIN)/$* -Wall $^ -o $@ $(LDFLAGS) $(IMAGE_IO_FLAGS) $(CUDA_LDFLAGS) $(OPENCL_LDFLAGS) $(OPENGL_LDFLAGS) + $(CXX) $(CXXFLAGS) -I$(BIN)/$* -Wall $^ -o $@ $(LDFLAGS) $(IMAGE_IO_FLAGS) $(CUDA_LDFLAGS) $(OPENCL_LDFLAGS) $(BIN)/%/out.png: $(BIN)/%/process @mkdir -p $(@D) @@ -30,7 +30,7 @@ $(BIN)/%/out.tiff: $(BIN)/%/process $(BIN)/%/process_viz: process.cpp $(BIN)/%-trace_all/local_laplacian.a @mkdir -p $(@D) - $(CXX) $(CXXFLAGS) -DNO_AUTO_SCHEDULE -I$(BIN)/$*-trace_all -Wall $^ -o $@ $(LDFLAGS) $(IMAGE_IO_FLAGS) $(CUDA_LDFLAGS) $(OPENCL_LDFLAGS) $(OPENGL_LDFLAGS) + $(CXX) $(CXXFLAGS) -DNO_AUTO_SCHEDULE -I$(BIN)/$*-trace_all -Wall $^ -o $@ $(LDFLAGS) $(IMAGE_IO_FLAGS) $(CUDA_LDFLAGS) $(OPENCL_LDFLAGS) ../../bin/HalideTraceViz: ../../util/HalideTraceViz.cpp $(MAKE) -C ../../ bin/HalideTraceViz diff --git a/apps/max_filter/Makefile b/apps/max_filter/Makefile index 6fcd9a59748c..bd755774b2f5 100644 --- a/apps/max_filter/Makefile +++ b/apps/max_filter/Makefile @@ -24,7 +24,7 @@ $(BIN)/%/runtime.a: $(GENERATOR_BIN)/max_filter.generator $(BIN)/%/filter: filter.cpp $(BIN)/%/max_filter.a $(BIN)/%/max_filter_auto_schedule.a $(BIN)/%/runtime.a @mkdir -p $(@D) - $(CXX) $(CXXFLAGS) -I$(BIN)/$* -Wall -O3 $^ -o $@ $(LDFLAGS) $(IMAGE_IO_FLAGS) $(CUDA_LDFLAGS) $(OPENCL_LDFLAGS) $(OPENGL_LDFLAGS) + $(CXX) $(CXXFLAGS) -I$(BIN)/$* -Wall -O3 $^ -o $@ $(LDFLAGS) $(IMAGE_IO_FLAGS) $(CUDA_LDFLAGS) $(OPENCL_LDFLAGS) $(BIN)/%/out.png: $(BIN)/%/filter $< ../images/rgba.png $(BIN)/$*/out.png diff --git a/apps/nl_means/Makefile b/apps/nl_means/Makefile index 13c9290cd3a2..2c7fecdccc47 100644 --- a/apps/nl_means/Makefile +++ b/apps/nl_means/Makefile @@ -18,7 +18,7 @@ $(BIN)/%/nl_means_auto_schedule.a: $(GENERATOR_BIN)/nl_means.generator $(BIN)/%/process: process.cpp $(BIN)/%/nl_means.a $(BIN)/%/nl_means_auto_schedule.a @mkdir -p $(@D) - $(CXX) $(CXXFLAGS) -I$(BIN)/$* -Wall $^ -o $@ $(LDFLAGS) $(IMAGE_IO_FLAGS) $(CUDA_LDFLAGS) $(OPENCL_LDFLAGS) $(OPENGL_LDFLAGS) + $(CXX) $(CXXFLAGS) -I$(BIN)/$* -Wall $^ -o $@ $(LDFLAGS) $(IMAGE_IO_FLAGS) $(CUDA_LDFLAGS) $(OPENCL_LDFLAGS) $(BIN)/%/out.png: $(BIN)/%/process @mkdir -p $(@D) diff --git a/apps/opengl_demo/Makefile b/apps/opengl_demo/Makefile deleted file mode 100644 index 5d6bc2bb23e7..000000000000 --- a/apps/opengl_demo/Makefile +++ /dev/null @@ -1,102 +0,0 @@ -# -# This could be more DRY using some Makefile magic, but for the example -# app will try to maximize clarity by making most rules explicit -# - -# Where to find Halide. -# -# If you are building this demo using Halide installed systemwide (e.g. on -# OS X installed via homebrew), you can set: -# -# HALIDE_TOOLS_DIR = /usr/local/share/halide/tools -# HALIDE_LIB_PATH = -# HALIDE_INC_PATH = -# -# These settings are for building within the Halide source tree: -HALIDE_TOOLS_DIR = ../../tools -HALIDE_LIB_PATH = -L ../../bin -HALIDE_INC_PATH = -I ../../include -HL_TARGET ?= host - -# Platform-specific settings. -# -UNAME = $(shell uname) - -ifeq ($(UNAME),Darwin) - - # These are for OS X: - DTX_FONT = /Library/Fonts/Arial.ttf - OPENGL_LIBS = -lglfw -framework OpenGL -framework GLUT - GENERATOR_LIBS = -lHalide -lz -lcurses - -else - - # These are for Ubuntu Linux - DTX_FONT = /usr/share/fonts/truetype/dejavu/DejaVuSans.ttf - OPENGL_LIBS = `pkg-config glfw3 --libs` -lGL -lglut -lX11 -lpthread -ldl -lXxf86vm -lXinerama -lXcursor -lXrandr - GENERATOR_LIBS = -lHalide -lz -lcurses -Wl,--rpath=$(HALIDE_LIB_PATH) - -endif - -# -# General build settings. Should be good cross-platform. -# -MAIN_LIBS = -lpng -ldrawtext $(OPENGL_LIBS) -GENERATOR_LIBS = -lHalide -lz -lcurses -CXXFLAGS = -std=c++11 -g -DDTX_FONT=\"$(DTX_FONT)\" $(HALIDE_INC_PATH) - -# Output directory. -BIN ?= bin - -.PHONY: run clean - -default: run - -run: $(BIN)/opengl_demo - $(BIN)/opengl_demo image.png - -clean: - rm -rf $(BIN) - -$(BIN)/opengl_demo: \ - $(BIN)/main.o \ - $(BIN)/layout.o \ - $(BIN)/timer.o \ - $(BIN)/glfw_helpers.o \ - $(BIN)/opengl_helpers.o \ - $(BIN)/png_helpers.o \ - $(BIN)/sample_filter_cpu.o \ - $(BIN)/sample_filter_opengl.o - $(CXX) $(CXXFLAGS) -o $@ $^ $(MAIN_LIBS) - -# -# Explicitly list the dependency on the generated filter header files, -# to ensure that they are created first. -# -$(BIN)/main.o: \ - $(BIN)/sample_filter_cpu.h \ - $(BIN)/sample_filter_opengl.h - -# -# Rules to AOT-compile the halide filter for both CPU and OpenGL; the -# compiled filters depend on $(BIN)/sample_filter.generator, which in turn -# depends on the halide filter source in sample_filter.cpp -# -$(BIN)/sample_filter_cpu.o $(BIN)/sample_filter_cpu.h: $(BIN)/sample_filter.generator - LD_LIBRARY_PATH=../../bin $(BIN)/sample_filter.generator -g sample_filter -e object,c_header,stmt -o $(BIN) -f sample_filter_cpu target=$(HL_TARGET) - -$(BIN)/sample_filter_opengl.o $(BIN)/sample_filter_opengl.h: $(BIN)/sample_filter.generator - LD_LIBRARY_PATH=../../bin $(BIN)/sample_filter.generator -g sample_filter -e object,c_header,stmt -o $(BIN) -f sample_filter_opengl target=host-opengl-debug - -$(BIN)/sample_filter.generator: sample_filter_generator.cpp - @mkdir -p $(@D) - $(CXX) $(CXXFLAGS) -o $@ $^ $(HALIDE_TOOLS_DIR)/GenGen.cpp $(HALIDE_LIB_PATH) $(GENERATOR_LIBS) $(HALIDE_SYSTEM_LIBS) - -# -# Build in subdir using auto-dependency mechanism -# -$(BIN)/%.o: %.cpp - @mkdir -p $(@D) - $(CXX) -c $(CXXFLAGS) -I$(BIN) -MMD -MF $(patsubst %.o,%.d,$@) -o $@ $< - --include $(wildcard $(BIN)/*.d) diff --git a/apps/opengl_demo/README.md b/apps/opengl_demo/README.md deleted file mode 100644 index d2e7e8c91b88..000000000000 --- a/apps/opengl_demo/README.md +++ /dev/null @@ -1,104 +0,0 @@ -# Halide OpenGL Demo - -This demo contains an OpenGL desktop app that displays an input image side by -side with the result of running a sample halide filter in three different ways: - -1. On the CPU, not using OpenGL. - -2. In OpenGL, with Halide transfering the input data from the host and - transferring the result data back to the host. - -3. In OpenGL, with Halide accepting input data that's in an OpenGL texture, and - leaving the result in an OpenGL texture. - -The display reports the timing for each. You should expect to see that #3 is -fastest as it runs entirely on the GPU, while #2 is slowest because of the data -transfer times. - -In this example we use AOT compilation twice: Once with `target=host` to produce -the filter that runs on the CPU; and once with `target=host-opengl` to produce -the filter that runs in OpenGL (which we call twice). - -The sample filter inverts the RGB channels of the input image. - -_This demo is known to work on OS X 10.11 and Ubuntu Linux 14.04 & 16.04. -Windows has not yet been tested._ - -### Instructions: - -Build and run the app by simply running `make`. It should open a window showing -the input and the three (identical) filtering results. You can close the window -and exit by pressing ESCAPE. - -The `Makefile` has variables to specify where to find Halide, how to link -OpenGL, and so forth. You may need to tweak them for your platform. - -See the Makefile for details on how the filter gets AOT-compiled for CPU and -OpenGL. Note that the `Makefile` actually specifies `target=host-opengl-debug` -when AOT-compiling the opengl filter; that enables tracing of Halide's -management of its OpenGL pipeline. - -#### Dependencies: - -This app depends on: - -- [GLFW 3](http://www.glfw.org) -- [libpng](http://www.libpng.org) -- [libdrawtext](http://nuclear.mutantstargoat.com/sw/libdrawtext/) - -On OS X, all three can be installed using [homebrew](http://brew.sh) - -```sh -brew install glfw -brew install libpng -brew install libdrawtext -``` - -Halide itself can be installed on OS X via - -```sh -brew tap halide/halide -brew install halide -``` - -On Ubuntu Linux, everything but libdrawtext can be installed via system -packages: - -```sh -sudo apt-get install libglfw3-dev libx11-dev freeglut3-dev libfreetype6-dev libgl-dev libpng-dev -``` - -For libdrawtext, try this: - -``` -git clone https://github.com/jtsiomb/libdrawtext.git -cd libdrawtext -./configure -make -sudo make install -``` - -### Files: - -- `sample_filter.cpp` - - The Halide filter generator source. - -- `main.cpp` - - Contains all the Halide client code. - - Note that it `#include`s the generated files `build/sample_filter_cpu.h` and - `build/sample_filter_opengl.h`. - -- `layout.{h,cpp}` - - A minimal rendering framework for this example app. - -- `timer.{h,cpp}` - - A minimal timing & reporting library. - -- `{glfw,opengl,png}_helpers.{cpp,h}` - - Conveniences that hide the dirty details of the low-level packages. diff --git a/apps/opengl_demo/glfw_helpers.cpp b/apps/opengl_demo/glfw_helpers.cpp deleted file mode 100644 index 07752597e42d..000000000000 --- a/apps/opengl_demo/glfw_helpers.cpp +++ /dev/null @@ -1,63 +0,0 @@ -#include "glfw_helpers.h" -#include -#include -#include -#include - -using namespace GlfwHelpers; - -static GLFWwindow *window; - -static void die(const char *msg) { - fprintf(stderr, "%s\n", msg); - exit(EXIT_FAILURE); -} - -static void error_callback(int error, const char *description) { - die(description); -} - -static void key_callback(GLFWwindow *window, int key, int scancode, int action, int mods) { - if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS) - glfwSetWindowShouldClose(window, GL_TRUE); -} - -static bool first_focus = false; -static void focus_callback(GLFWwindow *window, int) { - first_focus = true; -} - -struct info GlfwHelpers::setup(int width, int height) { - struct info info; - - glfwSetErrorCallback(error_callback); - if (!glfwInit()) die("couldn't init glfw!"); - glfwWindowHint(GLFW_DOUBLEBUFFER, GL_FALSE); // Single buffer mode, to avoid any doublebuffering timing issues - window = glfwCreateWindow(width, height, "opengl_halide_test", NULL, NULL); - if (!window) die("couldn't create window!"); - glfwSetKeyCallback(window, key_callback); - glfwSetWindowFocusCallback(window, focus_callback); - glfwMakeContextCurrent(window); - - while (!first_focus) { - glfwWaitEvents(); - } - - int framebuffer_width, framebuffer_height; - glfwGetFramebufferSize(window, &framebuffer_width, &framebuffer_height); - info.dpi_scale = float(framebuffer_width) / float(width); - - return info; -} - -void GlfwHelpers::terminate() { - while (!glfwWindowShouldClose(window)) { - glfwPollEvents(); - } - glfwDestroyWindow(window); - glfwTerminate(); -} - -void GlfwHelpers::set_opengl_context() { - glfwMakeContextCurrent(window); -} diff --git a/apps/opengl_demo/glfw_helpers.h b/apps/opengl_demo/glfw_helpers.h deleted file mode 100644 index cd3a0f05bdcf..000000000000 --- a/apps/opengl_demo/glfw_helpers.h +++ /dev/null @@ -1,15 +0,0 @@ -#ifndef _GLFW_HELPERS_H_ -#define _GLFW_HELPERS_H_ - -namespace GlfwHelpers { - -struct info { - float dpi_scale; -}; - -struct info setup(int width, int height); -void set_opengl_context(); -void terminate(); -} // namespace GlfwHelpers - -#endif diff --git a/apps/opengl_demo/image.png b/apps/opengl_demo/image.png deleted file mode 100644 index c73df2103613ba2df087c732c7dd2af2f437d7c3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 166992 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 $@%dyU?R5XdwWM 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 aDf_?djdK7(v@(DI0000 - -#include "opengl_helpers.h" - -#include "layout.h" - -using namespace Layout; - -static const int border_sz = 10; // pixels -static const int header_sz = 20; // pixels - -static struct info state; - -const struct info &Layout::setup(int image_width, int image_height) { - state.window_width = 2 * image_width + 3 * border_sz; - state.window_height = 2 * image_height + border_sz + 2 * header_sz; - return state; -} - -void Layout::draw_texture(enum location location, GLuint texture_id, int width, int height, const std::string &label) { - int x0, x1, y0, y1, lx, ly; - switch (location) { // set X coords - case LL: - case UL: - x0 = border_sz; - x1 = x0 + width; - lx = x0 + 2; - break; - case LR: - case UR: - x1 = state.window_width - border_sz; - x0 = x1 - width; - lx = x0 + 2; - break; - } - switch (location) { // set Y coords - case LL: - case LR: - y0 = header_sz; - y1 = y0 + height; - ly = 6; - break; - case UL: - case UR: - y1 = state.window_height - header_sz; - y0 = y1 - height; - ly = y1 + 6; - break; - } - - OpenGLHelpers::display_texture(texture_id, 2.0 * x0 / state.window_width - 1.0, 2.0 * x1 / state.window_width - 1.0, 2.0 * y0 / state.window_height - 1.0, 2.0 * y1 / state.window_height - 1.0); - OpenGLHelpers::draw_text(label, 2.0 * lx / state.window_width - 1.0, 2.0 * ly / state.window_height - 1.0); -} - -void Layout::draw_image(enum location location, const uint8_t *data, int width, int height, const std::string &label) { - const auto texture_id = OpenGLHelpers::create_texture(width, height, data); - draw_texture(location, texture_id, width, height, label); - OpenGLHelpers::delete_texture(texture_id); -} diff --git a/apps/opengl_demo/layout.h b/apps/opengl_demo/layout.h deleted file mode 100644 index be4947abc122..000000000000 --- a/apps/opengl_demo/layout.h +++ /dev/null @@ -1,28 +0,0 @@ -#ifndef _LAYOUT_HELPERS_H_ -#define _LAYOUT_HELPERS_H_ - -#if defined(__APPLE__) -#include -#else -#include -#endif - -namespace Layout { - -enum location { UL, - UR, - LL, - LR }; - -struct info { - int window_width; - int window_height; -}; - -const struct info &setup(int image_width, int image_height); - -void draw_image(enum location location, const uint8_t *data, int width, int height, const std::string &label); -void draw_texture(enum location location, GLuint texture_id, int width, int height, const std::string &label); -} // namespace Layout - -#endif diff --git a/apps/opengl_demo/main.cpp b/apps/opengl_demo/main.cpp deleted file mode 100644 index 955f21ac812c..000000000000 --- a/apps/opengl_demo/main.cpp +++ /dev/null @@ -1,172 +0,0 @@ -#include -#include - -#include "glfw_helpers.h" -#include "layout.h" -#include "opengl_helpers.h" -#include "png_helpers.h" -#include "timer.h" - -#include "sample_filter_cpu.h" -#include "sample_filter_opengl.h" -#include -#include - -/* - * Initializes a halide_buffer_t object for 8-bit RGBA data stored - * interleaved as rgbargba... in row-major order. - */ -Halide::Runtime::Buffer create_buffer(uint8_t *data, int width, int height) { - return Halide::Runtime::Buffer::make_interleaved(data, width, height, 4); -} - -/* - * Runs the filter on the CPU. Takes a pointer to memory with the image - * data to filter, and a pointer to memory in which to place the result - * data. - */ -std::string run_cpu_filter(const uint8_t *image_data, uint8_t *result_data, int width, int height) { - const auto time = Timer::start("CPU"); - - // Create halide input buffer and point it at the passed image data - auto input_buf = create_buffer((uint8_t *)image_data, width, height); - - // Create halide output buffer and point it at the passed result data storage - auto output_buf = create_buffer(result_data, width, height); - - // Run the AOT-compiled OpenGL filter - sample_filter_cpu(input_buf, output_buf); - - return Timer::report(time); -} - -/* - * Runs the filter on OpenGL. Takes a pointer to memory with the image - * data to filter, and a pointer to memory in which to place the result - * data. - */ -std::string run_opengl_filter_from_host_to_host(const uint8_t *image_data, uint8_t *result_data, int width, int height) { - const auto time = Timer::start("OpenGL host-to-host"); - - // Create halide input buffer and point it at the passed image data for - // the host memory. Halide will automatically allocate a texture to - // hold the data on the GPU. Mark the host memory as "dirty" so halide - // will know it needs to transfer the data to the GPU texture. - auto input_buf = create_buffer((uint8_t *)image_data, width, height); - input_buf.set_host_dirty(); - - // Create halide output buffer and point it at the passed result data - // memory. Halide will automatically allocate a texture to hold the - // data on the GPU. - auto output_buf = create_buffer(result_data, width, height); - - // Run the AOT-compiled OpenGL filter - sample_filter_opengl(input_buf, output_buf); - - // Ensure that halide copies the data back to the host - output_buf.copy_to_host(); - - return Timer::report(time); -} - -/* - * Runs the filter on OpenGL. Assumes the data is already in a texture, - * and leaves the output in a texture - */ -std::string run_opengl_filter_from_texture_to_texture(GLuint input_texture_id, GLuint output_texture_id, int width, int height) { - const auto time = Timer::start("OpenGL texture-to-texture"); - - // Create halide input buffer and tell it to use the existing GPU - // texture. No need to allocate memory on the host since this simple - // pipeline will run entirely on the GPU. - auto input_buf = create_buffer(nullptr, width, height); - halide_opengl_wrap_texture(nullptr, input_buf.raw_buffer(), input_texture_id); - - // Create halide output buffer and tell it to use the existing GPU texture. - // No need to allocate memory on the host since this simple pipeline will run - // entirely on the GPU. - auto output_buf = create_buffer(nullptr, width, height); - halide_opengl_wrap_texture(nullptr, output_buf.raw_buffer(), output_texture_id); - - // Run the AOT-compiled OpenGL filter - sample_filter_opengl(input_buf, output_buf); - - // Tell halide we are finished using the textures - halide_opengl_detach_texture(nullptr, output_buf.raw_buffer()); - halide_opengl_detach_texture(nullptr, input_buf.raw_buffer()); - - return Timer::report(time); -} - -int main(const int argc, const char *argv[]) { - if (argc != 2) { - std::cerr << "Usage: " << argv[0] << " filename\n"; - exit(1); - } - const std::string filename = argv[1]; - - const auto image = PNGHelpers::load(filename); - const auto width = image.width; - const auto height = image.height; - - const auto layout = Layout::setup(width, height); - const auto glfw = GlfwHelpers::setup(layout.window_width, layout.window_height); - OpenGLHelpers::setup(glfw.dpi_scale); - - /* - * Draw the original image - */ - Layout::draw_image(Layout::UL, image.data, width, height, "Input"); - - std::string report; - - /* - * Draw the result of running the filter on the CPU - */ - const auto cpu_result_data = (uint8_t *)calloc(width * height * 4, sizeof(uint8_t)); - report = run_cpu_filter(image.data, cpu_result_data, width, height); - Layout::draw_image(Layout::UR, cpu_result_data, width, height, report); - free((void *)cpu_result_data); - - /* - * Draw the result of running the filter on OpenGL, with data starting - * from and ending up on the host - */ - const auto opengl_result_data = (uint8_t *)calloc(width * height * 4, sizeof(uint8_t)); - report = run_opengl_filter_from_host_to_host(image.data, opengl_result_data, width, height); - Layout::draw_image(Layout::LL, opengl_result_data, width, height, report); - free((void *)opengl_result_data); - - /* - * Draw the result of running the filter on OpenGL, with data starting - * from and ending up in a texture on the device - */ - const auto image_texture_id = OpenGLHelpers::create_texture(width, height, image.data); - const auto result_texture_id = OpenGLHelpers::create_texture(width, height, nullptr); - report = run_opengl_filter_from_texture_to_texture(image_texture_id, result_texture_id, width, height); - Layout::draw_texture(Layout::LR, result_texture_id, width, height, report); - OpenGLHelpers::delete_texture(image_texture_id); - OpenGLHelpers::delete_texture(result_texture_id); - - // Release all Halide internal structures for the OpenGL context - halide_opengl_context_lost(nullptr); - - GlfwHelpers::terminate(); - - free((void *)image.data); - - return 0; -} - -/* - * Global definition required by halide with OpenGL backend, to prevent - * Halide from allocating its own OpenGL context. - * - * In general, this function needs to set an active OpenGL context - * and return 0 on success. - */ - -int halide_opengl_create_context(void * /*user_context*/) { - GlfwHelpers::set_opengl_context(); - return 0; -} diff --git a/apps/opengl_demo/opengl_helpers.cpp b/apps/opengl_demo/opengl_helpers.cpp deleted file mode 100644 index 1cf994f19879..000000000000 --- a/apps/opengl_demo/opengl_helpers.cpp +++ /dev/null @@ -1,67 +0,0 @@ -#include - -#include "opengl_helpers.h" - -using namespace OpenGLHelpers; - -static const int font_size = 12; - -void OpenGLHelpers::setup(float dpi_scale) { - const int scaled_font_size = font_size * dpi_scale; - dtx_use_font(dtx_open_font(DTX_FONT, scaled_font_size), scaled_font_size); - glClear(GL_COLOR_BUFFER_BIT); -} - -GLuint OpenGLHelpers::create_texture(int width, int height, const uint8_t *data) { - GLuint texture_id; - glEnable(GL_TEXTURE_2D); - glGenTextures(1, &texture_id); - glBindTexture(GL_TEXTURE_2D, texture_id); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data); - return texture_id; -} - -void OpenGLHelpers::delete_texture(GLuint texture_id) { - glDeleteTextures(1, &texture_id); -} - -void OpenGLHelpers::display_texture(GLuint texture_id, float x0, float x1, float y0, float y1) { - glBindTexture(GL_TEXTURE_2D, texture_id); - glMatrixMode(GL_PROJECTION); - glLoadIdentity(); - glMatrixMode(GL_MODELVIEW); - glLoadIdentity(); - glMatrixMode(GL_TEXTURE); - glLoadIdentity(); - glColor3f(1, 1, 1); - glBegin(GL_QUADS); - glTexCoord2d(1, 0); - glVertex2f(x1, y1); - glTexCoord2d(0, 0); - glVertex2f(x0, y1); - glTexCoord2d(0, 1); - glVertex2f(x0, y0); - glTexCoord2d(1, 1); - glVertex2f(x1, y0); - glEnd(); - glFinish(); -} - -void OpenGLHelpers::draw_text(const std::string &text, float x, float y) { - glMatrixMode(GL_PROJECTION); - glLoadIdentity(); - glOrtho(-1, 1, -1, 1, -1, 1); - glMatrixMode(GL_MODELVIEW); - glLoadIdentity(); - glTranslatef(x, y, 0); - glColor3f(1, 1, 1); - GLint viewport[4]; - glGetIntegerv(GL_VIEWPORT, viewport); - glScalef(2.0f / viewport[2], 2.0f / viewport[3], 1); - dtx_string(text.c_str()); - glFinish(); -} diff --git a/apps/opengl_demo/opengl_helpers.h b/apps/opengl_demo/opengl_helpers.h deleted file mode 100644 index 962f61989928..000000000000 --- a/apps/opengl_demo/opengl_helpers.h +++ /dev/null @@ -1,20 +0,0 @@ -#ifndef _OPENGL_HELPERS_H_ -#define _OPENGL_HELPERS_H_ - -#include - -#if defined(__APPLE__) -#include -#else -#include -#endif - -namespace OpenGLHelpers { -void setup(float dpi_scale); -GLuint create_texture(int width, int height, const uint8_t *data); -void delete_texture(GLuint texture_id); -void display_texture(GLuint texture_id, float x0, float x1, float y0, float y1); -void draw_text(const std::string &text, float x, float y); -} // namespace OpenGLHelpers - -#endif diff --git a/apps/opengl_demo/png_helpers.cpp b/apps/opengl_demo/png_helpers.cpp deleted file mode 100644 index c7d48f00949f..000000000000 --- a/apps/opengl_demo/png_helpers.cpp +++ /dev/null @@ -1,73 +0,0 @@ -#include -#include -#include - -#include "png_helpers.h" - -using namespace PNGHelpers; - -struct image_info PNGHelpers::load(const std::string &filepath) { - const auto fp = fopen(filepath.c_str(), "rb"); - if (fp == 0) { - perror(filepath.c_str()); - exit(1); - } - - // verify the header - png_byte header[8]; - fread(header, 1, 8, fp); - if (png_sig_cmp(header, 0, 8)) { - std::cerr << "error: " << filepath << " is not a PNG file.\n"; - exit(1); - } - - auto png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); - auto png_info = png_create_info_struct(png); - - if (setjmp(png_jmpbuf(png))) abort(); - - png_init_io(png, fp); - png_set_sig_bytes(png, 8); // already read header - png_read_info(png, png_info); - - const auto width = png_get_image_width(png, png_info); - const auto height = png_get_image_height(png, png_info); - const auto color_type = png_get_color_type(png, png_info); - const auto bit_depth = png_get_bit_depth(png, png_info); - - if (bit_depth == 16) - png_set_strip_16(png); - - if (color_type == PNG_COLOR_TYPE_PALETTE) - png_set_palette_to_rgb(png); - - if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) - png_set_expand_gray_1_2_4_to_8(png); - - if (png_get_valid(png, png_info, PNG_INFO_tRNS)) - png_set_tRNS_to_alpha(png); - - if (color_type == PNG_COLOR_TYPE_RGB || color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_PALETTE) - png_set_filler(png, 0xFF, PNG_FILLER_AFTER); - - if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA) - png_set_gray_to_rgb(png); - - png_read_update_info(png, png_info); - - const auto rowbytes = png_get_rowbytes(png, png_info); - const auto image_data = (png_byte *)malloc(rowbytes * height * sizeof(png_byte)); - - const auto row_pointers = (png_byte **)malloc(height * sizeof(png_byte *)); - for (int i = 0; i < height; i++) { - row_pointers[i] = image_data + i * rowbytes; - } - - png_read_image(png, row_pointers); - - png_destroy_read_struct(&png, &png_info, nullptr); - free(row_pointers); - fclose(fp); - - return {width, height, image_data}; -} diff --git a/apps/opengl_demo/png_helpers.h b/apps/opengl_demo/png_helpers.h deleted file mode 100644 index da1791526e00..000000000000 --- a/apps/opengl_demo/png_helpers.h +++ /dev/null @@ -1,15 +0,0 @@ -#ifndef _PNG_HELPERS_ -#define _PNG_HELPERS_ - -namespace PNGHelpers { - -struct image_info { - unsigned int width; - unsigned int height; - const uint8_t *data; -}; - -struct image_info load(const std::string &filepath); -} // namespace PNGHelpers - -#endif diff --git a/apps/opengl_demo/sample_filter_generator.cpp b/apps/opengl_demo/sample_filter_generator.cpp deleted file mode 100644 index 4bf30eaf641b..000000000000 --- a/apps/opengl_demo/sample_filter_generator.cpp +++ /dev/null @@ -1,24 +0,0 @@ -#include "Halide.h" - -class SampleFilter : public Halide::Generator { -public: - Input> input{"input", 3}; - Output> output{"output", 3}; - - void generate() { - Var x, y, c; - - output(x, y, c) = select(c == 3, input(x, y, c), cast(255.0f - input(x, y, c))); - - input.dim(0).set_stride(4).dim(2).set_stride(1).set_bounds(0, 4); - - output.dim(0).set_stride(4).dim(2).set_stride(1); - output.bound(c, 0, 4); - - if (get_target().has_feature(Target::OpenGL)) { - output.glsl(x, y, c); - } - } -}; - -HALIDE_REGISTER_GENERATOR(SampleFilter, sample_filter) diff --git a/apps/opengl_demo/timer.cpp b/apps/opengl_demo/timer.cpp deleted file mode 100644 index 2cd243a323ab..000000000000 --- a/apps/opengl_demo/timer.cpp +++ /dev/null @@ -1,24 +0,0 @@ -#include -#include - -#include "timer.h" - -using namespace Timer; - -struct info Timer::start(const std::string &what) { - struct info info { - what - }; - std::cerr << "\n-------------- Starting " << info.what << "\n"; - info.time = std::chrono::high_resolution_clock::now(); - return info; -} - -std::string Timer::report(const struct info &info) { - const auto end_time = std::chrono::high_resolution_clock::now(); - const auto ms = std::chrono::duration(end_time - info.time).count(); - std::stringstream report; - report << info.what << ": " << ms << "ms"; - std::cerr << "-------------- Finished " << report.str() << "\n"; - return report.str(); -} diff --git a/apps/opengl_demo/timer.h b/apps/opengl_demo/timer.h deleted file mode 100644 index 596e5c78fe55..000000000000 --- a/apps/opengl_demo/timer.h +++ /dev/null @@ -1,16 +0,0 @@ -#ifndef _TIMER_H_ -#define _TIMER_H_ - -#include - -namespace Timer { -struct info { - const std::string what; - std::chrono::time_point time; -}; - -struct info start(const std::string &what); -std::string report(const struct info &); -} // namespace Timer - -#endif diff --git a/apps/resnet_50/Makefile b/apps/resnet_50/Makefile index 188620382940..3d1dd30c9ce8 100644 --- a/apps/resnet_50/Makefile +++ b/apps/resnet_50/Makefile @@ -13,7 +13,7 @@ $(BIN)/%/pytorch_weights/ok: $(GENERATOR_BIN)/resnet50.generator: Resnet50Generator.cpp $(GENERATOR_DEPS) @mkdir -p $(@D) - $(CXX) $(CXXFLAGS) -g -fno-rtti $(filter %.cpp,$^) -o $@ $(LIBHALIDE_LDFLAGS) + $(CXX) $(CXXFLAGS) -g -fno-rtti $(filter %.cpp,$^) -o $@ $(LIBHALIDE_LDFLAGS) $(BIN)/%/resnet50.a: $(GENERATOR_BIN)/resnet50.generator @mkdir -p $(@D) @@ -21,7 +21,7 @@ $(BIN)/%/resnet50.a: $(GENERATOR_BIN)/resnet50.generator $(BIN)/%/process: process.cpp $(BIN)/%/resnet50.a @mkdir -p $(@D) - $(CXX) $(CXXFLAGS) -I$(BIN)/$* -Wall $^ -o $@ $(LDFLAGS) $(IMAGE_IO_FLAGS) $(CUDA_LDFLAGS) $(OPENCL_LDFLAGS) $(OPENGL_LDFLAGS) + $(CXX) $(CXXFLAGS) -I$(BIN)/$* -Wall $^ -o $@ $(LDFLAGS) $(IMAGE_IO_FLAGS) $(CUDA_LDFLAGS) $(OPENCL_LDFLAGS) benchmark_and_validate: $(BIN)/$(HL_TARGET)/process $(BIN)/$(HL_TARGET)/pytorch_weights/ok $< 10 $* $(BIN)/$(HL_TARGET)/pytorch_weights/ $(SEED) $(BIN)/$(HL_TARGET)/res50gen_output.bin diff --git a/apps/stencil_chain/Makefile b/apps/stencil_chain/Makefile index 91750f988869..116922d03095 100644 --- a/apps/stencil_chain/Makefile +++ b/apps/stencil_chain/Makefile @@ -6,7 +6,7 @@ build: $(BIN)/$(HL_TARGET)/process $(GENERATOR_BIN)/stencil_chain.generator: stencil_chain_generator.cpp $(GENERATOR_DEPS) @mkdir -p $(@D) - $(CXX) $(CXXFLAGS) $(filter %.cpp,$^) -o $@ $(LIBHALIDE_LDFLAGS) + $(CXX) $(CXXFLAGS) $(filter %.cpp,$^) -o $@ $(LIBHALIDE_LDFLAGS) $(BIN)/%/stencil_chain.a: $(GENERATOR_BIN)/stencil_chain.generator @mkdir -p $(@D) @@ -18,7 +18,7 @@ $(BIN)/%/stencil_chain_auto_schedule.a: $(GENERATOR_BIN)/stencil_chain.generator $(BIN)/%/process: process.cpp $(BIN)/%/stencil_chain.a $(BIN)/%/stencil_chain_auto_schedule.a @mkdir -p $(@D) - $(CXX) $(CXXFLAGS) -I$(BIN)/$* -Wall $^ -o $@ $(LDFLAGS) $(IMAGE_IO_FLAGS) $(CUDA_LDFLAGS) $(OPENCL_LDFLAGS) $(OPENGL_LDFLAGS) + $(CXX) $(CXXFLAGS) -I$(BIN)/$* -Wall $^ -o $@ $(LDFLAGS) $(IMAGE_IO_FLAGS) $(CUDA_LDFLAGS) $(OPENCL_LDFLAGS) $(BIN)/%/out.png: $(BIN)/%/process @mkdir -p $(@D) diff --git a/apps/support/Makefile.inc b/apps/support/Makefile.inc index c72bf1c36735..8f647457dd9f 100644 --- a/apps/support/Makefile.inc +++ b/apps/support/Makefile.inc @@ -98,7 +98,6 @@ ANDROID_API_VERSION ?= 26 CXX-host ?= $(CXX) CXX-host-opencl ?= $(CXX) -CXX-host-opengl ?= $(CXX) CXX-host-cuda ?= $(CXX) CXX-host-metal ?= $(CXX) CXX-host-hvx_128 ?= $(CXX) @@ -111,7 +110,6 @@ CXX-arm-32-profile-android ?= $(CXX-arm-32-android) CXXFLAGS-host ?= $(CXXFLAGS) CXXFLAGS-host-opencl ?= $(CXXFLAGS) -CXXFLAGS-host-opengl ?= $(CXXFLAGS) CXXFLAGS-host-cuda ?= $(CXXFLAGS) CXXFLAGS-host-metal ?= $(CXXFLAGS) CXXFLAGS-host-hvx_128 ?= $(CXXFLAGS) @@ -121,7 +119,6 @@ CXXFLAGS-arm-32-android ?= $(CXXFLAGS) LDFLAGS-host ?= $(LDFLAGS) LDFLAGS-host-opencl ?= $(LDFLAGS) -LDFLAGS-host-opengl ?= $(LDFLAGS) LDFLAGS-host-cuda ?= $(LDFLAGS) LDFLAGS-host-metal ?= $(LDFLAGS) LDFLAGS-host-hvx_128 ?= $(LDFLAGS) @@ -185,15 +182,6 @@ IMAGE_IO_CXX_FLAGS = $(LIBPNG_CXX_FLAGS) $(LIBJPEG_CXX_FLAGS) IMAGE_IO_FLAGS = $(IMAGE_IO_LIBS) $(IMAGE_IO_CXX_FLAGS) -PLATFORM_OPENGL_LDFLAGS=-lGL -lX11 -ifeq ($(UNAME), Darwin) -PLATFORM_OPENGL_LDFLAGS=-framework OpenGL -endif - -ifneq (, $(findstring opengl,$(HL_TARGET))) - OPENGL_LDFLAGS=$(PLATFORM_OPENGL_LDFLAGS) -endif - ifneq (, $(findstring metal,$(HL_TARGET))) LDFLAGS += -framework Metal -framework Foundation endif diff --git a/apps/unsharp/Makefile b/apps/unsharp/Makefile index 1accb3c498ea..fa912ad172e1 100644 --- a/apps/unsharp/Makefile +++ b/apps/unsharp/Makefile @@ -22,7 +22,7 @@ $(BIN)/%/runtime.a: $(GENERATOR_BIN)/unsharp.generator $(BIN)/%/filter: filter.cpp $(BIN)/%/unsharp.a $(BIN)/%/unsharp_auto_schedule.a $(BIN)/%/runtime.a @mkdir -p $(@D) - $(CXX) $(CXXFLAGS) -I$(BIN)/$* -Wall -O3 $^ -o $@ $(LDFLAGS) $(IMAGE_IO_FLAGS) $(CUDA_LDFLAGS) $(OPENCL_LDFLAGS) $(OPENGL_LDFLAGS) + $(CXX) $(CXXFLAGS) -I$(BIN)/$* -Wall -O3 $^ -o $@ $(LDFLAGS) $(IMAGE_IO_FLAGS) $(CUDA_LDFLAGS) $(OPENCL_LDFLAGS) $(BIN)/%/out.png: $(BIN)/%/filter $< ../images/rgba.png $(BIN)/$*/out.png diff --git a/cmake/HalideGeneratorHelpers.cmake b/cmake/HalideGeneratorHelpers.cmake index 220f1f56ceb8..29b069af0d94 100644 --- a/cmake/HalideGeneratorHelpers.cmake +++ b/cmake/HalideGeneratorHelpers.cmake @@ -342,7 +342,8 @@ function(_Halide_add_targets_to_runtime TARGET) endfunction() function(_Halide_target_link_gpu_libs TARGET VISIBILITY) - if ("${ARGN}" MATCHES "opengl") + # TODO: verify that this is correct & necessary for OpenGLCompute + if ("${ARGN}" MATCHES "openglcompute") if ("${ARGN}" MATCHES "egl") find_package(OpenGL REQUIRED COMPONENTS OpenGL EGL) target_link_libraries(${TARGET} ${VISIBILITY} OpenGL::OpenGL OpenGL::EGL) diff --git a/dependencies/CMakeLists.txt b/dependencies/CMakeLists.txt index 3fc8ba95ce6e..98803af0bdbc 100644 --- a/dependencies/CMakeLists.txt +++ b/dependencies/CMakeLists.txt @@ -6,6 +6,7 @@ set(THREADS_PREFER_PTHREAD_FLAG YES) find_package(Threads REQUIRED) set_target_properties(Threads::Threads PROPERTIES IMPORTED_GLOBAL TRUE) +# TODO: verify this is still correct / necessary for OpenGLCompute find_package(OpenGL) if (TARGET OpenGL::GL) set_target_properties(OpenGL::GL PROPERTIES IMPORTED_GLOBAL TRUE) @@ -18,7 +19,7 @@ endif () ## # Third-party dependencies in their own subdirectories -## +## add_subdirectory(llvm) diff --git a/python_bindings/correctness/target.py b/python_bindings/correctness/target.py index b54fb2984969..3f8e8347b23b 100644 --- a/python_bindings/correctness/target.py +++ b/python_bindings/correctness/target.py @@ -46,10 +46,10 @@ def test_target(): # Full specification round-trip, crazy features t1 = hl.Target(hl.TargetOS.Android, hl.TargetArch.ARM, 32, [hl.TargetFeature.JIT, hl.TargetFeature.SSE41, hl.TargetFeature.AVX, hl.TargetFeature.AVX2, - hl.TargetFeature.CUDA, hl.TargetFeature.OpenCL, hl.TargetFeature.OpenGL, hl.TargetFeature.OpenGLCompute, + hl.TargetFeature.CUDA, hl.TargetFeature.OpenCL, hl.TargetFeature.OpenGLCompute, hl.TargetFeature.Debug]) ts = t1.to_string() - assert ts == "arm-32-android-avx-avx2-cuda-debug-jit-opencl-opengl-openglcompute-sse41" + assert ts == "arm-32-android-avx-avx2-cuda-debug-jit-opencl-openglcompute-sse41" assert hl.Target.validate_target_string(ts) # Expected failures: diff --git a/python_bindings/src/PyEnums.cpp b/python_bindings/src/PyEnums.cpp index c64352f73101..b47cd3e761a9 100644 --- a/python_bindings/src/PyEnums.cpp +++ b/python_bindings/src/PyEnums.cpp @@ -15,7 +15,6 @@ void define_enums(py::module &m) { .value("Default_GPU", DeviceAPI::Default_GPU) .value("CUDA", DeviceAPI::CUDA) .value("OpenCL", DeviceAPI::OpenCL) - .value("GLSL", DeviceAPI::GLSL) .value("OpenGLCompute", DeviceAPI::OpenGLCompute) .value("Metal", DeviceAPI::Metal) .value("Hexagon", DeviceAPI::Hexagon); @@ -106,7 +105,6 @@ void define_enums(py::module &m) { .value("CLDoubles", Target::Feature::CLDoubles) .value("CLHalf", Target::Feature::CLHalf) .value("CLAtomics64", Target::Feature::CLAtomics64) - .value("OpenGL", Target::Feature::OpenGL) .value("OpenGLCompute", Target::Feature::OpenGLCompute) .value("EGL", Target::Feature::EGL) .value("UserContext", Target::Feature::UserContext) diff --git a/python_bindings/src/PyFunc.cpp b/python_bindings/src/PyFunc.cpp index 4cf8eb6736e2..2bb24193a962 100644 --- a/python_bindings/src/PyFunc.cpp +++ b/python_bindings/src/PyFunc.cpp @@ -342,10 +342,6 @@ void define_func(py::module &m) { .def("bound_extent", &Func::bound_extent, py::arg("var"), py::arg("extent")) - .def("shader", &Func::shader, py::arg("x"), py::arg("y"), py::arg("c"), py::arg("device_api")) - - .def("glsl", &Func::glsl, py::arg("x"), py::arg("y"), py::arg("c")) - .def("align_storage", &Func::align_storage, py::arg("dim"), py::arg("alignment")) .def("fold_storage", &Func::fold_storage, py::arg("dim"), py::arg("extent"), py::arg("fold_forward") = true) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 29458c7db0d9..0b45adf43715 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -81,7 +81,6 @@ set(HEADER_FILES ImageParam.h InferArguments.h InjectHostDevBufferCopies.h - InjectOpenGLIntrinsics.h Inline.h InlineReductions.h IntegerDivisionTable.h @@ -163,7 +162,6 @@ set(HEADER_FILES UnsafePromises.h Util.h Var.h - VaryingAttributes.h VectorizeLoops.h WasmExecutor.h WrapCalls.h @@ -241,7 +239,6 @@ set(SOURCE_FILES ImageParam.cpp InferArguments.cpp InjectHostDevBufferCopies.cpp - InjectOpenGLIntrinsics.cpp Inline.cpp InlineReductions.cpp IntegerDivisionTable.cpp @@ -335,7 +332,6 @@ set(SOURCE_FILES UnsafePromises.cpp Util.cpp Var.cpp - VaryingAttributes.cpp VectorizeLoops.cpp WasmExecutor.cpp WrapCalls.cpp @@ -454,11 +450,6 @@ if (TARGET_OPENCL) target_compile_definitions(Halide PRIVATE WITH_OPENCL) endif () -option(TARGET_OPENGL "Include OpenGL/GLSL target" ON) -if (TARGET_OPENGL) - target_compile_definitions(Halide PRIVATE WITH_OPENGL) -endif () - option(TARGET_METAL "Include Metal target" ON) if (TARGET_METAL) target_compile_definitions(Halide PRIVATE WITH_METAL) diff --git a/src/CodeGen_C.cpp b/src/CodeGen_C.cpp index 1ef6ea044168..b5f265b9f493 100644 --- a/src/CodeGen_C.cpp +++ b/src/CodeGen_C.cpp @@ -29,7 +29,6 @@ extern "C" unsigned char halide_internal_runtime_header_HalideRuntimeHexagonHost extern "C" unsigned char halide_internal_runtime_header_HalideRuntimeMetal_h[]; extern "C" unsigned char halide_internal_runtime_header_HalideRuntimeOpenCL_h[]; extern "C" unsigned char halide_internal_runtime_header_HalideRuntimeOpenGLCompute_h[]; -extern "C" unsigned char halide_internal_runtime_header_HalideRuntimeOpenGL_h[]; extern "C" unsigned char halide_internal_runtime_header_HalideRuntimeQurt_h[]; extern "C" unsigned char halide_internal_runtime_header_HalideRuntimeD3D12Compute_h[]; @@ -418,9 +417,6 @@ CodeGen_C::~CodeGen_C() { if (target.has_feature(Target::OpenGLCompute)) { stream << halide_internal_runtime_header_HalideRuntimeOpenGLCompute_h << "\n"; } - if (target.has_feature(Target::OpenGL)) { - stream << halide_internal_runtime_header_HalideRuntimeOpenGL_h << "\n"; - } if (target.has_feature(Target::D3D12Compute)) { stream << halide_internal_runtime_header_HalideRuntimeD3D12Compute_h << "\n"; } diff --git a/src/CodeGen_GPU_Host.cpp b/src/CodeGen_GPU_Host.cpp index d2aa48596f19..8fbddacdc77c 100644 --- a/src/CodeGen_GPU_Host.cpp +++ b/src/CodeGen_GPU_Host.cpp @@ -16,7 +16,6 @@ #include "LLVM_Headers.h" #include "Simplify.h" #include "Util.h" -#include "VaryingAttributes.h" namespace Halide { namespace Internal { @@ -98,13 +97,9 @@ template CodeGen_GPU_Host::CodeGen_GPU_Host(Target target) : CodeGen_CPU(target) { // For the default GPU, the order of preferences is: Metal, - // OpenCL, CUDA, OpenGLCompute, and OpenGL last. + // OpenCL, CUDA, OpenGLCompute last. // The code is in reverse order to allow later tests to override // earlier ones. - if (target.has_feature(Target::OpenGL)) { - debug(1) << "Constructing OpenGL device codegen\n"; - cgdev[DeviceAPI::GLSL] = std::make_unique(target); - } if (target.has_feature(Target::OpenGLCompute)) { debug(1) << "Constructing OpenGL Compute device codegen\n"; cgdev[DeviceAPI::OpenGLCompute] = new_CodeGen_OpenGLCompute_Dev(target); @@ -250,77 +245,31 @@ void CodeGen_GPU_Host::visit(const For *loop) { Value *gpu_num_coords_dim0 = zero_int32; Value *gpu_num_coords_dim1 = zero_int32; - if (loop->device_api == DeviceAPI::GLSL) { - - // GL draw calls that invoke the GLSL shader are issued for pairs of - // for-loops over spatial x and y dimensions. For each for-loop we create - // one scalar vertex attribute for the spatial dimension corresponding to - // that loop, plus one scalar attribute for each expression previously - // labeled as "glsl_varying" - - // Pass variables created during setup_gpu_vertex_buffer to the - // dev run function call. - gpu_num_padded_attributes = codegen(Variable::make(Int(32), "glsl.num_padded_attributes")); - gpu_num_coords_dim0 = codegen(Variable::make(Int(32), "glsl.num_coords_dim0")); - gpu_num_coords_dim1 = codegen(Variable::make(Int(32), "glsl.num_coords_dim1")); - - // Look up the allocation for the vertex buffer and cast it to the - // right type - gpu_vertex_buffer = codegen(Variable::make(type_of(), "glsl.vertex_buffer")); - gpu_vertex_buffer = builder->CreatePointerCast(gpu_vertex_buffer, - CodeGen_LLVM::f32_t->getPointerTo()); - } - // compute a closure over the state passed into the kernel HostClosure c(loop->body, loop->name); // Determine the arguments that must be passed into the halide function vector closure_args = c.arguments(); - // Halide allows passing of scalar float and integer arguments. For - // OpenGL, pack these into vec4 uniforms and varying attributes - if (loop->device_api == DeviceAPI::GLSL) { - - int num_uniform_floats = 0; - - // The spatial x and y coordinates are passed in the first two - // scalar float varying slots - int num_varying_floats = 2; - int num_uniform_ints = 0; - - // Pack scalar parameters into vec4 - for (size_t i = 0; i < closure_args.size(); i++) { - if (closure_args[i].is_buffer) { - continue; - } else if (ends_with(closure_args[i].name, ".varying")) { - closure_args[i].packed_index = num_varying_floats++; - } else if (closure_args[i].type.is_float()) { - closure_args[i].packed_index = num_uniform_floats++; - } else if (closure_args[i].type.is_int()) { - closure_args[i].packed_index = num_uniform_ints++; - } - } - } else { - // Sort the args by the size of the underlying type. This is - // helpful for avoiding struct-packing ambiguities in metal, - // which passes the scalar args as a struct. - std::sort(closure_args.begin(), closure_args.end(), - [](const DeviceArgument &a, const DeviceArgument &b) { - if (a.is_buffer == b.is_buffer) { - return a.type.bits() > b.type.bits(); - } else { - // Ensure that buffer arguments come first: - // for many OpenGL/Compute systems, the - // legal indices for buffer args are much - // more restrictive than for scalar args, - // and scalar args can be 'grown' by - // LICM. Putting buffers first makes it much - // more likely we won't fail on some - // hardware. - return a.is_buffer > b.is_buffer; - } - }); - } + // Sort the args by the size of the underlying type. This is + // helpful for avoiding struct-packing ambiguities in metal, + // which passes the scalar args as a struct. + std::sort(closure_args.begin(), closure_args.end(), + [](const DeviceArgument &a, const DeviceArgument &b) { + if (a.is_buffer == b.is_buffer) { + return a.type.bits() > b.type.bits(); + } else { + // Ensure that buffer arguments come first: + // for many OpenGL/Compute systems, the + // legal indices for buffer args are much + // more restrictive than for scalar args, + // and scalar args can be 'grown' by + // LICM. Putting buffers first makes it much + // more likely we won't fail on some + // hardware. + return a.is_buffer > b.is_buffer; + } + }); for (size_t i = 0; i < closure_args.size(); i++) { if (closure_args[i].is_buffer && allocations.contains(closure_args[i].name)) { diff --git a/src/CodeGen_Internal.cpp b/src/CodeGen_Internal.cpp index 8724784d2986..ef6406408ee0 100644 --- a/src/CodeGen_Internal.cpp +++ b/src/CodeGen_Internal.cpp @@ -222,7 +222,6 @@ bool function_takes_user_context(const std::string &name) { "halide_memoization_cache_release", "halide_cuda_run", "halide_opencl_run", - "halide_opengl_run", "halide_openglcompute_run", "halide_metal_run", "halide_d3d12compute_run", @@ -246,7 +245,6 @@ bool function_takes_user_context(const std::string &name) { "halide_vtcm_free", "halide_cuda_initialize_kernels", "halide_opencl_initialize_kernels", - "halide_opengl_initialize_kernels", "halide_openglcompute_initialize_kernels", "halide_metal_initialize_kernels", "halide_d3d12compute_initialize_kernels", diff --git a/src/CodeGen_LLVM.cpp b/src/CodeGen_LLVM.cpp index 04d94806585b..6f73b71542c3 100644 --- a/src/CodeGen_LLVM.cpp +++ b/src/CodeGen_LLVM.cpp @@ -240,7 +240,6 @@ std::unique_ptr CodeGen_LLVM::new_for_target(const Target &target, // The awkward mapping from targets to code generators if (target.features_any_of({Target::CUDA, Target::OpenCL, - Target::OpenGL, Target::OpenGLCompute, Target::Metal, Target::D3D12Compute})) { diff --git a/src/CodeGen_OpenGLCompute_Dev.cpp b/src/CodeGen_OpenGLCompute_Dev.cpp index fe07898a07c3..5b6ab04ca9ae 100644 --- a/src/CodeGen_OpenGLCompute_Dev.cpp +++ b/src/CodeGen_OpenGLCompute_Dev.cpp @@ -7,7 +7,6 @@ #include "IRMutator.h" #include "IROperator.h" #include "Simplify.h" -#include "VaryingAttributes.h" #include #include #include diff --git a/src/CodeGen_OpenGL_Dev.cpp b/src/CodeGen_OpenGL_Dev.cpp index 333d837eb64b..496f58d902d0 100644 --- a/src/CodeGen_OpenGL_Dev.cpp +++ b/src/CodeGen_OpenGL_Dev.cpp @@ -6,7 +6,6 @@ #include "IRMutator.h" #include "IROperator.h" #include "Simplify.h" -#include "VaryingAttributes.h" #include #include #include diff --git a/src/DeviceAPI.h b/src/DeviceAPI.h index ab132e091f8d..e75711592558 100644 --- a/src/DeviceAPI.h +++ b/src/DeviceAPI.h @@ -18,7 +18,6 @@ enum class DeviceAPI { Default_GPU, CUDA, OpenCL, - GLSL, OpenGLCompute, Metal, Hexagon, @@ -33,7 +32,6 @@ const DeviceAPI all_device_apis[] = {DeviceAPI::None, DeviceAPI::Default_GPU, DeviceAPI::CUDA, DeviceAPI::OpenCL, - DeviceAPI::GLSL, DeviceAPI::OpenGLCompute, DeviceAPI::Metal, DeviceAPI::Hexagon, diff --git a/src/DeviceInterface.cpp b/src/DeviceInterface.cpp index 3285d7f3b3ac..4d72805e153a 100644 --- a/src/DeviceInterface.cpp +++ b/src/DeviceInterface.cpp @@ -96,8 +96,6 @@ const halide_device_interface_t *get_device_interface_for_device_api(DeviceAPI d name = "cuda"; } else if (d == DeviceAPI::OpenGLCompute) { name = "openglcompute"; - } else if (d == DeviceAPI::GLSL) { - name = "opengl"; } else if (d == DeviceAPI::HexagonDma) { name = "hexagon_dma"; } else if (d == DeviceAPI::D3D12Compute) { @@ -152,8 +150,6 @@ DeviceAPI get_default_device_api_for_target(const Target &target) { return DeviceAPI::CUDA; } else if (target.has_feature(Target::OpenGLCompute)) { return DeviceAPI::OpenGLCompute; - } else if (target.has_feature(Target::OpenGL)) { - return DeviceAPI::GLSL; } else if (target.has_feature(Target::HexagonDma)) { return DeviceAPI::HexagonDma; } else if (target.has_feature(Target::D3D12Compute)) { @@ -184,9 +180,6 @@ Expr make_device_interface_call(DeviceAPI device_api, MemoryType memory_type) { case DeviceAPI::Metal: interface_name = "halide_metal_device_interface"; break; - case DeviceAPI::GLSL: - interface_name = "halide_opengl_device_interface"; - break; case DeviceAPI::OpenGLCompute: interface_name = "halide_openglcompute_device_interface"; break; diff --git a/src/Func.cpp b/src/Func.cpp index 0cbb5a563c2d..cc6ab6d2ea21 100644 --- a/src/Func.cpp +++ b/src/Func.cpp @@ -2454,35 +2454,6 @@ Func &Func::gpu_tile(const VarOrRVar &x, const VarOrRVar &y, const VarOrRVar &z, return *this; } -Func &Func::shader(const Var &x, const Var &y, const Var &c, DeviceAPI device_api) { - invalidate_cache(); - - reorder(c, x, y); - // GLSL outputs must be stored interleaved - reorder_storage(c, x, y); - - // TODO: Set appropriate constraints if this is the output buffer? - - Stage(func, func.definition(), 0).gpu_blocks(x, y, device_api); - - bool constant_bounds = false; - FuncSchedule &sched = func.schedule(); - for (size_t i = 0; i < sched.bounds().size(); i++) { - if (c.name() == sched.bounds()[i].var) { - constant_bounds = is_const(sched.bounds()[i].min) && - is_const(sched.bounds()[i].extent); - break; - } - } - user_assert(constant_bounds) - << "The color channel for image loops must have constant bounds, e.g., .bound(c, 0, 3).\n"; - return *this; -} - -Func &Func::glsl(const Var &x, const Var &y, const Var &c) { - return shader(x, y, c, DeviceAPI::GLSL).vectorize(c); -} - Func &Func::hexagon(const VarOrRVar &x) { invalidate_cache(); Stage(func, func.definition(), 0).hexagon(x); diff --git a/src/Func.h b/src/Func.h index a0c3e82e242a..d6cb36e2f35e 100644 --- a/src/Func.h +++ b/src/Func.h @@ -1974,16 +1974,6 @@ class Func { DeviceAPI device_api = DeviceAPI::Default_GPU); // @} - /** Schedule for execution using coordinate-based hardware api. - * GLSL is an example of this. Conceptually, this is - * similar to parallelization over 'x' and 'y' (since GLSL shaders compute - * individual output pixels in parallel) and vectorization over 'c' - * (since GLSL/RS implicitly vectorizes the color channel). */ - Func &shader(const Var &x, const Var &y, const Var &c, DeviceAPI device_api); - - /** Schedule for execution as GLSL kernel. */ - Func &glsl(const Var &x, const Var &y, const Var &c); - /** Schedule for execution on Hexagon. When a loop is marked with * Hexagon, that loop is executed on a Hexagon DSP. */ Func &hexagon(const VarOrRVar &x = Var::outermost()); diff --git a/src/FuseGPUThreadLoops.cpp b/src/FuseGPUThreadLoops.cpp index 9faf0d7a41df..6b1798b25528 100644 --- a/src/FuseGPUThreadLoops.cpp +++ b/src/FuseGPUThreadLoops.cpp @@ -1442,10 +1442,6 @@ class FuseGPUThreadLoops : public IRMutator { using IRMutator::visit; Stmt visit(const For *op) override { - if (op->device_api == DeviceAPI::GLSL) { - return op; - } - user_assert(!(CodeGen_GPU_Dev::is_gpu_thread_var(op->name))) << "Loops over GPU thread variable: \"" << op->name << "\" is outside of any loop over a GPU block variable. " diff --git a/src/Generator.h b/src/Generator.h index bc60b7ff4fb5..8edec2015961 100644 --- a/src/Generator.h +++ b/src/Generator.h @@ -2220,7 +2220,6 @@ class GeneratorOutputBase : public GIOBase { HALIDE_FORWARD_METHOD_CONST(Func, defined) HALIDE_FORWARD_METHOD(Func, fold_storage) HALIDE_FORWARD_METHOD(Func, fuse) - HALIDE_FORWARD_METHOD(Func, glsl) HALIDE_FORWARD_METHOD(Func, gpu) HALIDE_FORWARD_METHOD(Func, gpu_blocks) HALIDE_FORWARD_METHOD(Func, gpu_single_thread) @@ -2242,7 +2241,6 @@ class GeneratorOutputBase : public GIOBase { HALIDE_FORWARD_METHOD_CONST(Func, rvars) HALIDE_FORWARD_METHOD(Func, serial) HALIDE_FORWARD_METHOD(Func, set_estimate) - HALIDE_FORWARD_METHOD(Func, shader) HALIDE_FORWARD_METHOD(Func, specialize) HALIDE_FORWARD_METHOD(Func, specialize_fail) HALIDE_FORWARD_METHOD(Func, split) diff --git a/src/IRPrinter.cpp b/src/IRPrinter.cpp index 79318513ca31..240005b9c8a8 100644 --- a/src/IRPrinter.cpp +++ b/src/IRPrinter.cpp @@ -93,9 +93,6 @@ ostream &operator<<(ostream &out, const DeviceAPI &api) { case DeviceAPI::OpenGLCompute: out << ""; break; - case DeviceAPI::GLSL: - out << ""; - break; case DeviceAPI::Metal: out << ""; break; diff --git a/src/InjectOpenGLIntrinsics.cpp b/src/InjectOpenGLIntrinsics.cpp deleted file mode 100644 index b9e1d8c3fa46..000000000000 --- a/src/InjectOpenGLIntrinsics.cpp +++ /dev/null @@ -1,111 +0,0 @@ -#include "InjectOpenGLIntrinsics.h" -#include "CodeGen_GPU_Dev.h" -#include "FuseGPUThreadLoops.h" -#include "IRMutator.h" -#include "IROperator.h" -#include "Scope.h" -#include "Substitute.h" - -namespace Halide { -namespace Internal { - -using std::string; -using std::vector; - -namespace { - -/** Normalizes image loads/stores and produces glsl_texture_load/stores. */ -class InjectOpenGLIntrinsics : public IRMutator { -public: - InjectOpenGLIntrinsics() = default; - Scope scope; - bool inside_kernel_loop = false; - -private: - using IRMutator::visit; - - Expr visit(const Call *call) override { - if (call->is_intrinsic(Call::image_load)) { - vector call_args = call->args; - // - // Create - // glsl_texture_load("name", - // name.buffer, - // (x - x_min + 0.5)/x_extent, - // (y - y_min + 0.5)/y_extent, - // c) - // from - // image_load("name", - // name.buffer, - // x - x_min, x_extent, - // y - y_min, y_extent, - // c - c_min, c_extent - // ) - // - int dims = (call_args.size() - 2) / 2; - internal_assert(dims >= 1 && dims <= 3); - - vector args(5); - args[0] = call_args[0]; // "name" - args[1] = call_args[1]; // name.buffer - - // Normalize first two coordinates. - for (int i = 0; i < std::min(dims, 2); i++) { - int to_index = 2 + i; - int from_index = 2 + i * 2; - args[to_index] = - (Cast::make(Float(32), mutate(call_args[from_index])) + 0.5f) / - mutate(call_args[from_index + 1]); - } - - if (dims < 3) { - args[3] = FloatImm::make(Float(32), 0.5f); - args[4] = IntImm::make(Int(32), 0); - } else { - // Confirm that user explicitly specified constant value for min - // value of c dimension for ImageParams accessed by GLSL-based filters. - if (call->param.defined()) { - bool const_min_constraint = - call->param.min_constraint(2).defined() && - is_const(call->param.min_constraint(2)); - user_assert(const_min_constraint) - << "GLSL: Requires minimum for c-dimension set to constant " - << "for ImageParam '" << args[0] << "'. " - << "Call set_min(2, min) or set_bounds(2, min, extent) to set.\n"; - } - - Expr c_coordinate = mutate(call_args[2 + 2 * 2]); - args[4] = c_coordinate; - } - - return Call::make(call->type, Call::glsl_texture_load, - vector(&args[0], &args[5]), - Call::Intrinsic, FunctionPtr(), 0, - call->image, call->param); - } else if (call->is_intrinsic(Call::image_store)) { - user_assert(call->args.size() == 6) - << "GLSL stores require three coordinates.\n"; - - // Create - // gl_texture_store(name, name.buffer, x, y, c, value) - // out of - // image_store(name, name.buffer, x, y, c, value) - vector args(call->args); - args[5] = mutate(call->args[5]); // mutate value - return Call::make(call->type, Call::glsl_texture_store, - args, Call::Intrinsic); - } else { - return IRMutator::visit(call); - } - } -}; - -} // namespace - -Stmt inject_opengl_intrinsics(const Stmt &s) { - InjectOpenGLIntrinsics gl; - return gl.mutate(s); -} - -} // namespace Internal -} // namespace Halide diff --git a/src/InjectOpenGLIntrinsics.h b/src/InjectOpenGLIntrinsics.h deleted file mode 100644 index 3fcf9875d024..000000000000 --- a/src/InjectOpenGLIntrinsics.h +++ /dev/null @@ -1,22 +0,0 @@ -#ifndef HALIDE_INJECT_OPENGL_INTRINSICS_H -#define HALIDE_INJECT_OPENGL_INTRINSICS_H - -/** \file - * Defines the lowering pass that injects texture loads and texture - * stores for opengl. - */ - -#include "Expr.h" - -namespace Halide { -namespace Internal { - -/** Take a statement with for kernel for loops and turn loads and - * stores inside the loops into OpenGL texture load and store - * intrinsics. Should only be run when the OpenGL target is active. */ -Stmt inject_opengl_intrinsics(const Stmt &s); - -} // namespace Internal -} // namespace Halide - -#endif diff --git a/src/JITModule.cpp b/src/JITModule.cpp index dd67ca1d2dcd..0e3837ec6913 100644 --- a/src/JITModule.cpp +++ b/src/JITModule.cpp @@ -624,14 +624,12 @@ enum RuntimeKind { OpenCL, Metal, CUDA, - OpenGL, // NOTE: this feature is deprecated and will be removed in Halide 12. OpenGLCompute, Hexagon, D3D12Compute, OpenCLDebug, MetalDebug, CUDADebug, - OpenGLDebug, // NOTE: this feature is deprecated and will be removed in Halide 12. OpenGLComputeDebug, HexagonDebug, D3D12ComputeDebug, @@ -668,7 +666,6 @@ JITModule &make_module(llvm::Module *for_module, Target target, one_gpu.set_feature(Target::Metal, false); one_gpu.set_feature(Target::CUDA, false); one_gpu.set_feature(Target::HVX, false); - one_gpu.set_feature(Target::OpenGL, false); one_gpu.set_feature(Target::OpenGLCompute, false); one_gpu.set_feature(Target::D3D12Compute, false); string module_name; @@ -702,17 +699,6 @@ JITModule &make_module(llvm::Module *for_module, Target target, one_gpu.set_feature(Target::CUDA); module_name += "cuda"; break; - case OpenGLDebug: - one_gpu.set_feature(Target::Debug); - one_gpu.set_feature(Target::OpenGL); - module_name = "debug_opengl"; - load_opengl(one_gpu.has_feature(Target::EGL)); - break; - case OpenGL: - one_gpu.set_feature(Target::OpenGL); - module_name += "opengl"; - load_opengl(one_gpu.has_feature(Target::EGL)); - break; case OpenGLComputeDebug: one_gpu.set_feature(Target::Debug); one_gpu.set_feature(Target::OpenGLCompute); @@ -874,13 +860,6 @@ std::vector JITSharedRuntime::get(llvm::Module *for_module, const Tar result.push_back(m); } } - if (target.has_feature(Target::OpenGL)) { - auto kind = target.has_feature(Target::Debug) ? OpenGLDebug : OpenGL; - JITModule m = make_module(for_module, target, kind, result, create); - if (m.compiled()) { - result.push_back(m); - } - } if (target.has_feature(Target::OpenGLCompute)) { auto kind = target.has_feature(Target::Debug) ? OpenGLComputeDebug : OpenGLCompute; JITModule m = make_module(for_module, target, kind, result, create); diff --git a/src/LICM.cpp b/src/LICM.cpp index 64360fd64b9d..bb80691f79b0 100644 --- a/src/LICM.cpp +++ b/src/LICM.cpp @@ -246,9 +246,6 @@ class LICM : public IRMutator { if (old_in_gpu_loop && in_gpu_loop) { // Don't lift lets to in-between gpu blocks/threads return IRMutator::visit(op); - } else if (op->device_api == DeviceAPI::GLSL) { - // GLSL uses magic names for varying things. Just skip LICM. - return IRMutator::visit(op); } else { // Lift invariants diff --git a/src/LLVM_Runtime_Linker.cpp b/src/LLVM_Runtime_Linker.cpp index 1360c2876b61..33b38bdbefe2 100644 --- a/src/LLVM_Runtime_Linker.cpp +++ b/src/LLVM_Runtime_Linker.cpp @@ -1089,22 +1089,6 @@ std::unique_ptr get_initial_module_for_target(Target t, llvm::LLVM modules.push_back(get_initmod_opencl(c, bits_64, debug)); } } - if (t.has_feature(Target::OpenGL)) { - modules.push_back(get_initmod_opengl(c, bits_64, debug)); - if (t.os == Target::Linux) { - if (t.has_feature(Target::EGL)) { - modules.push_back(get_initmod_opengl_egl_context(c, bits_64, debug)); - } else { - modules.push_back(get_initmod_opengl_glx_context(c, bits_64, debug)); - } - } else if (t.os == Target::OSX) { - modules.push_back(get_initmod_osx_opengl_context(c, bits_64, debug)); - } else if (t.os == Target::Android) { - modules.push_back(get_initmod_opengl_egl_context(c, bits_64, debug)); - } else { - // You're on your own to provide definitions of halide_opengl_get_proc_address and halide_opengl_create_context - } - } if (t.has_feature(Target::OpenGLCompute)) { modules.push_back(get_initmod_openglcompute(c, bits_64, debug)); if (t.os == Target::Android) { diff --git a/src/Lower.cpp b/src/Lower.cpp index a25d18de30b3..35f2096addd2 100644 --- a/src/Lower.cpp +++ b/src/Lower.cpp @@ -34,7 +34,6 @@ #include "IRPrinter.h" #include "InferArguments.h" #include "InjectHostDevBufferCopies.h" -#include "InjectOpenGLIntrinsics.h" #include "Inline.h" #include "LICM.h" #include "LoopCarry.h" @@ -68,7 +67,6 @@ #include "UnpackBuffers.h" #include "UnrollLoops.h" #include "UnsafePromises.h" -#include "VaryingAttributes.h" #include "VectorizeLoops.h" #include "WrapCalls.h" @@ -205,7 +203,6 @@ Module lower(const vector &output_funcs, bool will_inject_host_copies = (t.has_gpu_feature() || t.has_feature(Target::OpenGLCompute) || - t.has_feature(Target::OpenGL) || t.has_feature(Target::HexagonDma) || (t.arch != Target::Hexagon && (t.has_feature(Target::HVX)))); @@ -257,8 +254,7 @@ Module lower(const vector &output_funcs, // OpenGL relies on GPU var canonicalization occurring before // storage flattening. if (t.has_gpu_feature() || - t.has_feature(Target::OpenGLCompute) || - t.has_feature(Target::OpenGL)) { + t.has_feature(Target::OpenGLCompute)) { debug(1) << "Canonicalizing GPU var names...\n"; s = canonicalize_gpu_vars(s); debug(2) << "Lowering after canonicalizing GPU var names:\n" @@ -312,13 +308,6 @@ Module lower(const vector &output_funcs, << s << "\n\n"; } - if (t.has_feature(Target::OpenGL)) { - debug(1) << "Injecting OpenGL texture intrinsics...\n"; - s = inject_opengl_intrinsics(s); - debug(2) << "Lowering after OpenGL intrinsics:\n" - << s << "\n\n"; - } - debug(1) << "Simplifying...\n"; s = simplify(s); s = unify_duplicate_lets(s); @@ -416,18 +405,6 @@ Module lower(const vector &output_funcs, debug(1) << "Simplifying...\n"; s = common_subexpression_elimination(s); - if (t.has_feature(Target::OpenGL)) { - debug(1) << "Detecting varying attributes...\n"; - s = find_linear_expressions(s); - debug(2) << "Lowering after detecting varying attributes:\n" - << s << "\n\n"; - - debug(1) << "Moving varying attribute expressions out of the shader...\n"; - s = setup_gpu_vertex_buffer(s); - debug(2) << "Lowering after removing varying attributes:\n" - << s << "\n\n"; - } - debug(1) << "Lowering unsafe promises...\n"; s = lower_unsafe_promises(s, t); debug(2) << "Lowering after lowering unsafe promises:\n" diff --git a/src/Module.cpp b/src/Module.cpp index 5445fc721649..74a3714e4abd 100644 --- a/src/Module.cpp +++ b/src/Module.cpp @@ -557,10 +557,6 @@ std::map Module::get_metadata_name_map() const { void Module::compile(const std::map &output_files) const { validate_outputs(output_files); - if (target().has_feature(Target::OpenGL)) { - user_warning << "WARNING: OpenGL is deprecated in Halide 11 and will be removed in Halide 12.\n"; - } - // output stmt and html prior to resolving submodules. We need to // clear the output after writing it, otherwise the output will // be overwritten by recursive calls after submodules are resolved. diff --git a/src/PartitionLoops.cpp b/src/PartitionLoops.cpp index dfe9a30d1931..0a5381972000 100644 --- a/src/PartitionLoops.cpp +++ b/src/PartitionLoops.cpp @@ -496,12 +496,6 @@ class PartitionLoops : public IRMutator { return IRMutator::visit(op); } - // We shouldn't partition GLSL loops - they have control-flow - // constraints. - if (op->device_api == DeviceAPI::GLSL) { - return op; - } - // Find simplifications in this loop body FindSimplifications finder(op->name); body.accept(&finder); @@ -777,11 +771,6 @@ class RenormalizeGPULoops : public IRMutator { vector> lifted_lets; Stmt visit(const For *op) override { - if (op->device_api == DeviceAPI::GLSL) { - // The partitioner did not enter GLSL loops - return op; - } - bool old_in_gpu_loop = in_gpu_loop; Stmt stmt; diff --git a/src/Pipeline.cpp b/src/Pipeline.cpp index 349a94c22ea2..b46b879b88a6 100644 --- a/src/Pipeline.cpp +++ b/src/Pipeline.cpp @@ -1075,10 +1075,6 @@ void Pipeline::realize(RealizationArg outputs, const Target &t, Target target = t; user_assert(defined()) << "Can't realize an undefined Pipeline\n"; - if (t.has_feature(Target::OpenGL)) { - user_warning << "WARNING: OpenGL is deprecated in Halide 11 and will be removed in Halide 12.\n"; - } - debug(2) << "Realizing Pipeline for " << target << "\n"; if (target.has_unknowns()) { diff --git a/src/StorageFlattening.cpp b/src/StorageFlattening.cpp index d78662fe73b2..cefd83542911 100644 --- a/src/StorageFlattening.cpp +++ b/src/StorageFlattening.cpp @@ -398,15 +398,11 @@ class FlattenDimensions : public IRMutator { Stmt visit(const For *op) override { bool old_in_shader = in_shader; bool old_in_gpu = in_gpu; - if ((op->for_type == ForType::GPUBlock || - op->for_type == ForType::GPUThread) && - op->device_api == DeviceAPI::GLSL) { - in_shader = true; - } if (op->for_type == ForType::GPUBlock || op->for_type == ForType::GPUThread) { in_gpu = true; } + // TODO: in_shader = true is no longer possible, clean up code accordingly Stmt stmt = IRMutator::visit(op); in_shader = old_in_shader; in_gpu = old_in_gpu; diff --git a/src/Target.cpp b/src/Target.cpp index ac1e6548ce8b..3b2648dff035 100644 --- a/src/Target.cpp +++ b/src/Target.cpp @@ -330,7 +330,6 @@ const std::map feature_name_map = { {"cl_doubles", Target::CLDoubles}, {"cl_half", Target::CLHalf}, {"cl_atomics64", Target::CLAtomics64}, - {"opengl", Target::OpenGL}, {"openglcompute", Target::OpenGLCompute}, {"egl", Target::EGL}, {"user_context", Target::UserContext}, @@ -661,7 +660,7 @@ bool Target::supported() const { bad |= has_feature(Target::Metal); #endif #if !defined(WITH_OPENGL) - bad |= has_feature(Target::OpenGL) || has_feature(Target::OpenGLCompute); + bad |= has_feature(Target::OpenGLCompute); #endif #if !defined(WITH_D3D12) bad |= has_feature(Target::D3D12Compute); @@ -854,9 +853,6 @@ DeviceAPI Target::get_required_device_api() const { if (has_feature(Target::OpenCL)) { return DeviceAPI::OpenCL; } - if (has_feature(Target::OpenGL)) { - return DeviceAPI::GLSL; - } if (has_feature(Target::OpenGLCompute)) { return DeviceAPI::OpenGLCompute; } @@ -869,8 +865,6 @@ Target::Feature target_feature_for_device_api(DeviceAPI api) { return Target::CUDA; case DeviceAPI::OpenCL: return Target::OpenCL; - case DeviceAPI::GLSL: - return Target::OpenGL; case DeviceAPI::OpenGLCompute: return Target::OpenGLCompute; case DeviceAPI::Metal: @@ -1123,7 +1117,6 @@ void target_test() { {{"x86-64-linux-cuda", "x86-64-linux", "x86-64-linux-cuda"}}, {{"x86-64-linux-cuda-cuda_capability_50", "x86-64-linux-cuda", "x86-64-linux-cuda"}}, {{"x86-64-linux-cuda-cuda_capability_50", "x86-64-linux-cuda-cuda_capability_30", "x86-64-linux-cuda-cuda_capability_30"}}, - {{"x86-64-linux-cuda", "x86-64-linux-opengl", "x86-64-linux-cuda-opengl"}}, {{"hexagon-32-qurt-hvx_v65", "hexagon-32-qurt-hvx_v62", "hexagon-32-qurt-hvx_v62"}}, {{"hexagon-32-qurt-hvx_v62", "hexagon-32-qurt", "hexagon-32-qurt"}}, {{"hexagon-32-qurt-hvx_v62-hvx", "hexagon-32-qurt", ""}}, diff --git a/src/Target.h b/src/Target.h index 2ae94a71c3a0..84aeb07e0e87 100644 --- a/src/Target.h +++ b/src/Target.h @@ -82,7 +82,6 @@ struct Target { CLDoubles = halide_target_feature_cl_doubles, CLHalf = halide_target_feature_cl_half, CLAtomics64 = halide_target_feature_cl_atomic64, - OpenGL = halide_target_feature_opengl, // NOTE: this feature is deprecated and will be removed in Halide 12. OpenGLCompute = halide_target_feature_openglcompute, EGL = halide_target_feature_egl, UserContext = halide_target_feature_user_context, diff --git a/src/VaryingAttributes.cpp b/src/VaryingAttributes.cpp deleted file mode 100644 index df9ebf94f6b1..000000000000 --- a/src/VaryingAttributes.cpp +++ /dev/null @@ -1,1389 +0,0 @@ -#include "VaryingAttributes.h" - -#include - -#include "CSE.h" -#include "CodeGen_GPU_Dev.h" -#include "IR.h" -#include "IRMutator.h" -#include "IROperator.h" -#include "Simplify.h" - -namespace Halide { -namespace Internal { - -namespace { - -Stmt make_block(Stmt first, Stmt rest) { - if (first.defined() && rest.defined()) { - return Block::make(first, rest); - } else if (first.defined()) { - return first; - } else { - return rest; - } -} - -// Find expressions that we can evaluate with interpolation hardware in the GPU -// -// This visitor keeps track of the "order" of the expression in terms of the -// specified variables. The order value 0 means that the expression is contant; -// order value 1 means that it is linear in terms of only one variable, check -// the member found to determine which; order value 2 means non-linear, it -// could be disqualified due to being quadratic, bilinear or the result of an -// unknown function. -class FindLinearExpressions : public IRMutator { -protected: - using IRMutator::visit; - - bool in_glsl_loops = false; - - Expr tag_linear_expression(Expr e, const std::string &name = unique_name('a')) { - - internal_assert(name.length() > 0); - - if (total_found >= max_expressions) { - return e; - } - - // Wrap the expression with an intrinsic to tag that it is a varying - // attribute. These tagged variables will be pulled out of the fragment - // shader during a subsequent pass - Expr intrinsic = Call::make(e.type(), Call::glsl_varying, - {name + ".varying", e}, - Call::Intrinsic); - ++total_found; - - return intrinsic; - } - - Expr visit(const Call *op) override { - std::vector new_args = op->args; - - // Check to see if this call is a load - if (op->is_intrinsic(Call::glsl_texture_load)) { - // Check if the texture coordinate arguments are linear wrt the GPU - // loop variables - internal_assert(!loop_vars.empty()) << "No GPU loop variables found at texture load\n"; - - // Iterate over the texture coordinate arguments - for (int i = 2; i != 4; ++i) { - new_args[i] = mutate(op->args[i]); - if (order == 1) { - new_args[i] = tag_linear_expression(new_args[i]); - } - } - } else if (op->is_intrinsic(Call::glsl_texture_store)) { - // Check if the value expression is linear wrt the loop variables - internal_assert(!loop_vars.empty()) << "No GPU loop variables found at texture store\n"; - - // The value is the 5th argument to the intrinsic - new_args[5] = mutate(new_args[5]); - if (order == 1) { - new_args[5] = tag_linear_expression(new_args[5]); - } - } - - // The texture lookup itself is counted as a non-linear operation - order = 2; - return Call::make(op->type, op->name, new_args, op->call_type, - op->func, op->value_index, op->image, op->param); - } - - Expr visit(const Let *op) override { - Expr mutated_value = mutate(op->value); - int value_order = order; - - ScopedBinding bind(scope, op->name, order); - - Expr mutated_body = mutate(op->body); - - if ((value_order == 1) && (total_found < max_expressions)) { - // Wrap the let value with a varying tag - mutated_value = Call::make(mutated_value.type(), Call::glsl_varying, - {op->name + ".varying", mutated_value}, - Call::Intrinsic); - ++total_found; - } - - return Let::make(op->name, mutated_value, mutated_body); - } - - Stmt visit(const For *op) override { - bool old_in_glsl_loops = in_glsl_loops; - bool kernel_loop = op->device_api == DeviceAPI::GLSL; - bool within_kernel_loop = !kernel_loop && in_glsl_loops; - // Check if the loop variable is a GPU variable thread variable and for GLSL - if (kernel_loop) { - loop_vars.push_back(op->name); - in_glsl_loops = true; - } else if (within_kernel_loop) { - // The inner loop variable is non-linear w.r.t the glsl pixel coordinate. - scope.push(op->name, 2); - } - - Stmt mutated_body = mutate(op->body); - - if (kernel_loop) { - loop_vars.pop_back(); - } else if (within_kernel_loop) { - scope.pop(op->name); - } - - in_glsl_loops = old_in_glsl_loops; - - if (mutated_body.same_as(op->body)) { - return op; - } else { - return For::make(op->name, op->min, op->extent, op->for_type, op->device_api, mutated_body); - } - } - - Expr visit(const Variable *op) override { - if (std::find(loop_vars.begin(), loop_vars.end(), op->name) != loop_vars.end()) { - order = 1; - } else if (scope.contains(op->name)) { - order = scope.get(op->name); - } else { - // If the variable is not found in scope, then we assume it is - // constant in terms of the independent variables. - order = 0; - } - return op; - } - - Expr visit(const IntImm *op) override { - order = 0; - return op; - } - Expr visit(const UIntImm *op) override { - order = 0; - return op; - } - Expr visit(const FloatImm *op) override { - order = 0; - return op; - } - Expr visit(const StringImm *op) override { - order = 0; - return op; - } - - Expr visit(const Cast *op) override { - - Expr mutated_value = mutate(op->value); - int value_order = order; - - // We can only interpolate float values, disqualify the expression if - // this is a cast to a different type - if (order && (!op->type.is_float())) { - order = 2; - } - - if ((order > 1) && (value_order == 1)) { - mutated_value = tag_linear_expression(mutated_value); - } - - return Cast::make(op->type, mutated_value); - } - - // Add and subtract do not make the expression non-linear, if it is already - // linear or constant - template - Expr visit_binary_linear(T *op) { - Expr a = mutate(op->a); - unsigned int order_a = order; - Expr b = mutate(op->b); - unsigned int order_b = order; - - order = std::max(order_a, order_b); - - // If the whole expression is greater than linear, check to see if - // either argument is linear and if so, add it to a candidate list - if ((order > 1) && (order_a == 1)) { - a = tag_linear_expression(a); - } - if ((order > 1) && (order_b == 1)) { - b = tag_linear_expression(b); - } - - return T::make(a, b); - } - - Expr visit(const Add *op) override { - return visit_binary_linear(op); - } - Expr visit(const Sub *op) override { - return visit_binary_linear(op); - } - - // Multiplying increases the order of the expression, possibly making it - // non-linear - Expr visit(const Mul *op) override { - Expr a = mutate(op->a); - unsigned int order_a = order; - Expr b = mutate(op->b); - unsigned int order_b = order; - - order = order_a + order_b; - - // If the whole expression is greater than linear, check to see if - // either argument is linear and if so, add it to a candidate list - if ((order > 1) && (order_a == 1)) { - a = tag_linear_expression(a); - } - if ((order > 1) && (order_b == 1)) { - b = tag_linear_expression(b); - } - - return Mul::make(a, b); - } - - // Dividing is either multiplying by a constant, or makes the result - // non-linear (i.e. order -1) - Expr visit(const Div *op) override { - Expr a = mutate(op->a); - unsigned int order_a = order; - Expr b = mutate(op->b); - unsigned int order_b = order; - - if (order_a && !order_b) { - // Case: x / c - order = order_a; - } else if (!order_a && order_b) { - // Case: c / x - order = 2; - } else { - order = order_a + order_b; - } - - if ((order > 1) && (order_a == 1)) { - a = tag_linear_expression(a); - } - if ((order > 1) && (order_b == 1)) { - b = tag_linear_expression(b); - } - - return Div::make(a, b); - } - - // For other binary operators, if either argument is non-constant, then the - // whole expression is non-linear - template - Expr visit_binary(T *op) { - - Expr a = mutate(op->a); - unsigned int order_a = order; - Expr b = mutate(op->b); - unsigned int order_b = order; - - if (order_a || order_b) { - order = 2; - } - - if ((order > 1) && (order_a == 1)) { - a = tag_linear_expression(a); - } - if ((order > 1) && (order_b == 1)) { - b = tag_linear_expression(b); - } - - return T::make(a, b); - } - - Expr visit(const Mod *op) override { - return visit_binary(op); - } - - // Break the expression into a piecewise function, if the expressions are - // linear, we treat the piecewise behavior specially during codegen - - // Once this is done, Min and Max should call visit_binary_linear and the code - // in setup_mesh will handle piecewise linear behavior introduced by these - // expressions - Expr visit(const Min *op) override { - return visit_binary(op); - } - Expr visit(const Max *op) override { - return visit_binary(op); - } - - Expr visit(const EQ *op) override { - return visit_binary(op); - } - Expr visit(const NE *op) override { - return visit_binary(op); - } - Expr visit(const LT *op) override { - return visit_binary(op); - } - Expr visit(const LE *op) override { - return visit_binary(op); - } - Expr visit(const GT *op) override { - return visit_binary(op); - } - Expr visit(const GE *op) override { - return visit_binary(op); - } - Expr visit(const And *op) override { - return visit_binary(op); - } - Expr visit(const Or *op) override { - return visit_binary(op); - } - - Expr visit(const Not *op) override { - Expr a = mutate(op->a); - unsigned int order_a = order; - - if (order_a) { - order = 2; - } - - return Not::make(a); - } - - Expr visit(const Broadcast *op) override { - Expr a = mutate(op->value); - - if (order == 1) { - a = tag_linear_expression(a); - } - - if (order) { - order = 2; - } - - return Broadcast::make(a, op->lanes); - } - - Expr visit(const Select *op) override { - - // If either the true expression or the false expression is non-linear - // in terms of the loop variables, then the select expression might - // evaluate to a non-linear expression and is disqualified. - - // If both are either linear or constant, and the condition expression - // is constant with respect to the loop variables, then either the true - // or false expression will be evaluated across the whole loop domain, - // and the select expression is linear. Otherwise, the expression is - // disqualified. - - // The condition expression must be constant (order == 0) with respect - // to the loop variables. - Expr mutated_condition = mutate(op->condition); - int condition_order = (order != 0) ? 2 : 0; - - Expr mutated_true_value = mutate(op->true_value); - int true_value_order = order; - - Expr mutated_false_value = mutate(op->false_value); - int false_value_order = order; - - order = std::max(std::max(condition_order, true_value_order), false_value_order); - - if ((order > 1) && (condition_order == 1)) { - mutated_condition = tag_linear_expression(mutated_condition); - } - if ((order > 1) && (true_value_order == 1)) { - mutated_true_value = tag_linear_expression(mutated_true_value); - } - if ((order > 1) && (false_value_order == 1)) { - mutated_false_value = tag_linear_expression(mutated_false_value); - } - - return Select::make(mutated_condition, mutated_true_value, mutated_false_value); - } - -public: - std::vector loop_vars; - - Scope scope; - - unsigned int order; - bool found; - - unsigned int total_found = 0; - - // This parameter controls the maximum number of linearly varying - // expressions halide will pull out of the fragment shader and evaluate per - // vertex, and allow the GPU to linearly interpolate across the domain. For - // OpenGL ES 2.0 we can pass 16 vec4 varying attributes, or 64 scalars. Two - // scalar slots are used by boilerplate code to pass pixel coordinates. - const unsigned int max_expressions = 62; - - FindLinearExpressions() = default; -}; - -} // namespace - -Stmt find_linear_expressions(const Stmt &s) { - - return FindLinearExpressions().mutate(s); -} - -namespace { - -// This visitor produces a map containing name and expression pairs from varying -// tagged intrinsics -class FindVaryingAttributeTags : public IRVisitor { -public: - FindVaryingAttributeTags(std::map &varyings_) - : varyings(varyings_) { - } - - using IRVisitor::visit; - - void visit(const Call *op) override { - if (op->is_intrinsic(Call::glsl_varying)) { - std::string name = op->args[0].as()->value; - varyings[name] = op->args[1]; - } - IRVisitor::visit(op); - } - - std::map &varyings; -}; - -// This visitor removes glsl_varying intrinsics. -class RemoveVaryingAttributeTags : public IRMutator { -public: - using IRMutator::visit; - - Expr visit(const Call *op) override { - if (op->is_intrinsic(Call::glsl_varying)) { - // Replace the call expression with its wrapped argument expression - return op->args[1]; - } else { - return IRMutator::visit(op); - } - } -}; - -} // namespace - -Stmt remove_varying_attributes(const Stmt &s) { - return RemoveVaryingAttributeTags().mutate(s); -} - -namespace { - -// This visitor removes glsl_varying intrinsics and replaces them with -// variables. After this visitor is called, the varying attribute expressions -// will no longer appear in the IR tree, only variables with the .varying tag -// will remain. -class ReplaceVaryingAttributeTags : public IRMutator { -public: - using IRMutator::visit; - - Expr visit(const Call *op) override { - if (op->is_intrinsic(Call::glsl_varying)) { - // Replace the intrinsic tag wrapper with a variable the variable - // name ends with the tag ".varying" - std::string name = op->args[0].as()->value; - - internal_assert(ends_with(name, ".varying")); - - return Variable::make(op->type, name); - } else { - return IRMutator::visit(op); - } - } -}; - -} // namespace - -Stmt replace_varying_attributes(const Stmt &s) { - return ReplaceVaryingAttributeTags().mutate(s); -} - -namespace { - -// This visitor produces a set of variable names that are tagged with -// ".varying". -class FindVaryingAttributeVars : public IRVisitor { -public: - using IRVisitor::visit; - - void visit(const Variable *op) override { - if (ends_with(op->name, ".varying")) { - variables.insert(op->name); - } - } - - std::set variables; -}; - -} // namespace - -// Remove varying attributes from the varying's map if they do not appear in the -// loop_stmt because they were simplified away. -void prune_varying_attributes(const Stmt &loop_stmt, std::map &varying) { - FindVaryingAttributeVars find; - loop_stmt.accept(&find); - - std::vector remove_list; - - for (const std::pair &i : varying) { - const std::string &name = i.first; - if (find.variables.find(name) == find.variables.end()) { - debug(2) << "Removed varying attribute " << name << "\n"; - remove_list.push_back(name); - } - } - - for (const std::string &i : remove_list) { - varying.erase(i); - } -} - -namespace { - -// This visitor changes the type of variables tagged with .varying to float, -// since GLSL will only interpolate floats. In the case that the type of the -// varying attribute was integer, the interpolated float value is snapped to the -// integer grid and cast to the integer type. This case occurs with coordinate -// expressions where the integer loop variables are manipulated without being -// converted to floating point. In other cases, like an affine transformation of -// image coordinates, the loop variables are cast to floating point within the -// interpolated expression. -class CastVaryingVariables : public IRMutator { -protected: - using IRMutator::visit; - - Expr visit(const Variable *op) override { - if ((ends_with(op->name, ".varying")) && (op->type != Float(32))) { - // The incoming variable will be float type because GLSL only - // interpolates floats - Expr v = Variable::make(Float(32), op->name); - - // If the varying attribute expression that this variable replaced - // was integer type, snap the interpolated floating point variable - // back to the integer grid. - return Cast::make(op->type, floor(v + 0.5f)); - } else { - // Otherwise, the variable keeps its float type. - return op; - } - } -}; - -// This visitor casts the named variables to float, and then propagates the -// float type through the expression. The variable is offset by 0.5f -class CastVariablesToFloatAndOffset : public IRMutator { -protected: - using IRMutator::visit; - - Expr visit(const Variable *op) override { - - // Check to see if the variable matches a loop variable name - if (std::find(names.begin(), names.end(), op->name) != names.end()) { - // This case is used by integer type loop variables. They are cast - // to float and offset. - return Expr(op) - 0.5f; - - } else if (scope.contains(op->name) && (op->type != scope.get(op->name).type())) { - // Otherwise, check to see if it is defined by a modified let - // expression and if so, change the type of the variable to match - // the modified expression - return Variable::make(scope.get(op->name).type(), op->name); - } else { - return op; - } - } - - Type float_type(const Expr &e) { - return Float(e.type().bits(), e.type().lanes()); - } - - template - Expr visit_binary_op(const T *op) { - Expr mutated_a = mutate(op->a); - Expr mutated_b = mutate(op->b); - - bool a_float = mutated_a.type().is_float(); - bool b_float = mutated_b.type().is_float(); - - // If either argument is a float, then make sure both are float - if (a_float || b_float) { - if (!a_float) { - mutated_a = Cast::make(float_type(op->b), mutated_a); - } - if (!b_float) { - mutated_b = Cast::make(float_type(op->a), mutated_b); - } - } - - return T::make(mutated_a, mutated_b); - } - - Expr visit(const Add *op) override { - return visit_binary_op(op); - } - Expr visit(const Sub *op) override { - return visit_binary_op(op); - } - Expr visit(const Mul *op) override { - return visit_binary_op(op); - } - Expr visit(const Div *op) override { - return visit_binary_op(op); - } - Expr visit(const Mod *op) override { - return visit_binary_op(op); - } - Expr visit(const Min *op) override { - return visit_binary_op(op); - } - Expr visit(const Max *op) override { - return visit_binary_op(op); - } - Expr visit(const EQ *op) override { - return visit_binary_op(op); - } - Expr visit(const NE *op) override { - return visit_binary_op(op); - } - Expr visit(const LT *op) override { - return visit_binary_op(op); - } - Expr visit(const LE *op) override { - return visit_binary_op(op); - } - Expr visit(const GT *op) override { - return visit_binary_op(op); - } - Expr visit(const GE *op) override { - return visit_binary_op(op); - } - Expr visit(const And *op) override { - return visit_binary_op(op); - } - Expr visit(const Or *op) override { - return visit_binary_op(op); - } - - Expr visit(const Select *op) override { - Expr mutated_condition = mutate(op->condition); - Expr mutated_true_value = mutate(op->true_value); - Expr mutated_false_value = mutate(op->false_value); - - bool t_float = mutated_true_value.type().is_float(); - bool f_float = mutated_false_value.type().is_float(); - - // If either argument is a float, then make sure both are float - if (t_float || f_float) { - if (!t_float) { - mutated_true_value = Cast::make(float_type(op->true_value), mutated_true_value); - } - if (!f_float) { - mutated_false_value = Cast::make(float_type(op->false_value), mutated_false_value); - } - } - - return Select::make(mutated_condition, mutated_true_value, mutated_false_value); - } - - Expr visit(const Ramp *op) override { - Expr mutated_base = mutate(op->base); - Expr mutated_stride = mutate(op->stride); - - // If either base or stride is a float, then make sure both are float - bool base_float = mutated_base.type().is_float(); - bool stride_float = mutated_stride.type().is_float(); - if (!base_float && stride_float) { - mutated_base = Cast::make(float_type(op->base), mutated_base); - } else if (base_float && !stride_float) { - mutated_stride = Cast::make(float_type(op->stride), mutated_stride); - } - - if (mutated_base.same_as(op->base) && mutated_stride.same_as(op->stride)) { - return op; - } else { - return Ramp::make(mutated_base, mutated_stride, op->lanes); - } - } - - Expr visit(const Let *op) override { - Expr mutated_value = mutate(op->value); - - bool changed = op->value.type().is_float() != mutated_value.type().is_float(); - if (changed) { - scope.push(op->name, mutated_value); - } - - Expr mutated_body = mutate(op->body); - - if (changed) { - scope.pop(op->name); - } - - return Let::make(op->name, mutated_value, mutated_body); - } - Stmt visit(const LetStmt *op) override { - - Expr mutated_value = mutate(op->value); - - bool changed = op->value.type().is_float() != mutated_value.type().is_float(); - if (changed) { - scope.push(op->name, mutated_value); - } - - Stmt mutated_body = mutate(op->body); - - if (changed) { - scope.pop(op->name); - } - - return LetStmt::make(op->name, mutated_value, mutated_body); - } - -public: - CastVariablesToFloatAndOffset(const std::vector &names_) - : names(names_) { - } - - const std::vector &names; - Scope scope; -}; - -// This is the base class for a special mutator that, by default, turns an IR -// tree into a tree of Stmts. Derived classes overload visit methods to filter -// out specific expressions which are placed in Evaluate nodes within the new -// tree. This functionality is used by GLSL varying attributes to transform -// tagged linear expressions into Store nodes for the vertex buffer. The -// IRFilter allows these expressions to be filtered out while maintaining the -// existing structure of Let variable scopes around them. -// -// TODO: could this be made to use the IRMutator pattern instead? -class IRFilter : public IRVisitor { -public: - virtual Stmt mutate(const Expr &e); - virtual Stmt mutate(const Stmt &s); - -protected: - using IRVisitor::visit; - - Stmt stmt; - - void visit(const IntImm *) override; - void visit(const FloatImm *) override; - void visit(const StringImm *) override; - void visit(const Cast *) override; - void visit(const Variable *) override; - void visit(const Add *) override; - void visit(const Sub *) override; - void visit(const Mul *) override; - void visit(const Div *) override; - void visit(const Mod *) override; - void visit(const Min *) override; - void visit(const Max *) override; - void visit(const EQ *) override; - void visit(const NE *) override; - void visit(const LT *) override; - void visit(const LE *) override; - void visit(const GT *) override; - void visit(const GE *) override; - void visit(const And *) override; - void visit(const Or *) override; - void visit(const Not *) override; - void visit(const Select *) override; - void visit(const Load *) override; - void visit(const Ramp *) override; - void visit(const Broadcast *) override; - void visit(const Call *) override; - void visit(const Let *) override; - void visit(const LetStmt *) override; - void visit(const AssertStmt *) override; - void visit(const ProducerConsumer *) override; - void visit(const For *) override; - void visit(const Store *) override; - void visit(const Provide *) override; - void visit(const Allocate *) override; - void visit(const Free *) override; - void visit(const Realize *) override; - void visit(const Block *) override; - void visit(const IfThenElse *) override; - void visit(const Evaluate *) override; -}; - -Stmt IRFilter::mutate(const Expr &e) { - if (e.defined()) { - e.accept(this); - } else { - stmt = Stmt(); - } - return stmt; -} - -Stmt IRFilter::mutate(const Stmt &s) { - if (s.defined()) { - s.accept(this); - } else { - stmt = Stmt(); - } - return stmt; -} - -template -void mutate_operator(IRFilter *mutator, const T *op, const A op_a, Stmt *stmt) { - Stmt a = mutator->mutate(op_a); - *stmt = a; -} -template -void mutate_operator(IRFilter *mutator, const T *op, const A op_a, const B op_b, Stmt *stmt) { - Stmt a = mutator->mutate(op_a); - Stmt b = mutator->mutate(op_b); - *stmt = make_block(a, b); -} -template -void mutate_operator(IRFilter *mutator, const T *op, const A op_a, const B op_b, const C op_c, Stmt *stmt) { - Stmt a = mutator->mutate(op_a); - Stmt b = mutator->mutate(op_b); - Stmt c = mutator->mutate(op_c); - *stmt = make_block(make_block(a, b), c); -} - -void IRFilter::visit(const IntImm *op) { - stmt = Stmt(); -} -void IRFilter::visit(const FloatImm *op) { - stmt = Stmt(); -} -void IRFilter::visit(const StringImm *op) { - stmt = Stmt(); -} -void IRFilter::visit(const Variable *op) { - stmt = Stmt(); -} - -void IRFilter::visit(const Cast *op) { - mutate_operator(this, op, op->value, &stmt); -} - -void IRFilter::visit(const Add *op) { - mutate_operator(this, op, op->a, op->b, &stmt); -} -void IRFilter::visit(const Sub *op) { - mutate_operator(this, op, op->a, op->b, &stmt); -} -void IRFilter::visit(const Mul *op) { - mutate_operator(this, op, op->a, op->b, &stmt); -} -void IRFilter::visit(const Div *op) { - mutate_operator(this, op, op->a, op->b, &stmt); -} -void IRFilter::visit(const Mod *op) { - mutate_operator(this, op, op->a, op->b, &stmt); -} -void IRFilter::visit(const Min *op) { - mutate_operator(this, op, op->a, op->b, &stmt); -} -void IRFilter::visit(const Max *op) { - mutate_operator(this, op, op->a, op->b, &stmt); -} -void IRFilter::visit(const EQ *op) { - mutate_operator(this, op, op->a, op->b, &stmt); -} -void IRFilter::visit(const NE *op) { - mutate_operator(this, op, op->a, op->b, &stmt); -} -void IRFilter::visit(const LT *op) { - mutate_operator(this, op, op->a, op->b, &stmt); -} -void IRFilter::visit(const LE *op) { - mutate_operator(this, op, op->a, op->b, &stmt); -} -void IRFilter::visit(const GT *op) { - mutate_operator(this, op, op->a, op->b, &stmt); -} -void IRFilter::visit(const GE *op) { - mutate_operator(this, op, op->a, op->b, &stmt); -} -void IRFilter::visit(const And *op) { - mutate_operator(this, op, op->a, op->b, &stmt); -} -void IRFilter::visit(const Or *op) { - mutate_operator(this, op, op->a, op->b, &stmt); -} - -void IRFilter::visit(const Not *op) { - mutate_operator(this, op, op->a, &stmt); -} - -void IRFilter::visit(const Select *op) { - mutate_operator(this, op, op->condition, op->true_value, op->false_value, &stmt); -} - -void IRFilter::visit(const Load *op) { - mutate_operator(this, op, op->predicate, op->index, &stmt); -} - -void IRFilter::visit(const Ramp *op) { - mutate_operator(this, op, op->base, op->stride, &stmt); -} - -void IRFilter::visit(const Broadcast *op) { - mutate_operator(this, op, op->value, &stmt); -} - -void IRFilter::visit(const Call *op) { - std::vector new_args(op->args.size()); - - // Mutate the args - for (size_t i = 0; i < op->args.size(); i++) { - Expr old_arg = op->args[i]; - Stmt new_arg = mutate(old_arg); - new_args[i] = new_arg; - } - - stmt = Stmt(); - for (size_t i = 0; i < new_args.size(); ++i) { - if (new_args[i].defined()) { - stmt = make_block(new_args[i], stmt); - } - } -} - -void IRFilter::visit(const Let *op) { - mutate_operator(this, op, op->value, op->body, &stmt); -} - -void IRFilter::visit(const LetStmt *op) { - mutate_operator(this, op, op->value, op->body, &stmt); -} - -void IRFilter::visit(const AssertStmt *op) { - mutate_operator(this, op, op->condition, op->message, &stmt); -} - -void IRFilter::visit(const ProducerConsumer *op) { - mutate_operator(this, op, op->body, &stmt); -} - -void IRFilter::visit(const For *op) { - mutate_operator(this, op, op->min, op->extent, op->body, &stmt); -} - -void IRFilter::visit(const Store *op) { - mutate_operator(this, op, op->predicate, op->value, op->index, &stmt); -} - -void IRFilter::visit(const Provide *op) { - stmt = Stmt(); - for (size_t i = 0; i < op->args.size(); i++) { - Stmt new_arg = mutate(op->args[i]); - if (new_arg.defined()) { - stmt = make_block(new_arg, stmt); - } - Stmt new_value = mutate(op->values[i]); - if (new_value.defined()) { - stmt = make_block(new_value, stmt); - } - } -} - -void IRFilter::visit(const Allocate *op) { - stmt = Stmt(); - for (size_t i = 0; i < op->extents.size(); i++) { - Stmt new_extent = mutate(op->extents[i]); - if (new_extent.defined()) { - stmt = make_block(new_extent, stmt); - } - } - - Stmt body = mutate(op->body); - if (body.defined()) { - stmt = make_block(body, stmt); - } - - Stmt condition = mutate(op->condition); - if (condition.defined()) { - stmt = make_block(condition, stmt); - } -} - -void IRFilter::visit(const Free *op) { -} - -void IRFilter::visit(const Realize *op) { - stmt = Stmt(); - - // Mutate the bounds - for (size_t i = 0; i < op->bounds.size(); i++) { - Expr old_min = op->bounds[i].min; - Expr old_extent = op->bounds[i].extent; - Stmt new_min = mutate(old_min); - Stmt new_extent = mutate(old_extent); - - if (new_min.defined()) { - stmt = make_block(new_min, stmt); - } - if (new_extent.defined()) { - stmt = make_block(new_extent, stmt); - } - } - - Stmt body = mutate(op->body); - if (body.defined()) { - stmt = make_block(body, stmt); - } - - Stmt condition = mutate(op->condition); - if (condition.defined()) { - stmt = make_block(condition, stmt); - } -} - -void IRFilter::visit(const Block *op) { - mutate_operator(this, op, op->first, op->rest, &stmt); -} - -void IRFilter::visit(const IfThenElse *op) { - mutate_operator(this, op, op->condition, op->then_case, op->else_case, &stmt); -} - -void IRFilter::visit(const Evaluate *op) { - mutate_operator(this, op, op->value, &stmt); -} - -// This visitor takes a IR tree containing a set of .glsl scheduled for-loops -// and creates a matching set of serial for-loops to setup a vertex buffer on -// the host. The visitor filters out glsl_varying intrinsics and transforms -// them into Store nodes to evaluate the linear expressions they tag within the -// scope of all of the Let definitions they fall within. -// The statement returned by this operation should be executed on the host -// before the call to halide_dev_run. -class CreateVertexBufferOnHost : public IRFilter { -public: - using IRFilter::visit; - - void visit(const Call *op) override { - - // Transform glsl_varying intrinsics into store operations to output the - // vertex coordinate values. - if (op->is_intrinsic(Call::glsl_varying)) { - - // Construct an expression for the offset of the coordinate value in - // terms of the current integer loop variables and the varying - // attribute channel number - std::string attribute_name = op->args[0].as()->value; - - Expr offset_expression = Variable::make(Int(32), "gpu.vertex_offset") + - attribute_order[attribute_name]; - - stmt = Store::make(vertex_buffer_name, op->args[1], offset_expression, - Parameter(), const_true(op->args[1].type().lanes()), ModulusRemainder()); - } else { - IRFilter::visit(op); - } - } - - void visit(const Let *op) override { - stmt = nullptr; - - Stmt mutated_value = mutate(op->value); - Stmt mutated_body = mutate(op->body); - - // If an operation was filtered out of the body, also filter out the - // whole let expression so that the body may be evaluated completely. In - // the case that the let variable is not used in the mutated body, it - // will be removed by simplification. - if (mutated_body.defined()) { - stmt = LetStmt::make(op->name, op->value, mutated_body); - } - - // If an operation with a side effect was filtered out of the value, the - // stmt'ified value is placed in a Block, so that the side effect will - // be included in filtered IR tree. - if (mutated_value.defined()) { - stmt = make_block(mutated_value, stmt); - } - } - - void visit(const LetStmt *op) override { - stmt = Stmt(); - - Stmt mutated_value = mutate(op->value); - Stmt mutated_body = mutate(op->body); - - if (mutated_body.defined()) { - stmt = LetStmt::make(op->name, op->value, mutated_body); - } - - if (mutated_value.defined()) { - stmt = make_block(mutated_value, stmt); - } - } - - void visit(const For *op) override { - if (CodeGen_GPU_Dev::is_gpu_var(op->name) && op->device_api == DeviceAPI::GLSL) { - // Create a for-loop of integers iterating over the coordinates in - // this dimension - - std::string name = op->name + ".idx"; - const std::vector &dim = dims[op->name]; - - internal_assert(for_loops.size() <= 1); - for_loops.push_back(op); - - Expr loop_variable = Variable::make(Int(32), name); - loop_variables.push_back(loop_variable); - - // TODO: When support for piecewise linear expressions is added this - // expression must support more than two coordinates in each - // dimension. - Expr coord_expr = select(loop_variable == 0, dim[0], dim[1]); - - // Visit the body of the for-loop - Stmt mutated_body = mutate(op->body); - - // If this was the inner most for-loop of the .glsl scheduled pair, - // add a let definition for the vertex index and Store the spatial - // coordinates - const For *nested_for = op->body.as(); - if (!(nested_for && CodeGen_GPU_Dev::is_gpu_var(nested_for->name))) { - - // Create a variable to store the offset in floats of this - // vertex - Expr gpu_varying_offset = Variable::make(Int(32), "gpu.vertex_offset"); - - // Add expressions for the x and y vertex coordinates. - Expr coord1 = cast(Variable::make(Int(32), for_loops[0]->name)); - Expr coord0 = cast(Variable::make(Int(32), for_loops[1]->name)); - - // Transform the vertex coordinates to GPU device coordinates on - // [-1,1] - coord1 = (coord1 / for_loops[0]->extent) * 2.0f - 1.0f; - coord0 = (coord0 / for_loops[1]->extent) * 2.0f - 1.0f; - - // Remove varying attribute intrinsics from the vertex setup IR - // tree. - mutated_body = remove_varying_attributes(mutated_body); - - // The GPU will take texture coordinates at pixel centers during - // interpolation, we offset the Halide integer grid by 0.5 so that - // these coordinates line up on integer coordinate values. - std::vector names = {for_loops[0]->name, for_loops[1]->name}; - CastVariablesToFloatAndOffset cast_and_offset(names); - mutated_body = cast_and_offset.mutate(mutated_body); - - // Store the coordinates into the vertex buffer in interleaved - // order - mutated_body = make_block(Store::make(vertex_buffer_name, - coord1, - gpu_varying_offset + 1, - Parameter(), const_true(), - ModulusRemainder()), - mutated_body); - - mutated_body = make_block(Store::make(vertex_buffer_name, - coord0, - gpu_varying_offset + 0, - Parameter(), const_true(), - ModulusRemainder()), - mutated_body); - - // TODO: The value 2 in this expression must be changed to reflect - // addition coordinate values in the fastest changing dimension when - // support for piecewise linear functions is added - Expr offset_expression = (loop_variables[0] * num_padded_attributes * 2) + - (loop_variables[1] * num_padded_attributes); - mutated_body = LetStmt::make("gpu.vertex_offset", - offset_expression, mutated_body); - } - - // Add a let statement for the for-loop name variable - Stmt loop_var = LetStmt::make(op->name, coord_expr, mutated_body); - - stmt = For::make(name, 0, (int)dim.size(), ForType::Serial, DeviceAPI::None, loop_var); - - } else { - IRFilter::visit(op); - } - } - - // The name of the previously allocated vertex buffer to store values - std::string vertex_buffer_name; - - // Expressions for the spatial values of each coordinate in the GPU scheduled - // loop dimensions. - typedef std::map> DimsType; - DimsType dims; - - // The channel of each varying attribute in the interleaved vertex buffer - std::map attribute_order; - - // The number of attributes padded up to the next multiple of four. This is - // the stride from one vertex to the next in the buffer - int num_padded_attributes; - - // Independent variable names in the linear expressions - std::vector for_loops; - - // Loop variables iterated across per GPU scheduled loop dimension to - // construct the vertex buffer - std::vector loop_variables; -}; - -// These two methods provide a workaround to maintain unused let statements in -// the IR tree util calls are added that used them in codegen. - -// TODO: We want to define a set of variables during lowering, and then use -// them during GLSL host codegen to pass values to the -// halide_dev_run function. It turns out that these variables will -// be simplified away since the call to the function does not appear -// in the IR. To avoid this we wrap the declaration in a -// return_second intrinsic as well as add a return_second intrinsic -// to consume the value. -// This prevents simplification passes that occur before codegen -// from removing the variables or substituting in their constant -// values. - -Expr dont_simplify(const Expr &v_) { - return Internal::Call::make(v_.type(), - Internal::Call::return_second, - {0, v_}, - Internal::Call::Intrinsic); -} - -Stmt used_in_codegen(Type type_, const std::string &v_) { - return Evaluate::make(Internal::Call::make(Int(32), - Internal::Call::return_second, - {Variable::make(type_, v_), 0}, - Internal::Call::Intrinsic)); -} - -// This mutator inserts a set of serial for-loops to create the vertex buffer -// on the host using CreateVertexBufferOnHost above. -class CreateVertexBufferHostLoops : public IRMutator { -public: - using IRMutator::visit; - - Stmt visit(const For *op) override { - if (CodeGen_GPU_Dev::is_gpu_var(op->name) && op->device_api == DeviceAPI::GLSL) { - - const For *loop1 = op; - const For *loop0 = loop1->body.as(); - - internal_assert(loop1->body.as()) << "Did not find pair of nested For loops"; - - // Construct a mesh of expressions to instantiate during runtime - std::map varyings; - - FindVaryingAttributeTags tag_finder(varyings); - op->accept(&tag_finder); - - // Establish and order for the attributes in each vertex - std::map attribute_order; - - // Add the attribute names to the mesh in the order that they appear in - // each vertex - attribute_order["__vertex_x"] = 0; - attribute_order["__vertex_y"] = 1; - - int idx = 2; - for (const std::pair &v : varyings) { - attribute_order[v.first] = idx++; - } - - // Construct a list of expressions giving to coordinate locations along - // each dimension, starting with the minimum and maximum coordinates - - attribute_order[loop0->name] = 0; - attribute_order[loop1->name] = 1; - - Expr loop0_max = Add::make(loop0->min, loop0->extent); - Expr loop1_max = Add::make(loop1->min, loop1->extent); - - std::vector> coords(2); - - coords[0].push_back(loop0->min); - coords[0].push_back(loop0_max); - - coords[1].push_back(loop1->min); - coords[1].push_back(loop1_max); - - // Count the two spatial x and y coordinates plus the number of - // varying attribute expressions found - int num_attributes = varyings.size() + 2; - - // Pad the number of attributes up to a multiple of four - int num_padded_attributes = (num_attributes + 0x3) & ~0x3; - int vertex_buffer_size = num_padded_attributes * coords[0].size() * coords[1].size(); - - // Filter out varying attribute expressions from the glsl scheduled - // loops. The expressions are filtered out in situ, among the - // variables in scope - CreateVertexBufferOnHost vs; - vs.vertex_buffer_name = "glsl.vertex_buffer"; - vs.num_padded_attributes = num_padded_attributes; - vs.dims[loop0->name] = coords[0]; - vs.dims[loop1->name] = coords[1]; - vs.attribute_order = attribute_order; - - Stmt vertex_setup = vs.mutate(loop1); - - // Remove varying attribute intrinsics from the vertex setup IR - // tree. These may occur if an expression such as a Let-value was - // filtered out without being mutated. - vertex_setup = remove_varying_attributes(vertex_setup); - - // Simplify the new host code. Workaround for #588 - vertex_setup = simplify(vertex_setup); - vertex_setup = simplify(vertex_setup); - vertex_setup = simplify(vertex_setup); - vertex_setup = simplify(vertex_setup); - - // Replace varying attribute intriniscs in the gpu scheduled loops - // with variables with ".varying" tagged names - Stmt loop_stmt = replace_varying_attributes(op); - - // Simplify - loop_stmt = simplify(loop_stmt, true); - - // It is possible that linear expressions we tagged in higher-level - // intrinsics were removed by simplification if they were only used in - // subsequent tagged linear expressions. Run a pass to check for - // these and remove them from the varying attribute list - prune_varying_attributes(loop_stmt, varyings); - - // At this point the varying attribute expressions have been removed from - // loop_stmt- it only contains variables tagged with .varying - - // The GPU will only interpolate floating point values so the varying - // attribute variables must be converted to floating point. If the - // original varying expression was integer, casts are inserts to - // snap the value back to the integer grid. - loop_stmt = CastVaryingVariables().mutate(loop_stmt); - - // clang-format off - // Insert two new for-loops for vertex buffer generation on the host - // before the two GPU scheduled for-loops - return LetStmt::make("glsl.num_coords_dim0", dont_simplify((int)(coords[0].size())), - LetStmt::make("glsl.num_coords_dim1", dont_simplify((int)(coords[1].size())), - LetStmt::make("glsl.num_padded_attributes", dont_simplify(num_padded_attributes), - Allocate::make(vs.vertex_buffer_name, Float(32), MemoryType::Auto, {vertex_buffer_size}, const_true(), - Block::make(vertex_setup, - Block::make(loop_stmt, - Block::make(used_in_codegen(Int(32), "glsl.num_coords_dim0"), - Block::make(used_in_codegen(Int(32), "glsl.num_coords_dim1"), - Block::make(used_in_codegen(Int(32), "glsl.num_padded_attributes"), - Free::make(vs.vertex_buffer_name)))))))))); - // clang-format on - } else { - return IRMutator::visit(op); - } - } -}; - -} // namespace - -Stmt setup_gpu_vertex_buffer(const Stmt &s) { - CreateVertexBufferHostLoops vb; - return vb.mutate(s); -} - -} // namespace Internal -} // namespace Halide diff --git a/src/VaryingAttributes.h b/src/VaryingAttributes.h deleted file mode 100644 index 55475471e1aa..000000000000 --- a/src/VaryingAttributes.h +++ /dev/null @@ -1,33 +0,0 @@ -#ifndef __HALIDE_VARYING_ATTRIBUTES__H -#define __HALIDE_VARYING_ATTRIBUTES__H - -/** \file - * This file contains functions that detect expressions in a GLSL scheduled - * function that may be evaluated per vertex and interpolated across the domain - * instead of being evaluated at each pixel location across the image. - */ - -#include "Expr.h" - -namespace Halide { -namespace Internal { - -/** find_linear_expressions(Stmt s) identifies expressions that may be moved - * out of the generated fragment shader into a varying attribute. These - * expressions are tagged by wrapping them in a glsl_varying intrinsic - */ -Stmt find_linear_expressions(const Stmt &s); - -/** Compute a set of 2D mesh coordinates based on the behavior of varying - * attribute expressions contained within a GLSL scheduled for loop. This - * method is called during lowering to extract varying attribute - * expressions and generate code to evalue them at each mesh vertex - * location. The operation is performed on the host before the draw call - * to invoke the shader - */ -Stmt setup_gpu_vertex_buffer(const Stmt &s); - -} // namespace Internal -} // namespace Halide - -#endif diff --git a/src/runtime/CMakeLists.txt b/src/runtime/CMakeLists.txt index 1385e04ae13a..0fc83ed53f3c 100644 --- a/src/runtime/CMakeLists.txt +++ b/src/runtime/CMakeLists.txt @@ -132,7 +132,6 @@ set(RUNTIME_HEADER_FILES HalideRuntimeHexagonHost.h HalideRuntimeMetal.h HalideRuntimeOpenCL.h - HalideRuntimeOpenGL.h HalideRuntimeOpenGLCompute.h HalideRuntimeQurt.h ) diff --git a/src/runtime/HalideRuntimeOpenGL.h b/src/runtime/HalideRuntimeOpenGL.h deleted file mode 100644 index 14bb8b57f945..000000000000 --- a/src/runtime/HalideRuntimeOpenGL.h +++ /dev/null @@ -1,105 +0,0 @@ -#ifndef HALIDE_HALIDERUNTIMEOPENGL_H -#define HALIDE_HALIDERUNTIMEOPENGL_H - -// Don't include HalideRuntime.h if the contents of it were already pasted into a generated header above this one -#ifndef HALIDE_HALIDERUNTIME_H - -#include "HalideRuntime.h" - -#endif - -#ifdef __cplusplus -extern "C" { -#endif - -/** \file - * Routines specific to the Halide OpenGL runtime. - */ - -#define HALIDE_RUNTIME_OPENGL - -HALIDE_ATTRIBUTE_DEPRECATED("OpenGL is deprecated in Halide 11 and will be removed in Halide 12") -extern const struct halide_device_interface_t *halide_opengl_device_interface(); - -/** These are forward declared here to allow clients to override the - * Halide Glsl runtime. Do not call them. */ -// @{ -HALIDE_ATTRIBUTE_DEPRECATED("OpenGL is deprecated in Halide 11 and will be removed in Halide 12") -extern int halide_opengl_initialize_kernels(void *user_context, void **state_ptr, - const char *src, int size); - -HALIDE_ATTRIBUTE_DEPRECATED("OpenGL is deprecated in Halide 11 and will be removed in Halide 12") -extern int halide_opengl_run(void *user_context, - void *state_ptr, - const char *entry_name, - int blocksX, int blocksY, int blocksZ, - int threadsX, int threadsY, int threadsZ, - int shared_mem_bytes, - size_t arg_sizes[], - void *args[], - int8_t is_buffer[], - int num_attributes, - float *vertex_buffer, - int num_coords_dim0, - int num_coords_dim1); -// @} - -/** Set the underlying OpenGL texture for a buffer. The texture must - * have an extent large enough to cover that specified by the - * halide_buffer_t extent fields. The dev field of the halide_buffer_t - * must be NULL when this routine is called. This call can fail due to - * being passed an invalid texture. The device and host dirty bits are - * left unmodified. */ -HALIDE_ATTRIBUTE_DEPRECATED("OpenGL is deprecated in Halide 11 and will be removed in Halide 12") -extern int halide_opengl_wrap_texture(void *user_context, struct halide_buffer_t *buf, uint64_t texture_id); - -/** Set the underlying OpenGL texture for a buffer to refer to the - * current render target (e.g., the frame buffer or an FBO). The - * render target must have an extent large enough to cover that - * specified by the halide_buffer_t extent fields. The dev field of - * the halide_buffer_t must be NULL when this routine is called. This - * call can fail due to running out of memory. The device and host - * dirty bits are left unmodified. */ -HALIDE_ATTRIBUTE_DEPRECATED("OpenGL is deprecated in Halide 11 and will be removed in Halide 12") -extern int halide_opengl_wrap_render_target(void *user_context, struct halide_buffer_t *buf); - -/** Disconnect this halide_buffer_t from the texture it was previously - * wrapped around. Should only be called for a halide_buffer_t that - * halide_opengl_wrap_texture was previously called on. Frees any - * storage associated with the binding of the halide_buffer_t and the - * device pointer, but does not free the texture. The dev field of - * the halide_buffer_t will be NULL on return. - */ -HALIDE_ATTRIBUTE_DEPRECATED("OpenGL is deprecated in Halide 11 and will be removed in Halide 12") -extern int halide_opengl_detach_texture(void *user_context, struct halide_buffer_t *buf); - -/** Return the underlying texture for a halide_buffer_t. This buffer - * must be valid on an OpenGL device, or not have any associated - * device memory. If there is no device memory (dev field is NULL), - * or if the buffer was wrapped via - * halide_opengl_wrap_render_target(), this returns 0. - */ -HALIDE_ATTRIBUTE_DEPRECATED("OpenGL is deprecated in Halide 11 and will be removed in Halide 12") -extern uintptr_t halide_opengl_get_texture(void *user_context, struct halide_buffer_t *buf); - -/** Forget all state associated with the previous OpenGL context. This is - * similar to halide_opengl_release, except that we assume that all OpenGL - * resources have already been reclaimed by the OS. */ -HALIDE_ATTRIBUTE_DEPRECATED("OpenGL is deprecated in Halide 11 and will be removed in Halide 12") -extern void halide_opengl_context_lost(void *user_context); - -/** This functions MUST be provided by the host environment to retrieve pointers - * to OpenGL API functions. */ -HALIDE_ATTRIBUTE_DEPRECATED("OpenGL is deprecated in Halide 11 and will be removed in Halide 12") -void *halide_opengl_get_proc_address(void *user_context, const char *name); - -/** This functions MUST be provided by the host environment to create an OpenGL - * context for use by the OpenGL backend. */ -HALIDE_ATTRIBUTE_DEPRECATED("OpenGL is deprecated in Halide 11 and will be removed in Halide 12") -int halide_opengl_create_context(void *user_context); - -#ifdef __cplusplus -} // End extern "C" -#endif - -#endif // HALIDE_HALIDERUNTIMEOPENGL_H diff --git a/src/runtime/opengl.cpp b/src/runtime/opengl.cpp deleted file mode 100644 index 73964bfb64ee..000000000000 --- a/src/runtime/opengl.cpp +++ /dev/null @@ -1,2101 +0,0 @@ -// Ignore deprecation warnings inside our own runtime -#define HALIDE_ALLOW_DEPRECATED 1 - -#include "HalideRuntimeOpenGL.h" -#include "device_interface.h" -#include "mini_opengl.h" -#include "printer.h" - -// This constant is used to indicate that the application will take -// responsibility for binding the output render target before calling the -// Halide function. -#define HALIDE_OPENGL_RENDER_TARGET ((uint64_t)-1) - -// Implementation note: all function that directly or indirectly access the -// runtime state in halide_opengl_state must be declared as WEAK, otherwise -// the behavior at runtime is undefined. - -// List of all OpenGL functions used by the runtime. The list is used to -// declare and initialize the dispatch table in OpenGLState below. -#define USED_GL_FUNCTIONS \ - GLFUNC(PFNGLDELETETEXTURESPROC, DeleteTextures); \ - GLFUNC(PFNGLGENTEXTURESPROC, GenTextures); \ - GLFUNC(PFNGLBINDTEXTUREPROC, BindTexture); \ - GLFUNC(PFNGLGETERRORPROC, GetError); \ - GLFUNC(PFNGLVIEWPORTPROC, Viewport); \ - GLFUNC(PFNGLGENBUFFERSPROC, GenBuffers); \ - GLFUNC(PFNGLDELETEBUFFERSPROC, DeleteBuffers); \ - GLFUNC(PFNGLBINDBUFFERPROC, BindBuffer); \ - GLFUNC(PFNGLBUFFERDATAPROC, BufferData); \ - GLFUNC(PFNGLTEXPARAMETERIPROC, TexParameteri); \ - GLFUNC(PFNGLTEXIMAGE2DPROC, TexImage2D); \ - GLFUNC(PFNGLTEXSUBIMAGE2DPROC, TexSubImage2D); \ - GLFUNC(PFNGLDISABLEPROC, Disable); \ - GLFUNC(PFNGLDISABLEPROC, Enable); \ - GLFUNC(PFNGLCREATESHADERPROC, CreateShader); \ - GLFUNC(PFNGLACTIVETEXTUREPROC, ActiveTexture); \ - GLFUNC(PFNGLSHADERSOURCEPROC, ShaderSource); \ - GLFUNC(PFNGLCOMPILESHADERPROC, CompileShader); \ - GLFUNC(PFNGLGETSHADERIVPROC, GetShaderiv); \ - GLFUNC(PFNGLGETSHADERINFOLOGPROC, GetShaderInfoLog); \ - GLFUNC(PFNGLDELETESHADERPROC, DeleteShader); \ - GLFUNC(PFNGLCREATEPROGRAMPROC, CreateProgram); \ - GLFUNC(PFNGLATTACHSHADERPROC, AttachShader); \ - GLFUNC(PFNGLLINKPROGRAMPROC, LinkProgram); \ - GLFUNC(PFNGLGETPROGRAMIVPROC, GetProgramiv); \ - GLFUNC(PFNGLGETPROGRAMINFOLOGPROC, GetProgramInfoLog); \ - GLFUNC(PFNGLUSEPROGRAMPROC, UseProgram); \ - GLFUNC(PFNGLDELETEPROGRAMPROC, DeleteProgram); \ - GLFUNC(PFNGLGETUNIFORMLOCATIONPROC, GetUniformLocation); \ - GLFUNC(PFNGLUNIFORM1IVPROC, Uniform1iv); \ - GLFUNC(PFNGLUNIFORM2IVPROC, Uniform2iv); \ - GLFUNC(PFNGLUNIFORM2IVPROC, Uniform4iv); \ - GLFUNC(PFNGLUNIFORM1FVPROC, Uniform1fv); \ - GLFUNC(PFNGLUNIFORM1FVPROC, Uniform4fv); \ - GLFUNC(PFNGLGENFRAMEBUFFERSPROC, GenFramebuffers); \ - GLFUNC(PFNGLDELETEFRAMEBUFFERSPROC, DeleteFramebuffers); \ - GLFUNC(PFNGLCHECKFRAMEBUFFERSTATUSPROC, CheckFramebufferStatus); \ - GLFUNC(PFNGLBINDFRAMEBUFFERPROC, BindFramebuffer); \ - GLFUNC(PFNGLFRAMEBUFFERTEXTURE2DPROC, FramebufferTexture2D); \ - GLFUNC(PFNGLGETATTRIBLOCATIONPROC, GetAttribLocation); \ - GLFUNC(PFNGLVERTEXATTRIBPOINTERPROC, VertexAttribPointer); \ - GLFUNC(PFNGLDRAWELEMENTSPROC, DrawElements); \ - GLFUNC(PFNGLENABLEVERTEXATTRIBARRAYPROC, EnableVertexAttribArray); \ - GLFUNC(PFNGLDISABLEVERTEXATTRIBARRAYPROC, DisableVertexAttribArray); \ - GLFUNC(PFNGLGETVERTEXATTRIBIVPROC, GetVertexAttribiv); \ - GLFUNC(PFNGLPIXELSTOREIPROC, PixelStorei); \ - GLFUNC(PFNGLREADPIXELS, ReadPixels); \ - GLFUNC(PFNGLGETSTRINGPROC, GetString); \ - GLFUNC(PFNGLGETINTEGERV, GetIntegerv); \ - GLFUNC(PFNGLGETBOOLEANV, GetBooleanv); \ - GLFUNC(PFNGLFINISHPROC, Finish); - -// List of all OpenGL functions used by the runtime, which may not -// exist due to an older or less capable version of GL. In using any -// of these functions, code must test if they are nullptr. -#define OPTIONAL_GL_FUNCTIONS \ - GLFUNC(PFNGLGENVERTEXARRAYS, GenVertexArrays); \ - GLFUNC(PFNGLBINDVERTEXARRAY, BindVertexArray); \ - GLFUNC(PFNGLDELETEVERTEXARRAYS, DeleteVertexArrays); \ - GLFUNC(PFNDRAWBUFFERS, DrawBuffers) - -// ---------- Types ---------- - -using namespace Halide::Runtime::Internal; - -namespace Halide { -namespace Runtime { -namespace Internal { -namespace OpenGL { - -extern WEAK halide_device_interface_t opengl_device_interface; - -WEAK const char *gl_error_name(int32_t err) { - const char *result; - switch (err) { - case 0x500: - result = "GL_INVALID_ENUM"; - break; - case 0x501: - result = "GL_INVALID_VALUE"; - break; - case 0x502: - result = "GL_INVALID_OPERATION"; - break; - case 0x503: - result = "GL_STACK_OVERFLOW"; - break; - case 0x504: - result = "GL_STACK_UNDERFLOW"; - break; - case 0x505: - result = "GL_OUT_OF_MEMORY"; - break; - case 0x506: - result = "GL_INVALID_FRAMEBUFFER_OPERATION"; - break; - case 0x507: - result = "GL_CONTEXT_LOST"; - break; - case 0x8031: - result = "GL_TABLE_TOO_LARGE"; - break; - default: - result = ""; - break; - } - return result; -} - -struct HalideMalloc { - ALWAYS_INLINE HalideMalloc(void *user_context, size_t size) - : user_context(user_context), ptr(halide_malloc(user_context, size)) { - } - ALWAYS_INLINE ~HalideMalloc() { - halide_free(user_context, ptr); - } - void *const user_context; - void *const ptr; -}; - -enum OpenGLProfile { - OpenGL, - OpenGLES -}; - -struct Argument { - // The kind of data stored in an argument - enum Kind { - Invalid, - Uniform, // uniform variable - Varying, // varying attribute - Inbuf, // input texture - Outbuf // output texture - }; - - // The elementary data type of the argument - enum Type { - Void, - Bool, - Float, - Int8, - Int16, - Int32, - UInt8, - UInt16, - UInt32 - }; - - char *name; - Kind kind; - Type type; - Argument *next; -}; - -struct KernelInfo { - char *name; - char *source; - Argument *arguments; - GLuint shader_id; - GLuint program_id; -}; - -struct ModuleState { - KernelInfo *kernel; - ModuleState *next; -}; - -// All persistent state maintained by the runtime. -struct GlobalState { - void init(); - bool CheckAndReportError(void *user_context, const char *location); - - bool initialized; - - // Information about the OpenGL platform we're running on. - OpenGLProfile profile; - int major_version, minor_version; - bool have_vertex_array_objects; - bool have_texture_rg; - bool have_texture_float; - bool have_texture_rgb8_rgba8; - - // Various objects shared by all filter kernels - GLuint framebuffer_id; - GLuint vertex_array_object; - GLuint vertex_buffer; - GLuint element_buffer; - - // Declare pointers used OpenGL functions -#define GLFUNC(PTYPE, VAR) PTYPE VAR - USED_GL_FUNCTIONS; - OPTIONAL_GL_FUNCTIONS; -#undef GLFUNC -}; - -WEAK bool GlobalState::CheckAndReportError(void *user_context, const char *location) { - GLenum err = GetError(); - if (err != GL_NO_ERROR) { - error(user_context) << "OpenGL error " << gl_error_name(err) << "(" << (int)err << ")" - << " at " << location << ".\n"; - return true; - } - return false; -} - -WEAK GlobalState global_state; - -// Saves & restores OpenGL state -class GLStateSaver { -public: - ALWAYS_INLINE GLStateSaver() { - save(); - } - ALWAYS_INLINE ~GLStateSaver() { - restore(); - } - -private: - // The state variables - GLint active_texture; - GLint array_buffer_binding; - GLint element_array_buffer_binding; - GLint framebuffer_binding; - GLint program; - GLint vertex_array_binding; - GLint viewport[4]; - GLboolean cull_face; - GLboolean depth_test; - int max_combined_texture_image_units; - GLint *texture_2d_binding; - int max_vertex_attribs; - GLint *vertex_attrib_array_enabled; - - // Define these out-of-line as WEAK, to avoid LLVM error "MachO doesn't support COMDATs" - void save(); - void restore(); -}; - -WEAK void GLStateSaver::save() { - global_state.GetIntegerv(GL_ACTIVE_TEXTURE, &active_texture); - global_state.GetIntegerv(GL_ARRAY_BUFFER_BINDING, &array_buffer_binding); - global_state.GetIntegerv(GL_ELEMENT_ARRAY_BUFFER_BINDING, &element_array_buffer_binding); - global_state.GetIntegerv(GL_FRAMEBUFFER_BINDING, &framebuffer_binding); - global_state.GetIntegerv(GL_CURRENT_PROGRAM, &program); - global_state.GetBooleanv(GL_CULL_FACE, &cull_face); - global_state.GetBooleanv(GL_DEPTH_TEST, &depth_test); - global_state.GetIntegerv(GL_VIEWPORT, viewport); - - global_state.GetIntegerv(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, &max_combined_texture_image_units); - texture_2d_binding = (GLint *)malloc(max_combined_texture_image_units * sizeof(GLint)); - for (int i = 0; i < max_combined_texture_image_units; i++) { - global_state.ActiveTexture(GL_TEXTURE0 + i); - global_state.GetIntegerv(GL_TEXTURE_BINDING_2D, &texture_2d_binding[i]); - } - - global_state.GetIntegerv(GL_MAX_VERTEX_ATTRIBS, &max_vertex_attribs); - vertex_attrib_array_enabled = (GLint *)malloc(max_vertex_attribs * sizeof(GLint)); - for (int i = 0; i < max_vertex_attribs; i++) { - global_state.GetVertexAttribiv(i, GL_VERTEX_ATTRIB_ARRAY_ENABLED, &vertex_attrib_array_enabled[i]); - } - - if (global_state.have_vertex_array_objects) { - global_state.GetIntegerv(GL_VERTEX_ARRAY_BINDING, &vertex_array_binding); - } - -#ifdef DEBUG_RUNTIME - debug(nullptr) << "Saved OpenGL state\n"; -#endif -} - -WEAK void GLStateSaver::restore() { -#ifdef DEBUG_RUNTIME - debug(nullptr) << "Restoring OpenGL state\n"; -#endif - - for (int i = 0; i < max_combined_texture_image_units; i++) { - global_state.ActiveTexture(GL_TEXTURE0 + i); - global_state.BindTexture(GL_TEXTURE_2D, texture_2d_binding[i]); - } - free(texture_2d_binding); - - if (global_state.have_vertex_array_objects) { - global_state.BindVertexArray(vertex_array_binding); - } - - for (int i = 0; i < max_vertex_attribs; i++) { - if (vertex_attrib_array_enabled[i]) { - global_state.EnableVertexAttribArray(i); - } else { - global_state.DisableVertexAttribArray(i); - } - } - free(vertex_attrib_array_enabled); - - global_state.ActiveTexture(active_texture); - global_state.BindFramebuffer(GL_FRAMEBUFFER, framebuffer_binding); - global_state.BindBuffer(GL_ARRAY_BUFFER, array_buffer_binding); - global_state.BindBuffer(GL_ELEMENT_ARRAY_BUFFER, element_array_buffer_binding); - global_state.UseProgram(program); - global_state.Viewport(viewport[0], viewport[1], viewport[2], viewport[3]); - (cull_face ? global_state.Enable : global_state.Disable)(GL_CULL_FACE); - (depth_test ? global_state.Enable : global_state.Disable)(GL_DEPTH_TEST); -} - -// A list of module-specific state. Each module corresponds to a single Halide filter -WEAK ModuleState *state_list; - -WEAK const char *kernel_marker = "/// KERNEL "; -WEAK const char *input_marker = "/// IN_BUFFER "; -WEAK const char *output_marker = "/// OUT_BUFFER "; -WEAK const char *uniform_marker = "/// UNIFORM "; -WEAK const char *varying_marker = "/// VARYING "; - -// ---------- Helper functions ---------- - -WEAK char *strndup(const char *s, size_t n) { - char *p = (char *)malloc(n + 1); - memcpy(p, s, n); - p[n] = '\0'; - return p; -} - -// Strip whitespace from the right side of -// a string -WEAK char *strstrip(char *str, size_t n) { - char *pos = str; - while (pos != str + n && *pos != '\0' && *pos != '\n' && *pos != ' ') { - pos++; - } - *pos = '\0'; - return str; -} - -WEAK void debug_buffer(void *user_context, halide_buffer_t *buf) { - debug(user_context) << *buf << "\n"; -} - -WEAK GLuint make_shader(void *user_context, GLenum type, - const char *source, GLint *length) { -#ifdef DEBUG_RUNTIME - { - debug(user_context) << ((type == GL_VERTEX_SHADER) ? "GL_VERTEX_SHADER" : "GL_FRAGMENT_SHADER") - << " SOURCE:\n"; - // debug() will go thru Printer<> which has a fixed, non-growing size. - // Just pass the source directly to halide_print instead, so it won't get clipped. - halide_print(user_context, source); - } -#endif - - GLuint shader = global_state.CreateShader(type); - if (global_state.CheckAndReportError(user_context, "make_shader(1)")) { - return 1; - } - if (*source == '\0') { - debug(user_context) << "Halide GLSL: passed shader source is empty, using default.\n"; - const char *default_shader = "varying vec2 pixcoord;\n void main() { }"; - global_state.ShaderSource(shader, 1, (const GLchar **)&default_shader, nullptr); - } else { - global_state.ShaderSource(shader, 1, (const GLchar **)&source, length); - } - if (global_state.CheckAndReportError(user_context, "make_shader(2)")) { - return 1; - } - global_state.CompileShader(shader); - if (global_state.CheckAndReportError(user_context, "make_shader(3)")) { - return 1; - } - - GLint shader_ok = 0; - global_state.GetShaderiv(shader, GL_COMPILE_STATUS, &shader_ok); - if (!shader_ok) { - print(user_context) << "Could not compile shader:\n"; - GLint log_len; - global_state.GetShaderiv(shader, GL_INFO_LOG_LENGTH, &log_len); - HalideMalloc log_tmp(user_context, log_len); - if (log_tmp.ptr) { - char *log = (char *)log_tmp.ptr; - global_state.GetShaderInfoLog(shader, log_len, nullptr, log); - print(user_context) << log << "\n"; - } - global_state.DeleteShader(shader); - return 0; - } - return shader; -} - -// Check whether string starts with a given prefix. -// Returns pointer to character after matched prefix if successful or nullptr. -WEAK const char *match_prefix(const char *s, const char *prefix) { - if (0 == strncmp(s, prefix, strlen(prefix))) { - return s + strlen(prefix); - } - return nullptr; -} - -// Parse declaration of the form "type name" and construct matching Argument. -WEAK Argument *parse_argument(void *user_context, const char *src, - const char *end) { - const char *name; - Argument::Type type = Argument::Void; - if ((name = match_prefix(src, "float "))) { - type = Argument::Float; - } else if ((name = match_prefix(src, "bool "))) { - type = Argument::Bool; - } else if ((name = match_prefix(src, "int8_t "))) { - type = Argument::Int8; - } else if ((name = match_prefix(src, "int16_t "))) { - type = Argument::Int16; - } else if ((name = match_prefix(src, "int32_t "))) { - type = Argument::Int32; - } else if ((name = match_prefix(src, "uint8_t "))) { - type = Argument::UInt8; - } else if ((name = match_prefix(src, "uint16_t "))) { - type = Argument::UInt16; - } else if ((name = match_prefix(src, "uint32_t "))) { - type = Argument::UInt32; - } - if (type == Argument::Void) { - error(user_context) << "Internal error: argument type not supported"; - return nullptr; - } - - Argument *arg = (Argument *)malloc(sizeof(Argument)); - arg->name = strndup(name, end - name); - arg->type = type; - arg->kind = Argument::Invalid; - arg->next = nullptr; - return arg; -} - -// Create KernelInfo for a piece of GLSL code -WEAK KernelInfo *create_kernel(void *user_context, const char *src, int size) { - KernelInfo *kernel = (KernelInfo *)malloc(sizeof(KernelInfo)); - - kernel->source = strndup(src, size); - kernel->arguments = nullptr; - kernel->program_id = 0; - - debug(user_context) << "Compiling GLSL kernel (size = " << size << "):\n"; - - // Parse initial comment block - const char *line = kernel->source; - while (*line) { - const char *next_line = strchr(line, '\n') + 1; - if (!next_line) { - next_line = line + size; - } - - const char *args; - if ((args = match_prefix(line, kernel_marker))) { - // set name - kernel->name = strstrip(strndup(args, next_line - args), next_line - args); - } else if ((args = match_prefix(line, uniform_marker))) { - if (Argument *arg = - parse_argument(user_context, args, next_line - 1)) { - arg->kind = Argument::Uniform; - arg->next = kernel->arguments; - kernel->arguments = arg; - } else { - halide_error(user_context, "Invalid VAR marker"); - goto error; - } - } else if ((args = match_prefix(line, varying_marker))) { - if (Argument *arg = - parse_argument(user_context, args, next_line - 1)) { - arg->kind = Argument::Varying; - arg->next = kernel->arguments; - kernel->arguments = arg; - } else { - halide_error(user_context, "Invalid VARYING marker"); - goto error; - } - } else if ((args = match_prefix(line, input_marker))) { - if (Argument *arg = parse_argument(user_context, args, next_line - 1)) { - arg->kind = Argument::Inbuf; - arg->next = kernel->arguments; - kernel->arguments = arg; - } else { - error(user_context) << "Invalid IN_BUFFER marker"; - goto error; - } - } else if ((args = match_prefix(line, output_marker))) { - if (Argument *arg = parse_argument(user_context, args, next_line - 1)) { - arg->kind = Argument::Outbuf; - arg->next = kernel->arguments; - kernel->arguments = arg; - } else { - error(user_context) << "Invalid OUT_BUFFER marker"; - goto error; - } - } else { - // Stop parsing if we encounter something we don't recognize - break; - } - line = next_line; - } - - // Arguments are currently in reverse order, flip the list. - { - Argument *cur = kernel->arguments; - kernel->arguments = nullptr; - while (cur) { - Argument *next = cur->next; - cur->next = kernel->arguments; - kernel->arguments = cur; - cur = next; - } - } - - return kernel; -error: - free(kernel); - return nullptr; -} - -// Delete all data associated with a kernel. Also release associated OpenGL -// shader and program. -WEAK void delete_kernel(void *user_context, KernelInfo *kernel) { - global_state.DeleteProgram(kernel->program_id); -#if 0 // TODO figure out why this got deleted. - global_state.DeleteShader(kernel->shader_id); -#endif - - Argument *arg = kernel->arguments; - while (arg) { - Argument *next = arg->next; - free(arg->name); - free(arg); - arg = next; - } - free(kernel->source); - free(kernel->name); - free(kernel); -} - -// Vertices and their order in a triangle strip for rendering a quad -// ranging from (-1,-1) to (1,1). -WEAK GLfloat quad_vertices[] = { - -1.0f, -1.0f, 1.0f, -1.0f, - -1.0f, 1.0f, 1.0f, 1.0f}; -WEAK GLuint quad_indices[] = {0, 1, 2, 3}; - -WEAK void GlobalState::init() { - initialized = false; - profile = OpenGL; - major_version = 2; - minor_version = 0; - framebuffer_id = 0; - vertex_array_object = vertex_buffer = element_buffer = 0; - have_vertex_array_objects = false; - have_texture_rg = false; - have_texture_rgb8_rgba8 = false; - // Initialize all GL function pointers to nullptr -#define GLFUNC(type, name) name = nullptr; - USED_GL_FUNCTIONS; - OPTIONAL_GL_FUNCTIONS; -#undef GLFUNC -} - -WEAK int load_gl_func(void *user_context, const char *name, void **ptr, bool required) { - void *p = halide_opengl_get_proc_address(user_context, name); - if (!p && required) { - error(user_context) << "Could not load function pointer for " << name; - return -1; - } - *ptr = p; - return 0; -} - -WEAK bool extension_supported(void *user_context, const char *name) { - // Iterate over space delimited extension strings. Note that glGetStringi - // is not part of GL ES 2.0, and not reliable in all implementations of - // GL ES 3.0. - const char *start = (const char *)global_state.GetString(GL_EXTENSIONS); - if (!start) { - return false; - } - while (const char *pos = strstr(start, name)) { - const char *end = pos + strlen(name); - // Ensure the found match is a full word, not a substring. - if ((pos == start || pos[-1] == ' ') && - (*end == ' ' || *end == '\0')) { - return true; - } - start = end; - } - - return false; -} - -// Check for availability of various version- and extension-specific features -// and hook up functions pointers as necessary -WEAK void init_extensions(void *user_context) { - if (global_state.major_version >= 3) { // This is likely valid for both OpenGL and OpenGL ES - load_gl_func(user_context, "glGenVertexArrays", (void **)&global_state.GenVertexArrays, false); - load_gl_func(user_context, "glBindVertexArray", (void **)&global_state.BindVertexArray, false); - load_gl_func(user_context, "glDeleteVertexArrays", (void **)&global_state.DeleteVertexArrays, false); - if (global_state.GenVertexArrays && global_state.BindVertexArray && global_state.DeleteVertexArrays) { - global_state.have_vertex_array_objects = true; - } - } - load_gl_func(user_context, "glDrawBuffers", (void **)&global_state.DrawBuffers, false); - - global_state.have_texture_rg = - global_state.major_version >= 3 || - (global_state.profile == OpenGL && - extension_supported(user_context, "GL_ARB_texture_rg")) || - (global_state.profile == OpenGLES && - extension_supported(user_context, "GL_EXT_texture_rg")); - - global_state.have_texture_rgb8_rgba8 = - global_state.major_version >= 3 || - (global_state.profile == OpenGLES && - extension_supported(user_context, "GL_OES_rgb8_rgba8")); - - global_state.have_texture_float = - (global_state.major_version >= 3) || - (global_state.profile == OpenGL && - extension_supported(user_context, "GL_ARB_texture_float")) || - (global_state.profile == OpenGLES && - extension_supported(user_context, "GL_OES_texture_float")); -} - -WEAK const char *parse_int(const char *str, int *val) { - int v = 0; - size_t i = 0; - while (str[i] >= '0' && str[i] <= '9') { - v = 10 * v + (str[i] - '0'); - i++; - } - if (i > 0) { - *val = v; - return &str[i]; - } - return nullptr; -} - -WEAK const char *parse_opengl_version(const char *str, int *major, int *minor) { - str = parse_int(str, major); - if (str == nullptr || *str != '.') { - return nullptr; - } - return parse_int(str + 1, minor); -} - -// Initialize the OpenGL-specific parts of the runtime. -WEAK int halide_opengl_init(void *user_context) { - if (global_state.initialized) { - return 0; - } - -#ifdef DEBUG_RUNTIME - halide_start_clock(user_context); -#endif - - global_state.init(); - - // Make a context if there isn't one - if (halide_opengl_create_context(user_context)) { - error(user_context) << "Failed to make OpenGL context"; - return -1; - } - - // Initialize pointers to core OpenGL functions. -#define GLFUNC(TYPE, VAR) \ - if (load_gl_func(user_context, "gl" #VAR, (void **)&global_state.VAR, true) < 0) { \ - return -1; \ - } - USED_GL_FUNCTIONS; -#undef GLFUNC - - const char *version = (const char *)global_state.GetString(GL_VERSION); - const char *gles_version = match_prefix(version, "OpenGL ES "); - int major, minor; - if (gles_version && parse_opengl_version(gles_version, &major, &minor)) { - global_state.profile = OpenGLES; - global_state.major_version = major; - global_state.minor_version = minor; - } else if (parse_opengl_version(version, &major, &minor)) { - global_state.profile = OpenGL; - global_state.major_version = major; - global_state.minor_version = minor; - } else { - global_state.profile = OpenGL; - global_state.major_version = 2; - global_state.minor_version = 0; - } - init_extensions(user_context); - debug(user_context) - << "Halide running on OpenGL " << ((global_state.profile == OpenGL) ? "" : "ES ") << major << "." << minor << "\n" - << " vertex_array_objects: " << (global_state.have_vertex_array_objects ? "yes\n" : "no\n") - << " texture_rg: " << (global_state.have_texture_rg ? "yes\n" : "no\n") - << " have_texture_rgb8_rgba8: " << (global_state.have_texture_rgb8_rgba8 ? "yes\n" : "no\n") - << " texture_float: " << (global_state.have_texture_float ? "yes\n" : "no\n"); - - // Initialize framebuffer. - global_state.GenFramebuffers(1, &global_state.framebuffer_id); - if (global_state.CheckAndReportError(user_context, "halide_opengl_init GenFramebuffers")) { - return 1; - } - - // Initialize vertex and element buffers. - GLuint buf[2]; - global_state.GenBuffers(2, buf); - global_state.BindBuffer(GL_ARRAY_BUFFER, buf[0]); - global_state.BufferData(GL_ARRAY_BUFFER, sizeof(quad_vertices), quad_vertices, GL_STATIC_DRAW); - global_state.BindBuffer(GL_ARRAY_BUFFER, 0); - global_state.BindBuffer(GL_ELEMENT_ARRAY_BUFFER, buf[1]); - global_state.BufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(quad_indices), quad_indices, GL_STATIC_DRAW); - global_state.BindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); - global_state.vertex_buffer = buf[0]; - global_state.element_buffer = buf[1]; - - if (global_state.have_vertex_array_objects) { - global_state.GenVertexArrays(1, &global_state.vertex_array_object); - if (global_state.CheckAndReportError(user_context, "halide_opengl_init GenVertexArrays")) { - return 1; - } - } - - global_state.initialized = true; - return 0; -} - -// Release all data allocated by the runtime. -// -// The OpenGL context itself is generally managed by the host application, so -// we leave it untouched. -WEAK int halide_opengl_device_release(void *user_context) { - if (!global_state.initialized) { - return 0; - } - - debug(user_context) << "halide_opengl_release\n"; - global_state.DeleteFramebuffers(1, &global_state.framebuffer_id); - - ModuleState *mod = state_list; - while (mod) { - delete_kernel(user_context, mod->kernel); - mod->kernel = nullptr; - ModuleState *next = mod->next; - // do not call free(mod) to avoid dangling pointers: the module state - // is still referenced in the code generated by Halide (see - // CodeGen_GPU_Host::get_module_state). - mod = next; - } - - global_state.DeleteBuffers(1, &global_state.vertex_buffer); - global_state.DeleteBuffers(1, &global_state.element_buffer); - if (global_state.have_vertex_array_objects) { - global_state.DeleteVertexArrays(1, &global_state.vertex_array_object); - } - - global_state = GlobalState(); - - return 0; -} - -// Determine OpenGL texture format and channel type for a given halide_buffer_t. -WEAK bool get_texture_format(void *user_context, halide_buffer_t *buf, - GLint *internal_format, GLint *format, GLint *type) { - if (buf->type == halide_type_of()) { - *type = GL_UNSIGNED_BYTE; - } else if (buf->type == halide_type_of()) { - *type = GL_UNSIGNED_SHORT; - } else if (buf->type == halide_type_of()) { - *type = GL_FLOAT; - } else { - error(user_context) << "OpenGL: Only uint8, uint16, and float textures are supported."; - return false; - } - - const int channels = (buf->dimensions > 2) ? buf->dim[2].extent : 0; - - // GL_LUMINANCE and GL_LUMINANCE_ALPHA aren't color-renderable in ES2, period, - // thus can't be read back via ReadPixels, thus are nearly useless to us. - // GL_RED and GL_RG are technically optional in ES2 (required in ES3), - // but as a practical matter, they are supported on pretty much every recent device - // (iOS: everything >= iPhone 4s; Android: everything >= 4.3 plus various older devices). - // This is definitely suboptimal; the only real alternative would be to implement - // these as GL_RGB or GL_RGBA, ignoring the extra channels. - if (channels <= 2 && !global_state.have_texture_rg) { - error(user_context) << "OpenGL: 1 and 2 channel textures are not supported for this version of OpenGL."; - return false; - } - - // Common formats supported by both GLES 2.0 and GL 2.1 are selected below - // - switch (channels) { - case 0: - case 1: - *format = GL_RED; - break; - case 2: - *format = GL_RG; - break; - case 3: - *format = GL_RGB; - break; - case 4: - *format = GL_RGBA; - break; - default: - error(user_context) << "OpenGL: Invalid number of color channels: " << channels; - return false; - } - - switch (global_state.profile) { - case OpenGLES: - // For OpenGL ES, the texture format has to match the pixel format - // since there no conversion is performed during texture transfers. - // See OES_texture_float. - *internal_format = *format; - break; - case OpenGL: - // For desktop OpenGL, the internal format specifiers include the - // precise data type, see ARB_texture_float. - if (*type == GL_FLOAT) { - switch (*format) { - case GL_RED: - case GL_RG: - case GL_RGB: - case GL_RGBA: - *internal_format = GL_RGBA32F; - break; - default: - error(user_context) << "OpenGL: Cannot select internal format for format " << *format; - return false; - } - } else { - *internal_format = *format; - } - break; - } - - return true; -} - -// This function returns the width, height and number of color channels that the -// texture for the specified halide_buffer_t will contain. It provides a single place -// to implement the logic snapping zero sized dimensions to one element. -WEAK bool get_texture_dimensions(void *user_context, halide_buffer_t *buf, GLint *width, - GLint *height, GLint *channels) { - if (buf->dimensions > 3) { - error(user_context) << "The GL backend supports buffers of at most 3 dimensions\n"; - return false; - } - - *width = buf->dim[0].extent; - if (*width == 0) { - error(user_context) << "Invalid dim[0].extent: " << *width << "\n"; - return false; - } - - // GLES 2.0 supports GL_TEXTURE_2D (plus cube map), but not 1d or 3d. If we - // end up with a buffer that has a zero extent, set the corresponding size - // to one. - *height = (buf->dimensions > 1) ? buf->dim[1].extent : 1; - *channels = (buf->dimensions > 2) ? buf->dim[2].extent : 1; - - return true; -} - -// Allocate a new texture matching the dimension and color format of the -// specified buffer. -WEAK int halide_opengl_device_malloc(void *user_context, halide_buffer_t *buf) { - if (int error = halide_opengl_init(user_context)) { - return error; - } - - if (!buf) { - error(user_context) << "Invalid buffer"; - return 1; - } - - // If the texture was already created by the host application, check that - // it has the correct format. Otherwise, allocate and set up an - // appropriate texture. - GLuint tex = 0; - bool halide_allocated = false; - - if (buf->device) { -#ifdef HAVE_GLES3 - // Look up the width and the height from the existing texture. Note that - // glGetTexLevelParameteriv does not support GL_TEXTURE_WIDTH or - // GL_TEXTURE_HEIGHT in GLES 2.0 - GLint width, height; - global_state.BindTexture(GL_TEXTURE_2D, tex); - global_state.GetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &width); - global_state.GetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &height); - if (global_state.CheckAndReportError(user_context, "halide_opengl_device_malloc binding texture (GLES3)")) { - return 1; - } - if (width < buf->dim[0].extent || height < buf->dim[1].extent) { - error(user_context) - << "Existing texture is smaller than buffer. " - << "Texture size: " << width << "x" << height - << ", buffer size: " << buf->dim[0].extent << "x" << buf->dim[1].extent; - return 1; - } -#endif - uint64_t handle = buf->device; - tex = (handle == HALIDE_OPENGL_RENDER_TARGET) ? 0 : (GLuint)handle; - } else { - if (buf->dimensions > 3) { - error(user_context) << "high-dimensional textures are not supported"; - return 1; - } - - // Generate texture ID - global_state.GenTextures(1, &tex); - if (global_state.CheckAndReportError(user_context, "halide_opengl_device_malloc GenTextures")) { - global_state.DeleteTextures(1, &tex); - return 1; - } - - // Set parameters for this texture: no interpolation and clamp to edges. - global_state.BindTexture(GL_TEXTURE_2D, tex); - global_state.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - global_state.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - global_state.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - global_state.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - if (global_state.CheckAndReportError(user_context, "halide_opengl_device_malloc binding texture")) { - global_state.DeleteTextures(1, &tex); - return 1; - } - - // Create empty texture here and fill it with glTexSubImage2D later. - GLint internal_format, format, type; - if (!get_texture_format(user_context, buf, &internal_format, &format, &type)) { - error(user_context) << "Invalid texture format"; - global_state.DeleteTextures(1, &tex); - return 1; - } - - GLint width, height, channels; - if (!get_texture_dimensions(user_context, buf, &width, &height, &channels)) { - error(user_context) << "Invalid texture dimensions"; - return 1; - } - - global_state.TexImage2D(GL_TEXTURE_2D, 0, internal_format, width, height, 0, format, type, nullptr); - if (global_state.CheckAndReportError(user_context, "halide_opengl_device_malloc TexImage2D")) { - global_state.DeleteTextures(1, &tex); - return 1; - } - - buf->device = tex; - buf->device_interface = &opengl_device_interface; - buf->device_interface->impl->use_module(); - halide_allocated = true; - debug(user_context) << "Allocated texture " << tex - << " of size " << width << " x " << height << "\n"; - - global_state.BindTexture(GL_TEXTURE_2D, 0); - } - - return 0; -} - -// Delete all texture information associated with a buffer. -WEAK int halide_opengl_device_free(void *user_context, halide_buffer_t *buf) { - if (!global_state.initialized) { - error(user_context) << "OpenGL runtime not initialized in call to halide_opengl_device_free."; - return 1; - } - - if (buf->device == 0) { - return 0; - } - - uint64_t handle = buf->device; - GLuint tex = (handle == HALIDE_OPENGL_RENDER_TARGET) ? 0 : (GLuint)handle; - - int result = 0; - debug(user_context) << "halide_opengl_device_free: Deleting texture " << tex << "\n"; - global_state.DeleteTextures(1, &tex); - if (global_state.CheckAndReportError(user_context, "halide_opengl_device_free DeleteTextures")) { - result = 1; - // do not return: we want to zero out the interface and - // device fields even if we can't delete the texture. - } - buf->device = 0; - buf->device_interface->impl->release_module(); - buf->device_interface = nullptr; - - return result; -} - -// Can't use std::min, std::max in Halide runtime. -template -ALWAYS_INLINE T std_min(T a, T b) { - return (a < b) ? a : b; -} -template -ALWAYS_INLINE T std_max(T a, T b) { - return (a > b) ? a : b; -} - -// This method copies image data from the layout specified by the strides of the -// halide_buffer_t to the packed interleaved format needed by GL. It is assumed that -// src and dst have the same number of channels. -template -ALWAYS_INLINE void halide_to_interleaved(const halide_buffer_t *src_buf, T *dst) { - const T *src = reinterpret_cast(src_buf->host); - int width = (src_buf->dimensions > 0) ? src_buf->dim[0].extent : 1; - int height = (src_buf->dimensions > 1) ? src_buf->dim[1].extent : 1; - int channels = (src_buf->dimensions > 2) ? src_buf->dim[2].extent : 1; - int x_stride = (src_buf->dimensions > 0) ? src_buf->dim[0].stride : 0; - int y_stride = (src_buf->dimensions > 1) ? src_buf->dim[1].stride : 0; - int c_stride = (src_buf->dimensions > 2) ? src_buf->dim[2].stride : 0; - for (int y = 0; y < height; y++) { - int dstidx = y * width * channels; - for (int x = 0; x < width; x++) { - int srcidx = y * y_stride + x * x_stride; - for (int c = 0; c < channels; c++) { - dst[dstidx] = src[srcidx]; - srcidx += c_stride; - dstidx += 1; - } - } - } -} - -// This method copies image data from the packed interleaved format needed by GL -// to the arbitrary strided layout specified by the halide_buffer_t. If src has fewer -// channels than dst, the excess in dst will be left untouched; if src has -// more channels than dst, the excess will be ignored. -template -ALWAYS_INLINE void interleaved_to_halide(void *user_context, const T *src, int src_channels, halide_buffer_t *dst_buf) { - T *dst = reinterpret_cast(dst_buf->host); - int width = (dst_buf->dimensions > 0) ? dst_buf->dim[0].extent : 1; - int height = (dst_buf->dimensions > 1) ? dst_buf->dim[1].extent : 1; - int dst_channels = (dst_buf->dimensions > 2) ? dst_buf->dim[2].extent : 1; - int x_stride = (dst_buf->dimensions > 0) ? dst_buf->dim[0].stride : 0; - int y_stride = (dst_buf->dimensions > 1) ? dst_buf->dim[1].stride : 0; - int c_stride = (dst_buf->dimensions > 2) ? dst_buf->dim[2].stride : 0; - int src_skip = std_max(0, src_channels - dst_channels); - int channels = std_min(src_channels, dst_channels); - - for (int y = 0; y < height; y++) { - int srcidx = y * width * src_channels; - for (int x = 0; x < width; x++) { - int dstidx = y * y_stride + x * x_stride; - for (int c = 0; c < channels; c++) { - dst[dstidx] = src[srcidx]; - srcidx += 1; - dstidx += c_stride; - } - srcidx += src_skip; - } - } -} - -// Copy image data from host memory to texture. -WEAK int halide_opengl_copy_to_device(void *user_context, halide_buffer_t *buf) { - if (!global_state.initialized) { - error(user_context) << "OpenGL runtime not initialized (halide_opengl_copy_to_device)."; - return 1; - } - - GLStateSaver state_saver; - - int err = halide_opengl_device_malloc(user_context, buf); - if (err) { - return err; - } - - if (!buf->host || !buf->device) { - debug_buffer(user_context, buf); - error(user_context) << "Invalid copy_to_device operation: host or device nullptr"; - return 1; - } - - uint64_t handle = buf->device; - if (handle == HALIDE_OPENGL_RENDER_TARGET) { - // TODO: this isn't correct; we want to ensure we copy to the current render_target. - debug(user_context) << "halide_opengl_copy_to_device: called for HALIDE_OPENGL_RENDER_TARGET\n"; - return 0; - } - GLuint tex = (GLuint)handle; - debug(user_context) << "halide_opengl_copy_to_device: " << tex << "\n"; - - global_state.BindTexture(GL_TEXTURE_2D, tex); - if (global_state.CheckAndReportError(user_context, "halide_opengl_copy_to_device BindTexture")) { - return 1; - } - GLint internal_format, format, type; - if (!get_texture_format(user_context, buf, &internal_format, &format, &type)) { - error(user_context) << "Invalid texture format"; - return 1; - } - - GLint width, height, buffer_channels; - if (!get_texture_dimensions(user_context, buf, &width, &height, &buffer_channels)) { - error(user_context) << "Invalid texture dimensions"; - return 1; - } - - // To use TexSubImage2D directly, the colors must be stored interleaved - // and rows must be stored consecutively. - // (Single-channel buffers are "interleaved" for our purposes here.) - bool is_interleaved = (buffer_channels == 1) || (buf->dim[2].stride == 1 && buf->dim[0].stride == buf->dim[2].extent); - bool is_packed = (buf->dim[1].stride == buf->dim[0].extent * buf->dim[0].stride); - if (is_interleaved && is_packed) { - global_state.PixelStorei(GL_UNPACK_ALIGNMENT, 1); - global_state.TexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, format, type, buf->host); - if (global_state.CheckAndReportError(user_context, "halide_opengl_copy_to_device TexSubImage2D(1)")) { - return 1; - } - } else { - debug(user_context) - << "Warning: In copy_to_device, host buffer is not interleaved. Doing slow interleave.\n"; - - size_t texture_size = width * height * buffer_channels * buf->type.bytes(); - HalideMalloc tmp(user_context, texture_size); - if (!tmp.ptr) { - error(user_context) << "halide_malloc failed inside copy_to_device"; - return -1; - } - - switch (type) { - case GL_UNSIGNED_BYTE: - halide_to_interleaved(buf, (uint8_t *)tmp.ptr); - break; - case GL_UNSIGNED_SHORT: - halide_to_interleaved(buf, (uint16_t *)tmp.ptr); - break; - case GL_FLOAT: - halide_to_interleaved(buf, (float *)tmp.ptr); - break; - } - - global_state.PixelStorei(GL_UNPACK_ALIGNMENT, 1); - global_state.TexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, format, type, tmp.ptr); - if (global_state.CheckAndReportError(user_context, "halide_opengl_copy_to_device TexSubImage2D(2)")) { - return 1; - } - } - - return 0; -} - -// Copy image data from texture back to host memory. -WEAK int halide_opengl_copy_to_host(void *user_context, halide_buffer_t *buf) { - if (!global_state.initialized) { - error(user_context) << "OpenGL runtime not initialized (halide_opengl_copy_to_host)."; - return 1; - } - - GLStateSaver state_saver; - - if (!buf->host || !buf->device) { - debug_buffer(user_context, buf); - error(user_context) << "Invalid copy_to_host operation: host or dev nullptr"; - return 1; - } - - GLint internal_format, format, type; - if (!get_texture_format(user_context, buf, &internal_format, &format, &type)) { - error(user_context) << "Invalid texture format"; - return 1; - } - - GLint width, height, buffer_channels; - if (!get_texture_dimensions(user_context, buf, &width, &height, &buffer_channels)) { - error(user_context) << "Invalid texture dimensions"; - return 1; - } - GLint texture_channels = buffer_channels; - - uint64_t handle = buf->device; - if (handle != HALIDE_OPENGL_RENDER_TARGET) { - GLuint tex = (GLuint)handle; - debug(user_context) << "halide_copy_to_host: texture " << tex << "\n"; - global_state.BindFramebuffer(GL_FRAMEBUFFER, global_state.framebuffer_id); - if (global_state.CheckAndReportError(user_context, "copy_to_host BindFramebuffer")) { - return 1; - } - global_state.FramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex, 0); - if (global_state.CheckAndReportError(user_context, "copy_to_host FramebufferTexture2D")) { - return 1; - } - } else { - debug(user_context) << "halide_copy_to_host: HALIDE_OPENGL_RENDER_TARGET\n"; - } - - // Check that framebuffer is set up correctly - GLenum status = global_state.CheckFramebufferStatus(GL_FRAMEBUFFER); - if (status != GL_FRAMEBUFFER_COMPLETE) { - error(user_context) - << "Setting up GL framebuffer " << global_state.framebuffer_id << " failed " << status; - return 1; - } - - // The only format/type pairs guaranteed to be readable in GLES2 are GL_RGBA+GL_UNSIGNED_BYTE, - // plus one other implementation-dependent pair specified here. Spoiler alert: - // some ES2 implementations return that very same pair here (i.e., they don't support - // any other formats); in that case, we need to read as RGBA and manually convert to - // what we need (usually GL_RGB). - // NOTE: this requires the currently-bound Framebuffer is correct. - // TODO: short and float will require even more effort on top of this. - if (global_state.profile == OpenGLES && format == GL_RGB) { - GLint extra_format, extra_type; - global_state.GetIntegerv(GL_IMPLEMENTATION_COLOR_READ_TYPE, &extra_type); - if (type != GL_UNSIGNED_BYTE && type != extra_type) { - error(user_context) << "ReadPixels does not support our type; we don't handle this yet.\n"; - return 1; - } - global_state.GetIntegerv(GL_IMPLEMENTATION_COLOR_READ_FORMAT, &extra_format); - if (format != GL_RGBA && format != extra_format) { - debug(user_context) << "ReadPixels does not support our format; falling back to GL_RGBA\n"; - format = GL_RGBA; - texture_channels = 4; - } - } - - // To download the texture directly, the colors must be stored interleaved - // and rows must be stored consecutively. - // (Single-channel buffers are "interleaved" for our purposes here.) - bool is_interleaved = (buffer_channels == 1) || (buf->dim[2].stride == 1 && buf->dim[0].stride == buf->dim[2].extent); - bool is_packed = (buf->dim[1].stride == buf->dim[0].extent * buf->dim[0].stride); - if (is_interleaved && is_packed && texture_channels == buffer_channels) { - global_state.PixelStorei(GL_PACK_ALIGNMENT, 1); -#ifdef DEBUG_RUNTIME - int64_t t1 = halide_current_time_ns(user_context); -#endif - global_state.ReadPixels(0, 0, buf->dim[0].extent, buf->dim[1].extent, format, type, buf->host); -#ifdef DEBUG_RUNTIME - int64_t t2 = halide_current_time_ns(user_context); -#endif - if (global_state.CheckAndReportError(user_context, "copy_to_host ReadPixels (1)")) { - return 1; - } -#ifdef DEBUG_RUNTIME - debug(user_context) << "ReadPixels(1) time: " << (t2 - t1) / 1e3 << "usec\n"; -#endif - } else { - debug(user_context) - << "Warning: In copy_to_host, host buffer is not interleaved, or not a native format. Doing slow deinterleave.\n"; - - size_t texture_size = width * height * texture_channels * buf->type.bytes(); - HalideMalloc tmp(user_context, texture_size); - if (!tmp.ptr) { - error(user_context) << "halide_malloc failed inside copy_to_host"; - return -1; - } - - global_state.PixelStorei(GL_PACK_ALIGNMENT, 1); -#ifdef DEBUG_RUNTIME - int64_t t1 = halide_current_time_ns(user_context); -#endif - global_state.ReadPixels(0, 0, buf->dim[0].extent, buf->dim[1].extent, format, type, tmp.ptr); -#ifdef DEBUG_RUNTIME - int64_t t2 = halide_current_time_ns(user_context); - debug(user_context) << "ReadPixels(2) time: " << (t2 - t1) / 1e3 << "usec\n"; -#endif - if (global_state.CheckAndReportError(user_context, "copy_to_host ReadPixels (2)")) { - return 1; - } - - // Premature optimization warning: interleaved_to_halide() could definitely - // be optimized, but ReadPixels() typically takes ~2-10x as long (especially on - // mobile devices), so the returns will be modest. -#ifdef DEBUG_RUNTIME - int64_t t3 = halide_current_time_ns(user_context); -#endif - switch (type) { - case GL_UNSIGNED_BYTE: - interleaved_to_halide(user_context, (uint8_t *)tmp.ptr, texture_channels, buf); - break; - case GL_UNSIGNED_SHORT: - interleaved_to_halide(user_context, (uint16_t *)tmp.ptr, texture_channels, buf); - break; - case GL_FLOAT: - interleaved_to_halide(user_context, (float *)tmp.ptr, texture_channels, buf); - break; - } -#ifdef DEBUG_RUNTIME - int64_t t4 = halide_current_time_ns(user_context); - debug(user_context) << "deinterleave time: " << (t4 - t3) / 1e3 << "usec\n"; -#endif - } - - return 0; -} - -} // namespace OpenGL -} // namespace Internal -} // namespace Runtime -} // namespace Halide - -using namespace Halide::Runtime::Internal::OpenGL; - -// Find the correct module for the called function -// TODO: This currently takes O(# of GLSL'd stages) and can -// be optimized -WEAK ModuleState *find_module(const char *stage_name) { - ModuleState *state_ptr = state_list; - - while (state_ptr != nullptr) { - KernelInfo *kernel = state_ptr->kernel; - if (kernel && strcmp(stage_name, kernel->name) == 0) { - return state_ptr; - } - state_ptr = state_ptr->next; - } - - return nullptr; -} - -// Create wrappers that satisfy old naming conventions - -extern "C" { - -WEAK int halide_opengl_run(void *user_context, - void *state_ptr, - const char *entry_name, - int blocksX, int blocksY, int blocksZ, - int threadsX, int threadsY, int threadsZ, - int shared_mem_bytes, - size_t arg_sizes[], void *args[], int8_t is_buffer[], - int num_padded_attributes, - float *vertex_buffer, - int num_coords_dim0, - int num_coords_dim1) { - if (!global_state.initialized) { - error(user_context) << "OpenGL runtime not initialized (halide_opengl_run)."; - return 1; - } - - GLStateSaver state_saver; - - // Find the right module - ModuleState *mod = find_module(entry_name); - if (!mod) { - error(user_context) << "Internal error: module state for stage " << entry_name << " not found\n"; - return 1; - } - - KernelInfo *kernel = mod->kernel; - - global_state.UseProgram(kernel->program_id); - if (global_state.CheckAndReportError(user_context, "halide_opengl_run UseProgram")) { - return 1; - } - - // TODO(abstephensg) it would be great to codegen these vec4 uniform buffers - // directly, instead of passing an array of arguments and then copying them - // out at runtime. - - // Determine the number of float and int uniform parameters. This code - // follows the argument packing convention in CodeGen_GPU_Host and - // CodeGen_OpenGL_Dev - int num_uniform_floats = 0; - int num_uniform_ints = 0; - - Argument *kernel_arg = kernel->arguments; - for (int i = 0; args[i]; i++, kernel_arg = kernel_arg->next) { - - // Check for a mismatch between the number of arguments declared in the - // fragment shader source header and the number passed to this function - if (!kernel_arg) { - error(user_context) - << "Too many arguments passed to halide_opengl_run\n" - << "Argument " << i << ": size=" << i << " value=" << args[i]; - return 1; - } - - // Count the number of float and int uniform parameters. - if (kernel_arg->kind == Argument::Uniform) { - switch (kernel_arg->type) { - case Argument::Float: - // Integer parameters less than 32 bits wide are passed as - // normalized float values - case Argument::Int8: - case Argument::UInt8: - case Argument::Int16: - case Argument::UInt16: - ++num_uniform_floats; - break; - case Argument::Bool: - case Argument::Int32: - case Argument::UInt32: - ++num_uniform_ints; - break; - default: - error(user_context) << "GLSL: Encountered invalid kernel argument type"; - return 1; - } - } - } - - // Pad up to a multiple of four - int num_padded_uniform_floats = (num_uniform_floats + 0x3) & ~0x3; - int num_padded_uniform_ints = (num_uniform_ints + 0x3) & ~0x3; - - // Allocate storage for the packed arguments - float uniform_float[num_padded_uniform_floats]; - int uniform_int[num_padded_uniform_ints]; - - bool bind_render_targets = true; - - // Copy input arguments to corresponding GLSL uniforms. - GLint num_active_textures = 0; - int uniform_float_idx = 0; - int uniform_int_idx = 0; - - kernel_arg = kernel->arguments; - for (int i = 0; args[i]; i++, kernel_arg = kernel_arg->next) { - - if (kernel_arg->kind == Argument::Outbuf) { - halide_assert(user_context, is_buffer[i] && "OpenGL Outbuf argument is not a buffer."); - // Check if the output buffer will be bound by the client instead of - // the Halide runtime - uint64_t handle = ((halide_buffer_t *)args[i])->device; - if (!handle) { - error(user_context) << "GLSL: Encountered invalid nullptr dev pointer"; - return 1; - } - if (handle == HALIDE_OPENGL_RENDER_TARGET) { - bind_render_targets = false; - } - // Outbuf textures are handled explicitly below - continue; - } else if (kernel_arg->kind == Argument::Inbuf) { - halide_assert(user_context, is_buffer[i] && "OpenGL Inbuf argument is not a buffer."); - GLint loc = - global_state.GetUniformLocation(kernel->program_id, kernel_arg->name); - if (global_state.CheckAndReportError(user_context, "halide_opengl_run GetUniformLocation(InBuf)")) { - return 1; - } - if (loc == -1) { - error(user_context) << "No sampler defined for input texture."; - return 1; - } - uint64_t handle = ((halide_buffer_t *)args[i])->device; - if (!handle) { - error(user_context) << "GLSL: Encountered invalid nullptr dev pointer"; - return 1; - } - global_state.ActiveTexture(GL_TEXTURE0 + num_active_textures); - global_state.BindTexture(GL_TEXTURE_2D, handle == HALIDE_OPENGL_RENDER_TARGET ? 0 : (GLuint)handle); - global_state.Uniform1iv(loc, 1, &num_active_textures); - - // Textures not created by the Halide runtime might not have - // parameters set, or might have had parameters set differently - global_state.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - global_state.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - global_state.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - global_state.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - - num_active_textures++; - // TODO: check maximum number of active textures - } else if (kernel_arg->kind == Argument::Uniform) { - // Copy the uniform parameter into the packed scalar list - // corresponding to its type. - - // Note: small integers are represented as floats in GLSL. - switch (kernel_arg->type) { - case Argument::Float: - uniform_float[uniform_float_idx++] = *(float *)args[i]; - break; - case Argument::Bool: - uniform_int[uniform_int_idx++] = *((bool *)args[i]) ? 1 : 0; - break; - case Argument::Int8: - uniform_float[uniform_float_idx++] = *((int8_t *)args[i]); - break; - case Argument::UInt8: - uniform_float[uniform_float_idx++] = *((uint8_t *)args[i]); - break; - case Argument::Int16: { - uniform_float[uniform_float_idx++] = *((int16_t *)args[i]); - break; - } - case Argument::UInt16: { - uniform_float[uniform_float_idx++] = *((uint16_t *)args[i]); - break; - } - case Argument::Int32: { - uniform_int[uniform_int_idx++] = *((int32_t *)args[i]); - break; - } - case Argument::UInt32: { - uint32_t value = *((uint32_t *)args[i]); - if (value > 0x7fffffff) { - error(user_context) - << "OpenGL: argument '" << kernel_arg->name << "' is too large for GLint"; - return -1; - } - uniform_int[uniform_int_idx++] = static_cast(value); - break; - } - case Argument::Void: - error(user_context) << "OpenGL: Encountered invalid kernel argument type"; - return 1; - } - } - } - - if (kernel_arg) { - error(user_context) << "Too few arguments passed to halide_opengl_run"; - return 1; - } - - // Set the packed uniform int parameters - for (int idx = 0; idx != num_padded_uniform_ints; idx += 4) { - - // Produce the uniform parameter name without using the std library. - Printer name(user_context); - name << "_uniformi" << (idx / 4); - - GLint loc = global_state.GetUniformLocation(kernel->program_id, name.str()); - if (global_state.CheckAndReportError(user_context, "halide_opengl_run GetUniformLocation")) { - return 1; - } - if (loc == -1) { - // Argument was probably optimized away by GLSL compiler. - continue; - } - - global_state.Uniform4iv(loc, 1, &uniform_int[idx]); - } - - // Set the packed uniform float parameters - for (int idx = 0; idx != num_padded_uniform_floats; idx += 4) { - - // Produce the uniform parameter name without using the std library. - Printer name(user_context); - name << "_uniformf" << (idx / 4); - - GLint loc = global_state.GetUniformLocation(kernel->program_id, name.str()); - if (global_state.CheckAndReportError(user_context, "halide_opengl_run GetUniformLocation(2)")) { - return 1; - } - if (loc == -1) { - // Argument was probably optimized away by GLSL compiler. - continue; - } - - global_state.Uniform4fv(loc, 1, &uniform_float[idx]); - } - - // Prepare framebuffer for rendering to output textures. - GLint output_min[2] = {0, 0}; - GLint output_extent[2] = {0, 0}; - - if (bind_render_targets) { - global_state.BindFramebuffer(GL_FRAMEBUFFER, global_state.framebuffer_id); - } - - global_state.Disable(GL_CULL_FACE); - global_state.Disable(GL_DEPTH_TEST); - - GLint num_output_textures = 0; - kernel_arg = kernel->arguments; - for (int i = 0; args[i]; i++, kernel_arg = kernel_arg->next) { - if (kernel_arg->kind != Argument::Outbuf) { - continue; - } - - halide_assert(user_context, is_buffer[i] && "OpenGL Outbuf argument is not a buffer."); - - // TODO: GL_MAX_COLOR_ATTACHMENTS - if (num_output_textures >= 1) { - error(user_context) - << "OpenGL ES 2.0 only supports one single output texture"; - return 1; - } - - halide_buffer_t *buf = (halide_buffer_t *)args[i]; - halide_assert(user_context, buf->dimensions >= 2); - uint64_t handle = buf->device; - if (!handle) { - error(user_context) << "GLSL: Encountered invalid nullptr dev pointer"; - return 1; - } - GLuint tex = (handle == HALIDE_OPENGL_RENDER_TARGET) ? 0 : (GLuint)handle; - - // Check to see if the object name is actually a FBO - if (bind_render_targets) { - debug(user_context) - << "Output texture " << num_output_textures << ": " << tex << "\n"; - global_state.FramebufferTexture2D(GL_FRAMEBUFFER, - GL_COLOR_ATTACHMENT0 + num_output_textures, - GL_TEXTURE_2D, tex, 0); - if (global_state.CheckAndReportError(user_context, "halide_opengl_run FramebufferTexture2D")) { - return 1; - } - } - - output_min[0] = buf->dim[0].min; - output_min[1] = buf->dim[1].min; - output_extent[0] = buf->dim[0].extent; - output_extent[1] = buf->dim[1].extent; - num_output_textures++; - } - // TODO: GL_MAX_DRAW_BUFFERS - if (num_output_textures == 0) { - error(user_context) << "halide_opengl_run: kernel has no output\n"; - // TODO: cleanup - return 1; - } else if (num_output_textures > 1) { - if (global_state.DrawBuffers) { - HalideMalloc draw_buffers_tmp(user_context, num_output_textures * sizeof(GLenum)); - if (!draw_buffers_tmp.ptr) { - error(user_context) << "halide_malloc"; - return 1; - } - GLenum *draw_buffers = (GLenum *)draw_buffers_tmp.ptr; - for (int i = 0; i < num_output_textures; i++) { - draw_buffers[i] = GL_COLOR_ATTACHMENT0 + i; - } - global_state.DrawBuffers(num_output_textures, draw_buffers); - if (global_state.CheckAndReportError(user_context, "halide_opengl_run DrawBuffers")) { - return 1; - } - } else { - error(user_context) << "halide_opengl_run: kernel has more than one output and DrawBuffers is not available (earlier than GL ES 3.0?).\n"; - // TODO: cleanup - return 1; - } - } - - if (bind_render_targets) { - // Check that framebuffer is set up correctly - GLenum status = global_state.CheckFramebufferStatus(GL_FRAMEBUFFER); - if (global_state.CheckAndReportError(user_context, "halide_opengl_run CheckFramebufferStatus")) { - return 1; - } - if (status != GL_FRAMEBUFFER_COMPLETE) { - error(user_context) - << "Setting up GL framebuffer " << global_state.framebuffer_id - << " failed (" << status << ")"; - // TODO: cleanup - return 1; - } - } - - // Set vertex attributes - GLint loc = global_state.GetUniformLocation(kernel->program_id, "output_extent"); - global_state.Uniform2iv(loc, 1, output_extent); - if (global_state.CheckAndReportError(user_context, "halide_opengl_run Uniform2iv(output_extent)")) { - return 1; - } - loc = global_state.GetUniformLocation(kernel->program_id, "output_min"); - global_state.Uniform2iv(loc, 1, output_min); - if (global_state.CheckAndReportError(user_context, "halide_opengl_run Uniform2iv(output_min)")) { - return 1; - } - -#if 0 // DEBUG_RUNTIME - debug(user_context) << "output_extent: " << output_extent[0] << "," << output_extent[1] << "\n"; - debug(user_context) << "output_min: " << output_min[0] << "," << output_min[1] << "\n"; -#endif - - // TODO(abestephensg): Sort coordinate dimensions when the linear solver is integrated - // Sort the coordinates - - // Construct an element buffer using the sorted vertex order. - // Note that this is "width" and "height" of the vertices, not the output image. - int width = num_coords_dim0; - int height = num_coords_dim1; - - int vertex_buffer_size = width * height * num_padded_attributes; - - int element_buffer_size = (width - 1) * (height - 1) * 6; - int element_buffer[element_buffer_size]; - - int idx = 0; - for (int h = 0; h != (height - 1); ++h) { - for (int w = 0; w != (width - 1); ++w) { - - // TODO(abestephensg): Use sorted coordinates when integrated - int v = w + h * width; - element_buffer[idx++] = v; - element_buffer[idx++] = v + 1; - element_buffer[idx++] = v + width + 1; - - element_buffer[idx++] = v + width + 1; - element_buffer[idx++] = v + width; - element_buffer[idx++] = v; - } - } - -#if 0 // DEBUG_RUNTIME - debug(user_context) << "Vertex buffer:"; - for (int i=0;i!=vertex_buffer_size;++i) { - if (!(i%num_padded_attributes)) { - debug(user_context) << "\n"; - } - debug(user_context) << vertex_buffer[i] << " "; - } - debug(user_context) << "\n"; - debug(user_context) << "\n"; - - debug(user_context) << "Element buffer:"; - for (int i=0;i!=element_buffer_size;++i) { - if (!(i%3)) { - debug(user_context) << "\n"; - } - debug(user_context) << element_buffer[i] << " "; - } - debug(user_context) << "\n"; -#endif - - // Setup viewport - global_state.Viewport(0, 0, output_extent[0], output_extent[1]); - - // Setup the vertex and element buffers - GLuint vertex_array_object = 0; - if (global_state.have_vertex_array_objects) { - global_state.GenVertexArrays(1, &vertex_array_object); - global_state.BindVertexArray(vertex_array_object); - } - - GLuint vertex_buffer_id; - global_state.GenBuffers(1, &vertex_buffer_id); - global_state.BindBuffer(GL_ARRAY_BUFFER, vertex_buffer_id); - global_state.BufferData(GL_ARRAY_BUFFER, sizeof(float) * vertex_buffer_size, vertex_buffer, GL_STATIC_DRAW); - if (global_state.CheckAndReportError(user_context, "halide_opengl_run vertex BufferData et al")) { - return 1; - } - - GLuint element_buffer_id; - global_state.GenBuffers(1, &element_buffer_id); - global_state.BindBuffer(GL_ELEMENT_ARRAY_BUFFER, element_buffer_id); - global_state.BufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(float) * element_buffer_size, element_buffer, GL_STATIC_DRAW); - if (global_state.CheckAndReportError(user_context, "halide_opengl_run element BufferData et al")) { - return 1; - } - - // The num_padded_attributes argument is the number of vertex attributes, - // including the spatial x and y coordinates, padded up to a multiple of - // four so that the attributes may be packed into vec4 slots. - int num_packed_attributes = num_padded_attributes / 4; - - // Set up the per vertex attributes - GLint attrib_ids[num_packed_attributes]; - - for (int i = 0; i != num_packed_attributes; i++) { - - // The attribute names can synthesized by the runtime based on the - // number of packed varying attributes - Printer attribute_name(user_context); - attribute_name << "_varyingf" << i << "_attrib"; - - // TODO(abstephensg): Switch to glBindAttribLocation - GLint attrib_id = global_state.GetAttribLocation(kernel->program_id, attribute_name.buf); - attrib_ids[i] = attrib_id; - - // Check to see if the varying attribute was simplified out of the - // program by the GLSL compiler. - if (attrib_id == -1) { - continue; - } - - global_state.VertexAttribPointer(attrib_id, 4, GL_FLOAT, GL_FALSE /* Normalized */, sizeof(GLfloat) * num_padded_attributes, (void *)(i * sizeof(GLfloat) * 4)); - if (global_state.CheckAndReportError(user_context, "halide_opengl_run VertexAttribPointer et al")) { - return 1; - } - - global_state.EnableVertexAttribArray(attrib_id); - if (global_state.CheckAndReportError(user_context, "halide_opengl_run EnableVertexAttribArray et al")) { - return 1; - } - } - - // Draw the scene - global_state.DrawElements(GL_TRIANGLES, element_buffer_size, GL_UNSIGNED_INT, nullptr); - if (global_state.CheckAndReportError(user_context, "halide_opengl_run DrawElements et al")) { - return 1; - } - - // Cleanup - if (global_state.have_vertex_array_objects) { - global_state.DeleteVertexArrays(1, &vertex_array_object); - } - - global_state.DeleteBuffers(1, &vertex_buffer_id); - global_state.DeleteBuffers(1, &element_buffer_id); - - return 0; -} - -WEAK int halide_opengl_device_sync(void *user_context, struct halide_buffer_t *) { - if (!global_state.initialized) { - error(user_context) << "OpenGL runtime not initialized (halide_opengl_device_sync)."; - return 1; - } -#ifdef DEBUG_RUNTIME - int64_t t0 = halide_current_time_ns(user_context); -#endif - global_state.Finish(); -#ifdef DEBUG_RUNTIME - int64_t t1 = halide_current_time_ns(user_context); - debug(user_context) << "halide_opengl_device_sync: took " << (t1 - t0) / 1e3 << "usec\n"; -#endif - return 0; -} - -// Called at the beginning of a code block generated by Halide. This function -// is responsible for setting up the OpenGL environment and compiling the GLSL -// code into a fragment shader. -WEAK int halide_opengl_initialize_kernels(void *user_context, void **state_ptr, - const char *src, int size) { - debug(user_context) << "In initialize_kernels\n"; - - if (int error = halide_opengl_init(user_context)) { - return error; - } - - const char *this_kernel = src; - - ModuleState **state = (ModuleState **)state_ptr; - ModuleState *module = *state; - - while (this_kernel) { - // Find the start of the next kernel - const char *next_kernel = strstr(this_kernel + 1, kernel_marker); - - // Use that to compute the length of this kernel - int len = 0; - if (!next_kernel) { - len = strlen(this_kernel); - } else { - len = next_kernel - this_kernel; - } - - // Construct a new ModuleState and add it to the global list - module = (ModuleState *)malloc(sizeof(ModuleState)); - module->kernel = nullptr; - module->next = state_list; - state_list = module; - *state = module; - - KernelInfo *kernel = module->kernel; - if (!kernel) { - kernel = create_kernel(user_context, this_kernel, len); - if (!kernel) { - error(user_context) << "Invalid kernel: " << this_kernel; - return -1; - } - module->kernel = kernel; - } - - // Create the vertex shader. The runtime will output boilerplate for the - // vertex shader based on a fixed program plus arguments obtained from - // the comment header passed in the fragment shader. Since there are a - // relatively small number of vertices (i.e. usually only four), per-vertex - // expressions interpolated by varying attributes are evaluated - // by host code on the CPU and passed to the GPU as values in the - // vertex buffer. - enum { PrinterLength = 1024 * 4 }; - Printer vertex_src(user_context); - - // Count the number of varying attributes, this is 2 for the spatial - // x and y coordinates, plus the number of scalar varying attribute - // expressions pulled out of the fragment shader. - int num_varying_float = 2; - - for (Argument *arg = kernel->arguments; arg; arg = arg->next) { - if (arg->kind == Argument::Varying) { - ++num_varying_float; - } - } - - int num_packed_varying_float = ((num_varying_float + 3) & ~0x3) / 4; - - for (int i = 0; i != num_packed_varying_float; ++i) { - vertex_src << "attribute vec4 _varyingf" << i << "_attrib;\n"; - vertex_src << "varying vec4 _varyingf" << i << ";\n"; - } - - vertex_src << "uniform ivec2 output_min;\n" - << "uniform ivec2 output_extent;\n" - << "void main() {\n" - - // Host codegen always passes the spatial vertex coordinates - // in the first two elements of the _varyingf0_attrib - << " vec2 position = vec2(_varyingf0_attrib[0], _varyingf0_attrib[1]);\n" - << " gl_Position = vec4(position, 0.0, 1.0);\n" - << " vec2 texcoord = 0.5 * position + 0.5;\n" - << " vec2 pixcoord = texcoord * vec2(output_extent.xy) + vec2(output_min.xy);\n"; - - // Copy through all of the varying attributes - for (int i = 0; i != num_packed_varying_float; ++i) { - vertex_src << " _varyingf" << i << " = _varyingf" << i << "_attrib;\n"; - } - - vertex_src << " _varyingf0.xy = pixcoord;\n"; - - vertex_src << "}\n"; - - // Check to see if there was sufficient storage for the vertex program. - if (vertex_src.size() >= PrinterLength) { - error(user_context) << "Vertex shader source truncated"; - return 1; - } - - // Initialize vertex shader. - GLuint vertex_shader_id = make_shader(user_context, - GL_VERTEX_SHADER, vertex_src.buf, nullptr); - if (vertex_shader_id == 0) { - halide_error(user_context, "Failed to create vertex shader"); - return 1; - } - - // Create the fragment shader - GLuint fragment_shader_id = make_shader(user_context, GL_FRAGMENT_SHADER, - kernel->source, nullptr); - // Link GLSL program - GLuint program = global_state.CreateProgram(); - global_state.AttachShader(program, vertex_shader_id); - global_state.AttachShader(program, fragment_shader_id); - global_state.LinkProgram(program); - - // Release the individual shaders - global_state.DeleteShader(vertex_shader_id); - global_state.DeleteShader(fragment_shader_id); - - GLint status; - global_state.GetProgramiv(program, GL_LINK_STATUS, &status); - if (!status) { - GLint log_len; - global_state.GetProgramiv(program, GL_INFO_LOG_LENGTH, &log_len); - HalideMalloc log_tmp(user_context, log_len); - if (log_tmp.ptr) { - char *log = (char *)log_tmp.ptr; - global_state.GetProgramInfoLog(program, log_len, nullptr, log); - debug(user_context) << "Could not link GLSL program:\n" - << log << "\n"; - } - global_state.DeleteProgram(program); - return -1; - } - kernel->program_id = program; - - this_kernel = next_kernel; - } - return 0; -} - -WEAK int halide_opengl_device_and_host_malloc(void *user_context, struct halide_buffer_t *buf) { - return halide_default_device_and_host_malloc(user_context, buf, &opengl_device_interface); -} - -WEAK int halide_opengl_device_and_host_free(void *user_context, struct halide_buffer_t *buf) { - return halide_default_device_and_host_free(user_context, buf, &opengl_device_interface); -} - -WEAK const halide_device_interface_t *halide_opengl_device_interface() { - return &opengl_device_interface; -} - -WEAK void halide_opengl_context_lost(void *user_context) { - if (!global_state.initialized) { - return; - } - - debug(user_context) << "halide_opengl_context_lost\n"; - for (ModuleState *mod = state_list; mod; mod = mod->next) { - // Reset program handle to force recompilation. - mod->kernel->program_id = 0; - } - - global_state.init(); -} - -WEAK int halide_opengl_wrap_texture(void *user_context, halide_buffer_t *buf, uint64_t texture_id) { - if (!global_state.initialized) { - if (int error = halide_opengl_init(user_context)) { - return error; - } - } - if (texture_id == 0) { - error(user_context) << "Texture " << texture_id << " is not a valid texture name."; - return -3; - } - halide_assert(user_context, buf->device == 0); - if (buf->device != 0) { - return -2; - } - buf->device = texture_id; - buf->device_interface = &opengl_device_interface; - buf->device_interface->impl->use_module(); - return 0; -} - -WEAK int halide_opengl_wrap_render_target(void *user_context, halide_buffer_t *buf) { - if (!global_state.initialized) { - if (int error = halide_opengl_init(user_context)) { - return error; - } - } - halide_assert(user_context, buf->device == 0); - if (buf->device != 0) { - return -2; - } - buf->device = HALIDE_OPENGL_RENDER_TARGET; - buf->device_interface = &opengl_device_interface; - buf->device_interface->impl->use_module(); - return 0; -} - -WEAK int halide_opengl_detach_texture(void *user_context, halide_buffer_t *buf) { - if (buf->device == 0) { - return 0; - } - - halide_assert(user_context, buf->device_interface == &opengl_device_interface); - buf->device = 0; - buf->device_interface->impl->release_module(); - buf->device_interface = nullptr; - return 0; -} - -WEAK uintptr_t halide_opengl_get_texture(void *user_context, halide_buffer_t *buf) { - if (buf->device == 0) { - return 0; - } - halide_assert(user_context, buf->device_interface == &opengl_device_interface); - uint64_t handle = buf->device; - // client_bound always return 0 here. - return handle == HALIDE_OPENGL_RENDER_TARGET ? 0 : (uintptr_t)handle; -} - -namespace { -WEAK __attribute__((destructor)) void halide_opengl_cleanup() { - halide_opengl_device_release(nullptr); -} -} // namespace - -} // extern "C" - -namespace Halide { -namespace Runtime { -namespace Internal { -namespace OpenGL { - -WEAK halide_device_interface_impl_t opengl_device_interface_impl = { - halide_use_jit_module, - halide_release_jit_module, - halide_opengl_device_malloc, - halide_opengl_device_free, - halide_opengl_device_sync, - halide_opengl_device_release, - halide_opengl_copy_to_host, - halide_opengl_copy_to_device, - halide_opengl_device_and_host_malloc, - halide_opengl_device_and_host_free, - halide_default_buffer_copy, - halide_default_device_crop, - halide_default_device_slice, - halide_default_device_release_crop, - halide_opengl_wrap_texture, - halide_opengl_detach_texture}; - -WEAK halide_device_interface_t opengl_device_interface = { - halide_device_malloc, - halide_device_free, - halide_device_sync, - halide_device_release, - halide_copy_to_host, - halide_copy_to_device, - halide_device_and_host_malloc, - halide_device_and_host_free, - halide_buffer_copy, - halide_device_crop, - halide_device_slice, - halide_device_release_crop, - halide_device_wrap_native, - halide_device_detach_native, - nullptr, - &opengl_device_interface_impl}; - -} // namespace OpenGL -} // namespace Internal -} // namespace Runtime -} // namespace Halide diff --git a/src/runtime/runtime_api.cpp b/src/runtime/runtime_api.cpp index 230c907721d0..6351b68a2572 100644 --- a/src/runtime/runtime_api.cpp +++ b/src/runtime/runtime_api.cpp @@ -7,7 +7,6 @@ #include "HalideRuntimeHexagonHost.h" #include "HalideRuntimeMetal.h" #include "HalideRuntimeOpenCL.h" -#include "HalideRuntimeOpenGL.h" #include "HalideRuntimeOpenGLCompute.h" #include "HalideRuntimeQurt.h" #include "cpu_features.h" @@ -148,16 +147,16 @@ extern "C" __attribute__((used)) void *halide_runtime_api_functions[] = { (void *)&halide_opencl_set_device_type, (void *)&halide_opencl_set_platform_name, (void *)&halide_opencl_wrap_cl_mem, - (void *)&halide_opengl_context_lost, - (void *)&halide_opengl_create_context, - (void *)&halide_opengl_detach_texture, - (void *)&halide_opengl_device_interface, - (void *)&halide_opengl_get_proc_address, - (void *)&halide_opengl_get_texture, - (void *)&halide_opengl_initialize_kernels, - (void *)&halide_opengl_run, - (void *)&halide_opengl_wrap_render_target, - (void *)&halide_opengl_wrap_texture, + // (void *)&halide_opengl_context_lost, + // (void *)&halide_opengl_create_context, + // (void *)&halide_opengl_detach_texture, + // (void *)&halide_opengl_device_interface, + // (void *)&halide_opengl_get_proc_address, + // (void *)&halide_opengl_get_texture, + // (void *)&halide_opengl_initialize_kernels, + // (void *)&halide_opengl_run, + // (void *)&halide_opengl_wrap_render_target, + // (void *)&halide_opengl_wrap_texture, (void *)&halide_openglcompute_device_interface, (void *)&halide_openglcompute_initialize_kernels, (void *)&halide_openglcompute_run, diff --git a/test/correctness/gpu_multi_device.cpp b/test/correctness/gpu_multi_device.cpp index b92a1b37ae22..b9a872f33af2 100644 --- a/test/correctness/gpu_multi_device.cpp +++ b/test/correctness/gpu_multi_device.cpp @@ -39,16 +39,6 @@ struct MultiDevicePipeline { .gpu_tile(x, y, xi, yi, 8, 8, TailStrategy::Auto, DeviceAPI::Metal); current_stage++; } - if (jit_target.has_feature(Target::OpenGL)) { - stage[current_stage](x, y, c) = stage[current_stage - 1](x, y, c) + 69; - stage[current_stage] - .compute_root() - .bound(c, 0, 3) - .reorder(c, x, y) - .glsl(x, y, c) - .vectorize(c); - current_stage++; - } if (jit_target.has_feature(Target::OpenGLCompute)) { stage[current_stage](x, y, c) = stage[current_stage - 1](x, y, c) + 69; stage[current_stage] diff --git a/test/correctness/plain_c_includes.c b/test/correctness/plain_c_includes.c index 18529c77a8fb..65a436014cbd 100644 --- a/test/correctness/plain_c_includes.c +++ b/test/correctness/plain_c_includes.c @@ -10,7 +10,6 @@ #include "HalideRuntimeHexagonHost.h" #include "HalideRuntimeMetal.h" #include "HalideRuntimeOpenCL.h" -#include "HalideRuntimeOpenGL.h" #include "HalideRuntimeOpenGLCompute.h" #include "HalideRuntimeQurt.h" diff --git a/test/correctness/target.cpp b/test/correctness/target.cpp index 64060606d0e5..d7d50bffed1c 100644 --- a/test/correctness/target.cpp +++ b/test/correctness/target.cpp @@ -52,7 +52,7 @@ int main(int argc, char **argv) { // Full specification round-trip, crazy features t1 = Target(Target::Android, Target::ARM, 32, {Target::JIT, Target::SSE41, Target::AVX, Target::AVX2, - Target::CUDA, Target::OpenCL, Target::OpenGL, Target::OpenGLCompute, + Target::CUDA, Target::OpenCL, Target::OpenGLCompute, Target::Debug}); ts = t1.to_string(); if (ts != "arm-32-android-avx-avx2-cuda-debug-jit-opencl-opengl-openglcompute-sse41") { diff --git a/test/opengl/CMakeLists.txt b/test/opengl/CMakeLists.txt deleted file mode 100644 index b38c20a3a36a..000000000000 --- a/test/opengl/CMakeLists.txt +++ /dev/null @@ -1,28 +0,0 @@ -if (TARGET OpenGL::GL) - tests(GROUPS opengl - SOURCES - conv_select.cpp - copy_pixels.cpp - copy_to_device.cpp - copy_to_host.cpp - float_texture.cpp - inline_reduction.cpp - internal.cpp - lut.cpp - multiple_stages.cpp - produce.cpp - rewrap_texture.cpp - save_state.cpp - select.cpp - set_pixels.cpp - shifted_domains.cpp - special_funcs.cpp - sumcolor_reduction.cpp - sum_reduction.cpp - tuples.cpp - varying.cpp - ) - foreach (test_name IN LISTS TEST_NAMES) - target_link_libraries("${test_name}" PRIVATE OpenGL::GL) - endforeach () -endif () diff --git a/test/opengl/conv_select.cpp b/test/opengl/conv_select.cpp deleted file mode 100644 index 735c752d06f0..000000000000 --- a/test/opengl/conv_select.cpp +++ /dev/null @@ -1,50 +0,0 @@ -// test case provided by Lee Yuguang - -#include "Halide.h" -#include - -#include "testing.h" - -using namespace Halide; - -int main() { - // This test must be run with an OpenGL target. - const Target target = get_jit_target_from_environment().with_feature(Target::OpenGL); - - // Define the input - const int width = 10, height = 10, channels = 4, res_channels = 2; - Buffer input(width, height, channels); - input.fill([](int x, int y, int c) { - return float(x + y); - }); - - // Define the algorithm. - Var x, y, c; - RDom r(0, 2, "r"); - Func f, g; - - Expr coordx = clamp(x + r, 0, input.width() - 1); - f(x, y, c) = cast(sum(input(coordx, y, c))); - - Expr R = select(f(x, y, c) > 9.0f, 1.0f, 0.0f); - Expr G = select(f(x, y, c) > 9.0f, 0.f, 1.0f); - g(x, y, c) = mux(c, {R, G}); - - // Schedule f and g to compute in separate passes on the GPU. - g.bound(c, 0, 2).glsl(x, y, c); - - // Generate the result. - Buffer result = g.realize(width, height, res_channels, target); - result.copy_to_host(); - - //Check the result. - if (!Testing::check_result(result, [](int x, int y, int c) { - const float temp = ((x + y) > 4) ? 1.0f : 0.0f; - return (c == 0) ? temp : (1.0f - temp); - })) { - return 1; - } - - printf("Success!\n"); - return 0; -} diff --git a/test/opengl/copy_pixels.cpp b/test/opengl/copy_pixels.cpp deleted file mode 100644 index 97cacecd32e1..000000000000 --- a/test/opengl/copy_pixels.cpp +++ /dev/null @@ -1,33 +0,0 @@ -#include "Halide.h" -#include - -#include "testing.h" - -using namespace Halide; - -int main() { - // This test must be run with an OpenGL target. - const Target target = get_jit_target_from_environment().with_feature(Target::OpenGL); - - Buffer input(255, 10, 3); - input.fill([](int x, int y, int c) { - return 10 * x + y + c; - }); - - Var x, y, c; - Func g; - g(x, y, c) = input(x, y, c); - - Buffer out(255, 10, 3); - g.bound(c, 0, 3); - g.glsl(x, y, c); - g.realize(out, target); - out.copy_to_host(); - - if (!Testing::check_result(out, [&](int x, int y, int c) { return input(x, y, c); })) { - return 1; - } - - printf("Success!\n"); - return 0; -} diff --git a/test/opengl/copy_to_device.cpp b/test/opengl/copy_to_device.cpp deleted file mode 100644 index 0feedf5895c8..000000000000 --- a/test/opengl/copy_to_device.cpp +++ /dev/null @@ -1,38 +0,0 @@ -#include "Halide.h" - -#include "testing.h" - -using namespace Halide; - -// Test that internal allocations work correctly with copy_to_device. -// This requires that suitable halide_buffer_t objects are created internally. -int main() { - // This test must be run with an OpenGL target. - const Target target = get_jit_target_from_environment().with_feature(Target::OpenGL); - - Buffer input(255, 10, 3); - input.fill([](int x, int y, int c) { - return 10 * x + y + c; - }); - - Var x, y, c; - Func g, h; - h(x, y, c) = input(x, y, c); - h.compute_root(); // force internal allocation of h - - // access h from shader to trigger copy_to_device operation - g(x, y, c) = h(x, y, c); - g.bound(c, 0, 3); - g.glsl(x, y, c); - - Buffer out(255, 10, 3); - g.realize(out, target); - out.copy_to_host(); - - if (!Testing::check_result(out, [&](int x, int y, int c) { return input(x, y, c); })) { - return 1; - } - - printf("Success!\n"); - return 0; -} diff --git a/test/opengl/copy_to_host.cpp b/test/opengl/copy_to_host.cpp deleted file mode 100644 index c03759065a9d..000000000000 --- a/test/opengl/copy_to_host.cpp +++ /dev/null @@ -1,39 +0,0 @@ -#include "Halide.h" -#include - -#include "testing.h" - -using namespace Halide; - -int main() { - // This test must be run with an OpenGL target. - const Target target = get_jit_target_from_environment().with_feature(Target::OpenGL); - - Func gpu("gpu"), cpu("cpu"); - Var x, y, c; - - // Fill buffer using GLSL - gpu(x, y, c) = cast(mux(c, {10 * x + y, 127, 12})); - gpu.bound(c, 0, 3); - gpu.glsl(x, y, c); - gpu.compute_root(); - - // This should trigger a copy_to_host operation - cpu(x, y, c) = gpu(x, y, c); - - Buffer out(10, 10, 3); - cpu.realize(out, target); - - if (!Testing::check_result(out, [&](int x, int y, int c) { - switch (c) { - case 0: return 10*x+y; - case 1: return 127; - case 2: return 12; - default: return -1; - } })) { - return 1; - } - - printf("Success!\n"); - return 0; -} diff --git a/test/opengl/float_texture.cpp b/test/opengl/float_texture.cpp deleted file mode 100644 index 166863d559ea..000000000000 --- a/test/opengl/float_texture.cpp +++ /dev/null @@ -1,37 +0,0 @@ -#include "Halide.h" -#include - -#include "testing.h" - -using namespace Halide; - -int main() { - // This test must be run with an OpenGL target. - const Target target = get_jit_target_from_environment().with_feature(Target::OpenGL); - - Buffer input(255, 255, 3); - input.fill([](int x, int y, int c) { - // Note: the following values can be >1.0f to test whether - // OpenGL performs clamping operations as part of the copy - // operation. (It may do so if something other than floats - // are stored in the actual texture.) - return (10 * x + y + c); - }); - - Var x, y, c; - Func g; - g(x, y, c) = input(x, y, c); - - Buffer out(255, 255, 3); - g.bound(c, 0, 3); - g.glsl(x, y, c); - g.realize(out, target); - out.copy_to_host(); - - if (!Testing::check_result(out, [&](int x, int y, int c) { return input(x, y, c); })) { - return 1; - } - - printf("Success!\n"); - return 0; -} diff --git a/test/opengl/inline_reduction.cpp b/test/opengl/inline_reduction.cpp deleted file mode 100644 index 6630145e284f..000000000000 --- a/test/opengl/inline_reduction.cpp +++ /dev/null @@ -1,27 +0,0 @@ -#include "Halide.h" -#include - -#include "testing.h" - -using namespace Halide; - -int main() { - // This test must be run with an OpenGL target. - const Target target = get_jit_target_from_environment().with_feature(Target::OpenGL); - - Func f; - Var x, y, c; - RDom r(0, 10); - f(x, y, c) = sum(cast(r)); - f.bound(c, 0, 3).glsl(x, y, c); - - Buffer result = f.realize(100, 100, 3, target); - - if (!Testing::check_result(result, [&](int x, int y, int c) { return 45; })) { - return 1; - } - - printf("Success!\n"); - - return 0; -} diff --git a/test/opengl/internal.cpp b/test/opengl/internal.cpp deleted file mode 100644 index e1ce9c34ed5b..000000000000 --- a/test/opengl/internal.cpp +++ /dev/null @@ -1,10 +0,0 @@ -#include "../../src/CodeGen_OpenGL_Dev.h" - -using namespace Halide; -using namespace Halide::Internal; - -int main() { - CodeGen_GLSL::test(); - - return 0; -} diff --git a/test/opengl/lut.cpp b/test/opengl/lut.cpp deleted file mode 100644 index d51f7f1f8bf6..000000000000 --- a/test/opengl/lut.cpp +++ /dev/null @@ -1,76 +0,0 @@ - -#include "Halide.h" -#include - -#include "testing.h" - -using namespace Halide; - -// This test creates two input images and uses one to perform a dependent lookup -// into the other. - -int test_lut1d() { - - // This test must be run with an OpenGL target. - const Target target = get_jit_target_from_environment().with_feature(Target::OpenGL); - - Var x("x"); - Var y("y"); - Var c("c"); - - Buffer input(8, 8, 3); - input.fill([](int x, int y, int c) { - const float v = (1.0f / 16.0f) + (float)x / 8.0f; - switch (c) { - case 0: - return (uint8_t)(v * 255.0f); - case 1: - return (uint8_t)((1.0f - v) * 255.0f); - default: - return (uint8_t)((v > 0.5 ? 1.0 : 0.0) * 255.0f); - } - }); - - // 1D Look Up Table case - Buffer lut1d(8, 1, 3); - for (int c = 0; c != 3; ++c) { - for (int i = 0; i != 8; ++i) { - lut1d(i, 0, c) = (float)(1 + i); - } - } - - Func f0("f"); - Expr e = cast(8.0f * cast(input(x, y, c)) / 255.0f); - - f0(x, y, c) = lut1d(clamp(e, 0, 7), 0, c); - - Buffer out0(8, 8, 3); - - f0.bound(c, 0, 3); - f0.glsl(x, y, c); - f0.realize(out0, target); - out0.copy_to_host(); - - if (!Testing::check_result(out0, [](int x, int y, int c) { - switch (c) { - case 0: return (float)(1 + x); - case 1: return (float)(8 - x); - case 2: return (x > 3) ? 8.0f : 1.0f; - default: return std::numeric_limits::infinity(); - } })) { - return 1; - } - - return 0; -} - -int main() { - - if (test_lut1d() == 0) { - printf("Success!\n"); - } else { - printf("FAILED\n"); - } - - return 0; -} diff --git a/test/opengl/multiple_stages.cpp b/test/opengl/multiple_stages.cpp deleted file mode 100644 index f5bac6b8b197..000000000000 --- a/test/opengl/multiple_stages.cpp +++ /dev/null @@ -1,44 +0,0 @@ -#include "Halide.h" -#include - -#include "testing.h" - -using namespace Halide; - -int main() { - - // This test must be run with an OpenGL target. - const Target target = get_jit_target_from_environment().with_feature(Target::OpenGL); - - Func f, g, h; - Var x, y, c; - g(x, y, c) = cast(x); - h(x, y, c) = 1 + g(x, y, c); - f(x, y, c) = h(x, y, c) + cast(y); - f.bound(c, 0, 3).glsl(x, y, c); - h.bound(c, 0, 3).compute_root(); - g.bound(c, 0, 3).compute_root().glsl(x, y, c); - - Buffer result = f.realize(10, 10, 3, target); - result.copy_to_host(); - - if (!Testing::check_result(result, [&](int i, int j, int k) { return i + j + 1; })) { - return 1; - } - - Func f2, g2; - f2(x, y, c) = cast(x); - g2(x, y, c) = f2(x, y, c) + cast(y); - - f2.bound(c, 0, 3).glsl(x, y, c).compute_root(); - g2.bound(c, 0, 3).glsl(x, y, c); - - Buffer result2 = g2.realize(10, 10, 3, target); - if (!Testing::check_result(result2, 0.01f, [&](int i, int j, int k) { return (float)(i + j); })) { - return 1; - } - - printf("Success!\n"); - - return 0; -} diff --git a/test/opengl/produce.cpp b/test/opengl/produce.cpp deleted file mode 100644 index 002f9ec89045..000000000000 --- a/test/opengl/produce.cpp +++ /dev/null @@ -1,70 +0,0 @@ -#include "Halide.h" -#include - -#include "testing.h" - -using namespace Halide; - -// This test creates two input images and uses one to perform a dependent lookup -// into the other. The lookup table is produced using a Halide func scheduled -// on the host. - -int test_lut1d() { - - // This test must be run with an OpenGL target. - const Target target = get_jit_target_from_environment().with_feature(Target::OpenGL); - - Var x("x"); - Var y("y"); - Var c("c"); - - Buffer input(8, 8, 3); - input.fill([](int x, int y, int c) { - float v = (1.0f / 16.0f) + (float)x / 8.0f; - switch (c) { - case 0: return (uint8_t)(v * 255.0f); - case 1: return (uint8_t)((1.0f - v) * 255.0f); - default: return (uint8_t)((v > 0.5 ? 1.0 : 0.0) * 255.0f); - } }); - - // 1D Look Up Table case - Func lut1d("lut1d"); - lut1d(x) = cast(1 + x); - - Func f0("f"); - Expr e = cast(8.0f * cast(input(x, y, c)) / 255.0f); - - f0(x, y, c) = lut1d(clamp(e, 0, 7)); - lut1d.compute_root(); - - f0.bound(c, 0, 3); - f0.glsl(x, y, c); - - Buffer out0(8, 8, 3); - f0.realize(out0, target); - - out0.copy_to_host(); - - if (!Testing::check_result(out0, [](int x, int y, int c) { - switch (c) { - case 0: return (float)(1 + x); - case 1: return (float)(8 - x); - case 2: return (x > 3) ? 8.0f : 1.0f; - default: return -1.0f; - } })) { - return 1; - } - - return 0; -} - -int main() { - - if (test_lut1d() == 0) { - printf("Success!\n"); - } else { - printf("FAILED\n"); - } - - return 0; -} diff --git a/test/opengl/rewrap_texture.cpp b/test/opengl/rewrap_texture.cpp deleted file mode 100644 index f19842338670..000000000000 --- a/test/opengl/rewrap_texture.cpp +++ /dev/null @@ -1,67 +0,0 @@ -#ifdef _WIN32 -#include -int main() { - printf("[SKIP] OpenGL on Windows is broken.\n"); - return 0; -} -#else - -#include "Halide.h" - -#include - -#if __APPLE__ -// TODO: why are these deprecated? Can we update this test? -#define GL_SILENCE_DEPRECATION -#include -#else -#include -#endif - -using namespace Halide; - -int main() { - - // This test must be run with an OpenGL target. - const Target target = get_jit_target_from_environment().with_feature(Target::OpenGL); - - const int width = 255; - const int height = 10; - - Buffer input(width, height, 3); - Buffer out1(width, height, 3); - Buffer out2(width, height, 3); - Buffer out3(width, height, 3); - - Var x, y, c; - Func g; - g(x, y, c) = input(x, y, c); - g.bound(c, 0, 3); - g.glsl(x, y, c); - - g.realize(out1, target); // run once to initialize OpenGL - - GLuint texture_id; - glGenTextures(1, &texture_id); - glBindTexture(GL_TEXTURE_2D, texture_id); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); - - // wrapping a texture should work - out2.device_wrap_native(DeviceAPI::GLSL, texture_id, target); - g.realize(out2, target); - out2.device_detach_native(); - - // re-wrapping the texture should not abort - out3.device_wrap_native(DeviceAPI::GLSL, texture_id, target); - g.realize(out3, target); - out3.device_detach_native(); - - printf("Success!\n"); - return 0; -} - -#endif diff --git a/test/opengl/save_state.cpp b/test/opengl/save_state.cpp deleted file mode 100644 index 574565775728..000000000000 --- a/test/opengl/save_state.cpp +++ /dev/null @@ -1,314 +0,0 @@ -#ifdef _WIN32 -#include -int main() { - printf("[SKIP] OpenGL on Windows is broken.\n"); - return 0; -} -#else - -#include "Halide.h" - -#include -#include -#include -#include - -#if __APPLE__ -// TODO: why are these deprecated? Can we update this test? -#define GL_SILENCE_DEPRECATION -#include -#else -#define GL_GLEXT_PROTOTYPES -#include -#endif - -// Generates an arbitrary program. -class Program { -public: - static GLuint handle() { - const char *vertexShader = " \ - attribute vec4 Position; \ - attribute vec2 TexCoordIn; \ - varying vec2 TexCoordOut; \ - void main(void) { \ - gl_Position = Position; \ - TexCoordOut = TexCoordIn; \ - }"; - - const char *fragmentShader = " \ - varying vec2 TexCoordOut; \ - uniform sampler2D Texture; \ - void main(void) { \ - gl_FragColor = texture2D(Texture, TexCoordOut); \ - }"; - - GLuint handle = glCreateProgram(); - glAttachShader(handle, compileShader("vertex", vertexShader, GL_VERTEX_SHADER)); - glAttachShader(handle, compileShader("fragment", fragmentShader, GL_FRAGMENT_SHADER)); - glLinkProgram(handle); - - GLint linkSuccess; - glGetProgramiv(handle, GL_LINK_STATUS, &linkSuccess); - if (linkSuccess == GL_FALSE) { - GLchar messages[256]; - glGetProgramInfoLog(handle, sizeof(messages), 0, messages); - fprintf(stderr, "Error linking program: %s\n", messages); - exit(1); - } - - return handle; - } - -private: - static GLuint compileShader(const char *label, const char *shaderString, GLenum shaderType) { - const GLuint handle = glCreateShader(shaderType); - const int len = strlen(shaderString); - glShaderSource(handle, 1, &shaderString, &len); - glCompileShader(handle); - GLint compileSuccess; - glGetShaderiv(handle, GL_COMPILE_STATUS, &compileSuccess); - if (compileSuccess == GL_FALSE) { - GLchar messages[256]; - glGetShaderInfoLog(handle, sizeof(messages), 0, messages); - fprintf(stderr, "Error compiling %s shader: %s\n", label, messages); - exit(1); - } - return handle; - } -}; - -// Encapsulates setting OpenGL's state to arbitrary values, and checking -// whether the state matches those values. -class KnownState { -private: - void gl_enable(GLenum cap, bool state) { - (state ? glEnable : glDisable)(cap); - } - - GLuint gl_gen(void (*fn)(GLsizei, GLuint *)) { - GLuint val; - (*fn)(1, &val); - return val; - } - - void check_value(const char *operation, const char *label, GLenum pname, GLint initial) { - GLint val; - glGetIntegerv(pname, &val); - if (val != initial) { - fprintf(stderr, "%s did not restore %s: initial value was %d (%#x), current value is %d (%#x)\n", operation, label, initial, initial, val, val); - errors = true; - } - } - - void check_value(const char *operation, const char *label, GLenum pname, GLenum initial) { - check_value(operation, label, pname, (GLint)initial); - } - - void check_value(const char *operation, const char *label, GLenum pname, GLint initial[], int n = 4) { - GLint val[2048]; - glGetIntegerv(pname, val); - for (int i = 0; i < n; i++) { - if (val[i] != initial[i]) { - fprintf(stderr, "%s did not restore %s: initial value was", operation, label); - for (int j = 0; j < n; j++) { - fprintf(stderr, " %d", initial[j]); - } - fprintf(stderr, ", current value is"); - for (int j = 0; j < n; j++) { - fprintf(stderr, " %d", val[j]); - } - fprintf(stderr, "\n"); - errors = true; - return; - } - } - } - - void check_value(const char *operation, const char *label, GLenum pname, bool initial) { - GLboolean val; - glGetBooleanv(pname, &val); - if (val != initial) { - fprintf(stderr, "%s did not restore boolean %s: initial value was %s, current value is %s\n", operation, label, initial ? "true" : "false", val ? "true" : "false"); - errors = true; - } - } - - void check_error(const char *label) { - GLenum err = glGetError(); - if (err != GL_NO_ERROR) { - fprintf(stderr, "Error setting %s: OpenGL error %#x\n", label, err); - errors = true; - } - } - - // version of OpenGL - int gl_major_version; - int gl_minor_version; - - GLenum initial_active_texture; - GLint initial_viewport[4]; - GLuint initial_array_buffer_binding; - GLuint initial_element_array_buffer_binding; - GLuint initial_current_program; - GLuint initial_framebuffer_binding; - static const int ntextures = 10; - GLuint initial_bound_textures[ntextures]; - bool initial_cull_face; - bool initial_depth_test; - - static const int nvertex_attribs = 10; - bool initial_vertex_attrib_array_enabled[nvertex_attribs]; - - // The next two functions are stolen from opengl.cpp - // and are used to parse the major/minor version of OpenGL - // to see if vertex array objects are supported - const char *parse_int(const char *str, int *val) { - int v = 0; - size_t i = 0; - while (str[i] >= '0' && str[i] <= '9') { - v = 10 * v + (str[i] - '0'); - i++; - } - if (i > 0) { - *val = v; - return &str[i]; - } - return nullptr; - } - - const char *parse_opengl_version(const char *str, int *major, int *minor) { - str = parse_int(str, major); - if (str == nullptr || *str != '.') { - return nullptr; - } - return parse_int(str + 1, minor); - } - - GLuint initial_vertex_array_binding; - -public: - bool errors{false}; - - // This sets most values to generated or arbitrary values, which the - // halide calls would be unlikely to accidentally use. But for boolean - // values, we want to be sure that halide is really restoring the - // initial value, not just setting it to true or false. So we need to - // be able to try both. - void setup(bool boolval) { - // parse the OpenGL version - const char *version = (const char *)glGetString(GL_VERSION); - parse_opengl_version(version, &gl_major_version, &gl_minor_version); - - glGenTextures(ntextures, initial_bound_textures); - for (int i = 0; i < ntextures; i++) { - glActiveTexture(GL_TEXTURE0 + i); - glBindTexture(GL_TEXTURE_2D, initial_bound_textures[i]); - } - glActiveTexture(initial_active_texture = GL_TEXTURE3); - - // Vertex array objects are only used by Halide if the OpenGL version >=3 - if (gl_major_version >= 3) { - glBindVertexArray(initial_vertex_array_binding = gl_gen(glGenVertexArrays)); - } - - for (int i = 0; i < nvertex_attribs; i++) { - if ((initial_vertex_attrib_array_enabled[i] = boolval)) { - glEnableVertexAttribArray(i); - } else { - glDisableVertexAttribArray(i); - } - char buf[256]; - sprintf(buf, "vertex attrib array %d state", i); - check_error(buf); - } - - glUseProgram(initial_current_program = Program::handle()); - glViewport(initial_viewport[0] = 111, initial_viewport[1] = 222, initial_viewport[2] = 333, initial_viewport[3] = 444); - gl_enable(GL_CULL_FACE, initial_cull_face = boolval); - gl_enable(GL_DEPTH_TEST, initial_depth_test = boolval); - glBindBuffer(GL_ARRAY_BUFFER, initial_array_buffer_binding = gl_gen(glGenBuffers)); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, initial_element_array_buffer_binding = gl_gen(glGenBuffers)); - glBindFramebuffer(GL_FRAMEBUFFER, initial_framebuffer_binding = gl_gen(glGenFramebuffers)); - - check_error("known state"); - } - - void check(const char *operation) { - check_value(operation, "ActiveTexture", GL_ACTIVE_TEXTURE, initial_active_texture); - check_value(operation, "current program", GL_CURRENT_PROGRAM, initial_current_program); - check_value(operation, "framebuffer binding", GL_FRAMEBUFFER_BINDING, initial_framebuffer_binding); - check_value(operation, "array buffer binding", GL_ARRAY_BUFFER_BINDING, initial_array_buffer_binding); - check_value(operation, "element array buffer binding", GL_ELEMENT_ARRAY_BUFFER_BINDING, initial_element_array_buffer_binding); - check_value(operation, "viewport", GL_VIEWPORT, initial_viewport); - check_value(operation, "GL_CULL_FACE", GL_CULL_FACE, initial_cull_face); - check_value(operation, "GL_DEPTH_TEST", GL_DEPTH_TEST, initial_cull_face); - - // Vertex array objects are only used by Halide if the OpenGL version >=3 - if (gl_major_version >= 3) { - check_value(operation, "vertex array binding", GL_VERTEX_ARRAY_BINDING, initial_vertex_array_binding); - } else { - fprintf(stderr, "Skipping vertex array binding tests because OpenGL version is %d.%d (<3.0)\n", gl_major_version, gl_minor_version); - } - - for (int i = 0; i < ntextures; i++) { - char buf[100]; - sprintf(buf, "bound texture (unit %d)", i); - glActiveTexture(GL_TEXTURE0 + i); - check_value(operation, buf, GL_TEXTURE_BINDING_2D, initial_bound_textures[i]); - } - - for (int i = 0; i < nvertex_attribs; i++) { - int initial = initial_vertex_attrib_array_enabled[i]; - GLint val; - glGetVertexAttribiv(i, GL_VERTEX_ATTRIB_ARRAY_ENABLED, &val); - if (val != initial) { - fprintf(stderr, "%s did not restore boolean VertexAttributeArrayEnabled(%d): initial value was %s, current value is %s\n", operation, i, initial ? "true" : "false", val ? "true" : "false"); - errors = true; - } - } - } -}; - -using namespace Halide; - -int main() { - // This test must be run with an OpenGL target. - const Target target = get_jit_target_from_environment().with_feature(Target::OpenGL); - - KnownState known_state; - - Buffer input(255, 10, 3); - Buffer out(UInt(8), 255, 10, 3); - - Var x, y, c; - Func g; - g(x, y, c) = input(x, y, c); - g.bound(c, 0, 3); - g.glsl(x, y, c); - g.realize(out, target); // let Halide initialize OpenGL - - known_state.setup(true); - g.realize(out, target); - known_state.check("realize"); - - known_state.setup(true); - out.copy_to_host(); - known_state.check("copy_to_host"); - - known_state.setup(false); - g.realize(out, target); - known_state.check("realize"); - - known_state.setup(false); - out.copy_to_host(); - known_state.check("copy_to_host"); - - if (known_state.errors) { - return 1; - } - - printf("Success!\n"); - return 0; -} - -#endif diff --git a/test/opengl/select.cpp b/test/opengl/select.cpp deleted file mode 100644 index f4c358b43e70..000000000000 --- a/test/opengl/select.cpp +++ /dev/null @@ -1,214 +0,0 @@ -#include "Halide.h" -#include - -#include "testing.h" - -using namespace Halide; - -int test_per_channel_select() { - - printf("Testing select of channel.\n"); - - // This test must be run with an OpenGL target. - const Target target = get_jit_target_from_environment().with_feature(Target::OpenGL); - - Func gpu("gpu"), cpu("cpu"); - Var x("x"), y("y"), c("c"); - - gpu(x, y, c) = cast(mux(c, {128, x, y, x * y})); - gpu.bound(c, 0, 4); - gpu.glsl(x, y, c); - gpu.compute_root(); - - cpu(x, y, c) = gpu(x, y, c); - - Buffer out(10, 10, 4); - cpu.realize(out, target); - - // Verify the result - if (!Testing::check_result(out, [&](int x, int y, int c) { - switch (c) { - case 0: return 128; - case 1: return x; - case 2: return y; - default: return x*y; - } })) { - return 1; - } - - return 0; -} - -int test_flag_scalar_select() { - - printf("Testing select of scalar value with flag.\n"); - - // This test must be run with an OpenGL target. - const Target target = get_jit_target_from_environment().with_feature(Target::OpenGL); - - Func gpu("gpu"), cpu("cpu"); - Var x("x"), y("y"), c("c"); - - int flag_value = 0; - - Param flag("flag"); - flag.set(flag_value); - - gpu(x, y, c) = cast(select(flag != 0, 128, - 255)); - gpu.bound(c, 0, 4); - gpu.glsl(x, y, c); - gpu.compute_root(); - - // This should trigger a copy_to_host operation - cpu(x, y, c) = gpu(x, y, c); - - Buffer out(10, 10, 4); - cpu.realize(out, target); - - // Verify the result - if (!Testing::check_result(out, [&](int x, int y, int c) { - return !flag_value ? 255 : 128; - })) { - return 1; - } - - return 0; -} - -int test_flag_pixel_select() { - - printf("Testing select of pixel value with flag.\n"); - - // This test must be run with an OpenGL target. - const Target target = get_jit_target_from_environment().with_feature(Target::OpenGL); - - Func gpu("gpu"), cpu("cpu"); - Var x("x"), y("y"), c("c"); - - int flag_value = 0; - - Param flag("flag"); - flag.set(flag_value); - - Buffer image(10, 10, 4); - for (int y = 0; y < image.height(); y++) { - for (int x = 0; x < image.width(); x++) { - for (int c = 0; c < image.channels(); c++) { - image(x, y, c) = 128; - } - } - } - - gpu(x, y, c) = cast(select(flag != 0, image(x, y, c), - 255)); - gpu.bound(c, 0, 4); - gpu.glsl(x, y, c); - gpu.compute_root(); - - // This should trigger a copy_to_host operation - cpu(x, y, c) = gpu(x, y, c); - - Buffer out(10, 10, 4); - cpu.realize(out, target); - - // Verify the result - if (!Testing::check_result(out, [&](int x, int y, int c) { - return !flag_value ? 255 : 128; - })) { - return 1; - } - - return 0; -} - -int test_nested_select() { - - printf("Testing nested select.\n"); - - // This test must be run with an OpenGL target. - const Target target = get_jit_target_from_environment().with_feature(Target::OpenGL); - - // Define the algorithm. - Var x("x"), y("y"), c("c"); - Func f("f"); - Expr temp = cast(select(x == 0, 1, 2)); - f(x, y, c) = select(y == 0, temp, 255 - temp); - - // Schedule f to run on the GPU. - const int channels = 3; - f.bound(c, 0, channels).glsl(x, y, c); - - // Generate the result. - const int width = 10, height = 10; - Buffer out = f.realize(width, height, channels, target); - - // Check the result. - int errors = 0; - out.for_each_element([&](int x, int y, int c) { - uint8_t temp = x == 0 ? 1 : 2; - uint8_t expected = y == 0 ? temp : 255 - temp; - uint8_t actual = out(x, y, c); - if (expected != actual && ++errors == 1) { - fprintf(stderr, "out(%d, %d, %d) = %d instead of %d\n", - x, y, c, actual, expected); - } - }); - - return errors; -} - -int test_nested_select_varying() { - - printf("Testing nested select with varying condition.\n"); - - // This test must be run with an OpenGL target. - const Target target = get_jit_target_from_environment().with_feature(Target::OpenGL); - - // Define the algorithm. - Var x("x"), y("y"), c("c"); - Func f("f"); - Expr temp = cast(select(x - c > 0, 1, 2)); - f(x, y, c) = select(y == 0, temp, 255 - temp); - - // Schedule f to run on the GPU. - const int channels = 3; - f.bound(c, 0, channels).glsl(x, y, c); - - // Generate the result. - const int width = 10, height = 10; - Buffer out = f.realize(width, height, channels, target); - - // Check the result. - int errors = 0; - out.for_each_element([&](int x, int y, int c) { - uint8_t temp = x - c > 0 ? 1 : 2; - uint8_t expected = y == 0 ? temp : 255 - temp; - uint8_t actual = out(x, y, c); - if (expected != actual && ++errors == 1) { - fprintf(stderr, "out(%d, %d, %d) = %d instead of %d\n", - x, y, c, actual, expected); - } - }); - - return errors; -} - -int main() { - - int err = 0; - - err |= test_per_channel_select(); - err |= test_flag_scalar_select(); - err |= test_flag_pixel_select(); - err |= test_nested_select(); - err |= test_nested_select_varying(); - - if (err) { - printf("FAILED\n"); - return 1; - } - - printf("Success!\n"); - return 0; -} diff --git a/test/opengl/set_pixels.cpp b/test/opengl/set_pixels.cpp deleted file mode 100644 index 7c282878af0b..000000000000 --- a/test/opengl/set_pixels.cpp +++ /dev/null @@ -1,28 +0,0 @@ -#include "Halide.h" -#include - -#include "testing.h" - -using namespace Halide; - -int main() { - // This test must be run with an OpenGL target. - const Target target = get_jit_target_from_environment().with_feature(Target::OpenGL); - - Func f; - Var x, y, c; - - f(x, y, c) = cast(42); - - Buffer out(10, 10, 3); - f.bound(c, 0, 3).glsl(x, y, c); - f.realize(out, target); - - out.copy_to_host(); - if (!Testing::check_result(out, [](int x, int y, int c) { return 42; })) { - return 1; - } - - printf("Success!\n"); - return 0; -} diff --git a/test/opengl/shifted_domains.cpp b/test/opengl/shifted_domains.cpp deleted file mode 100644 index 38e2e81b2771..000000000000 --- a/test/opengl/shifted_domains.cpp +++ /dev/null @@ -1,66 +0,0 @@ -#include "Halide.h" -#include - -#include "testing.h" - -using namespace Halide; - -// This test executes a simple kernel with a non-zero min value. The code is -// adapted from lesson_06_realizing_over_shifted_domains.cpp and scheduled for -// GLSL -int shifted_domains() { - - // This test must be run with an OpenGL target. - const Target target = get_jit_target_from_environment().with_feature(Target::OpenGL); - - int errors = 0; - - Func gradient("gradient"); - Var x("x"), y("y"), c("c"); - gradient(x, y, c) = cast(x + y); - - gradient.bound(c, 0, 1); - gradient.glsl(x, y, c); - - printf("Evaluating gradient from (0, 0) to (7, 7)\n"); - Buffer result(8, 8, 1); - gradient.realize(result, target); - result.copy_to_host(); - - if (!Testing::check_result(result, 5e-5f, [](int x, int y) { return float(x + y); })) - errors++; - - Buffer shifted(5, 7, 1); - shifted.set_min(100, 50); - - printf("Evaluating gradient from (100, 50) to (104, 56)\n"); - - gradient.realize(shifted, target); - shifted.copy_to_host(); - - if (!Testing::check_result(shifted, 5e-5f, [](int x, int y) { return float(x + y); })) - errors++; - - // Test with a negative min - shifted.set_min(-100, -50); - - printf("Evaluating gradient from (-100, -50) to (-96, -44)\n"); - - gradient.realize(shifted, target); - shifted.copy_to_host(); - - if (!Testing::check_result(shifted, 5e-5f, [](int x, int y) { return float(x + y); })) - errors++; - - return errors; -} - -int main() { - - if (shifted_domains() != 0) { - return 1; - } - - printf("Success!\n"); - return 0; -} diff --git a/test/opengl/special_funcs.cpp b/test/opengl/special_funcs.cpp deleted file mode 100644 index 677bf05a23c0..000000000000 --- a/test/opengl/special_funcs.cpp +++ /dev/null @@ -1,150 +0,0 @@ -#include "Halide.h" -#include -#include -#include -#include - -using namespace Halide; - -Var x, y, c; - -double square(double x) { - return x * x; -} - -template -void test_function(Expr e, Buffer &cpu_result, Buffer &gpu_result) { - Func cpu("cpu"), gpu("gpu"); - - Target cpu_target = get_host_target(); - Target gpu_target = get_host_target().with_feature(Target::OpenGL); - cpu(x, y, c) = e; - gpu(x, y, c) = e; - - cpu.realize(cpu_result, cpu_target); - - gpu.bound(c, 0, 3).glsl(x, y, c); - gpu.realize(gpu_result, gpu_target); - gpu_result.copy_to_host(); -} - -template -bool test_exact(Expr r, Expr g, Expr b) { - Expr e = cast(mux(c, {r, g, b})); - const int W = 256, H = 256; - Buffer cpu_result(W, H, 3); - Buffer gpu_result(W, H, 3); - test_function(e, cpu_result, gpu_result); - - for (int y = 0; y < gpu_result.height(); y++) { - for (int x = 0; x < gpu_result.width(); x++) { - if (!(gpu_result(x, y, 0) == cpu_result(x, y, 0) && - gpu_result(x, y, 1) == cpu_result(x, y, 1) && - gpu_result(x, y, 2) == cpu_result(x, y, 2))) { - std::cerr << "Incorrect pixel for " << e << " at (" << x << ", " << y << ")\n" - << " (" - << (int)gpu_result(x, y, 0) << ", " - << (int)gpu_result(x, y, 1) << ", " - << (int)gpu_result(x, y, 2) << ") != (" - << (int)cpu_result(x, y, 0) << ", " - << (int)cpu_result(x, y, 1) << ", " - << (int)cpu_result(x, y, 2) - << ")\n"; - return false; - } - } - } - return true; -} - -template -bool test_approx(Expr r, Expr g, Expr b, double rms_error) { - Expr e = cast(mux(c, {r, g, b})); - const int W = 256, H = 256; - Buffer cpu_result(W, H, 3); - Buffer gpu_result(W, H, 3); - test_function(e, cpu_result, gpu_result); - - double err = 0.0; - for (int y = 0; y < gpu_result.height(); y++) { - for (int x = 0; x < gpu_result.width(); x++) { - err += square(gpu_result(x, y, 0) - cpu_result(x, y, 0)); - err += square(gpu_result(x, y, 1) - cpu_result(x, y, 1)); - err += square(gpu_result(x, y, 2) - cpu_result(x, y, 2)); - } - } - err = sqrt(err / (W * H)); - if (err > rms_error) { - std::cerr << "RMS error too large for " << e << ": " - << err << " > " << rms_error << "\n"; - return false; - } else { - return true; - } -} - -int main() { - - int errors = 0; - - if (!test_exact(0, 0, 0)) { - printf("Failed constant value test\n"); - errors++; - } - if (!test_exact(clamp(x + y, 0, 255), 0, 0)) { - printf("Failed clamp test\n"); - errors++; - } - - if (!test_exact( - max(x, y), - cast(min(cast(x), cast(y))), - clamp(x, 0, 10))) { - printf("Failed min/max test\n"); - errors++; - } - - if (!test_exact(trunc(x + 0.25f), trunc(-(x + 0.75f)), 0.0f)) { - printf("Failed trunc test\n"); - errors++; - } - - // Trigonometric functions in GLSL are fast but not very accurate, - // especially outside of 0..2pi. - // The GLSL ES 1.0 spec does not define the precision of these operations - // so a wide error bound is used in this test. - Expr r = (256 * x + y) / ceilf(65536.f / (2 * 3.1415926536f)); - if (!test_approx(sin(r), cos(r), 0.0f, 5e-2)) { - errors++; - printf("Failed trigonometric test\n"); - } - - // TODO: the test must account for differences in default rounding behavior - // between the CPU and GPU for float <-> integer conversions. In this case - // the operation is performed in float in the GLSL shader, and then - // converted back to a normalized integer value. - if (!test_approx( - (x - 127) / 3 + 127, - (x - 127) % 3 + 127, - 0, - 1)) { - printf("Failed integer operation test\n"); - errors++; - } - - if (!test_exact( - lerp(cast(x), cast(y), cast(128)), - lerp(cast(x), cast(y), 0.5f), - cast(lerp(cast(x), cast(y), 0.2f)))) { - printf("Failed lerp test\n"); - errors++; - } - - if (errors == 0) { - printf("Success!\n"); - return 0; - } else { - printf("FAILED %d tests\n", errors); - return 1; - } -} diff --git a/test/opengl/sum_reduction.cpp b/test/opengl/sum_reduction.cpp deleted file mode 100644 index 97fd40d5905c..000000000000 --- a/test/opengl/sum_reduction.cpp +++ /dev/null @@ -1,46 +0,0 @@ -#include "Halide.h" -#include - -#include "testing.h" - -using namespace Halide; - -int main() { - // This test must be run with an OpenGL target. - const Target target = get_jit_target_from_environment().with_feature(Target::OpenGL); - - // Define the input - const int width = 10, height = 10, channels = 4; - Buffer input(width, height, channels); - input.fill([](int x, int y, int c) { - return float(x + y); - }); - - // Define the algorithm. - Var x, y, c; - RDom r(0, 5, "r"); - Func g; - Expr coordx = clamp(x + r, 0, input.width() - 1); - g(x, y, c) = cast(sum(input(coordx, y, c)) / sum(r) * 255.0f); - - // Schedule f and g to compute in separate passes on the GPU. - g.bound(c, 0, 4).glsl(x, y, c); - - // Generate the result. - Buffer result = g.realize(width, height, channels, target); - result.copy_to_host(); - - // Check the result. - if (!Testing::check_result(result, 1e-3f, [&](int x, int y, int c) { - float temp = 0.0f; - for (int r = 0; r < 5; r++) { - temp += input(std::min(x + r, input.width() - 1), y, c); - } - return temp / 10.0f * 255.0f; - })) { - return 1; - } - - printf("Success!\n"); - return 0; -} diff --git a/test/opengl/sumcolor_reduction.cpp b/test/opengl/sumcolor_reduction.cpp deleted file mode 100644 index 6532376061be..000000000000 --- a/test/opengl/sumcolor_reduction.cpp +++ /dev/null @@ -1,40 +0,0 @@ -#include "Halide.h" -#include - -#include "testing.h" - -using namespace Halide; - -int main() { - // This test must be run with an OpenGL target. - const Target target = get_jit_target_from_environment().with_feature(Target::OpenGL); - - // Define the input. - const int width = 10, height = 10, channels = 3; - Buffer input(width, height, channels); - input.fill([](int x, int y, int c) { - return x + y; - }); - - // Define the algorithm. - Var x, y, c; - RDom r(0, 3, "r"); - Func g; - - g(x, y, c) = sum(input(x, y, r)); - - // Schedule f and g to compute in separate passes on the GPU. - g.bound(c, 0, 3).glsl(x, y, c); - - // Generate the result. - Buffer result = g.realize(10, 10, 3, target); - result.copy_to_host(); - - // Check the result. - if (!Testing::check_result(result, 1e-6f, [](int x, int y, int c) { return 3.0f * (x + y); })) { - return 1; - } - - printf("Success!\n"); - return 0; -} diff --git a/test/opengl/testing.h b/test/opengl/testing.h deleted file mode 100644 index 860ea55c172c..000000000000 --- a/test/opengl/testing.h +++ /dev/null @@ -1,86 +0,0 @@ -#ifndef _TESTING_H_ -#define _TESTING_H_ - -#include "Halide.h" -#include -#include -#include -#include - -namespace Testing { - -template -bool neq(T a, T b, T tol) { - return std::abs(a - b) > tol; -} - -// Check 3-dimension buffer -template -auto check_result(const Halide::Buffer &buf, T tol, F f) -> decltype(std::declval()(0, 0, 0), bool()) { - class err : std::exception { - public: - static void vector(const std::vector &v) { - for (size_t i = 0; i < v.size(); i++) { - if (i > 0) { - std::cerr << ","; - } - std::cerr << +v[i]; // use unary + to promote uint8_t from char to numeric - } - } - }; - try { - buf.for_each_element([&](int x, int y) { - std::vector expected; - std::vector result; - for (int c = 0; c < buf.channels(); c++) { - expected.push_back(f(x, y, c)); - result.push_back(buf(x, y, c)); - } - for (int c = 0; c < buf.channels(); c++) { - if (neq(result[c], expected[c], tol)) { - std::cerr << "Error: result ("; - err::vector(result); - std::cerr << ") should be ("; - err::vector(expected); - std::cerr << ") at x=" << x << " y=" << y << "\n"; - throw err(); - } - } - }); - } catch (err &) { - return false; - } - return true; -} - -// Check 2-dimension buffer -template -auto check_result(const Halide::Buffer &buf, T tol, F f) -> decltype(std::declval()(0, 0), bool()) { - class err : std::exception {}; - try { - buf.for_each_element([&](int x, int y) { - const T expected = f(x, y); - const T result = buf(x, y); - if (neq(result, expected, tol)) { - std::cerr << "Error: result ("; - std::cerr << +result; - std::cerr << ") should be ("; - std::cerr << +expected; - std::cerr << ") at x=" << x << " y=" << y << "\n"; - throw err(); - } - }); - } catch (err &) { - return false; - } - return true; -} - -// Shorthand to check with tolerance=0 -template -bool check_result(const Halide::Buffer &buf, Func f) { - return check_result(buf, 0, f); -} -} // namespace Testing - -#endif // _TESTING_H_ diff --git a/test/opengl/tuples.cpp b/test/opengl/tuples.cpp deleted file mode 100644 index b4a834ffd1ca..000000000000 --- a/test/opengl/tuples.cpp +++ /dev/null @@ -1,40 +0,0 @@ -#include "Halide.h" -#include - -#include "testing.h" - -using namespace Halide; - -int main() { - // This test must be run with an OpenGL target. - const Target target = get_jit_target_from_environment().with_feature(Target::OpenGL); - - Buffer input(255, 10, 3); - input.fill([](int x, int y, int c) { - return 10 * x + y + c; - }); - - Var x, y, c; - Func g; - g(x, y, c) = {input(x, y, c), input(x, y, c) / 2}; - - // h will be an opengl stage with tuple input. Tuple outputs - // aren't supported because OpenGL ES 2.0 doesn't support multiple - // output textures. - Func h; - h(x, y, c) = min(g(x, y, c)[0], g(x, y, c)[1]); - - Buffer out(255, 10, 3); - g.compute_root(); - h.compute_root().bound(c, 0, 3).glsl(x, y, c); - - h.realize(out, target); - out.copy_to_host(); - - if (!Testing::check_result(out, [&](int x, int y, int c) { return input(x, y, c) / 2; })) { - return 1; - } - - printf("Success!\n"); - return 0; -} diff --git a/test/opengl/vagrant/.gitignore b/test/opengl/vagrant/.gitignore deleted file mode 100644 index 8000dd9db47c..000000000000 --- a/test/opengl/vagrant/.gitignore +++ /dev/null @@ -1 +0,0 @@ -.vagrant diff --git a/test/opengl/vagrant/README.md b/test/opengl/vagrant/README.md deleted file mode 100644 index febf3be7d1ad..000000000000 --- a/test/opengl/vagrant/README.md +++ /dev/null @@ -1,136 +0,0 @@ -# Testing OpenGL on Ubuntu 14 & 16 using vagrant & VirtualBox - -## Overview - -This subdirectory (`Halide/test/opengl/vagrant`) provides the setup to build -Halide and run the OpenGL tests headlessly on Ubuntu 14.04 and/or 16.04, running -virtually under [vagrant](http://vagrantup.com) and -[VirtualBox](https://www.virtualbox.org). - -This is intended in particular for use by those who develop Halide's OpenGL -back-end on OS X and need to test on Linux. - -The `Vagrantfile` provisions with the necessary capabilities to build Halide and -build & run Halide's OpenGL test suite. In particular it installs llvm-3.8 and -OpenGL with software rendering to a dummy X server. - -## Quick instructions - -Presuming that you have [vagrant](http://vagrantup.com) and -[VirtualBox](https://www.virtualbox.org) installed, - -``` -$ cd Halide/test/opengl/vagrant -$ vagrant up [u14|u16] -[...] -$ vagrant ssh [u14|u16] -c "sh /vagrant/build_tests.sh" -[...] -``` - -The `[u14|u16]` argument is optional, the default is `u16` to use the Ubuntu -16.04 virtual machine. Specify `u14` to use the Ubuntu 14.04 macihne. - -After a bit of time and a lot of verbiage, you should eventually see the `make` -output for building and running the OpenGL tests - -## Detailed instructions - -### Starting and provisioning the virtual machine(s) - -As per above, you can start the machines using - -``` -$ cd Halide/test/opengl/vagrant -$ vagrant up [u14|u16] -[...] -``` - -The first time you run it for a given machine, it will download the necessary -base box, then boot and provision the machine. This will take several minutes. - -You may notice some errors or warnings in the output of `vagrant up`'s -provisioning; these can be safely ignored. (In particular for `u16` the output -ends with `ttyname failed: Inappropriate ioctl for device` which looks omnious -but is harmless.) - -As usual, you can stop or power down the machine using -`vagrant suspend [u14|u16]` or `vagrant halt [u14|u16]`; subsequently starting -it up again using `vagrant up [u14|u16]` should be reasonably quick. For more -info, see the `vagrant help` or the [vagrant](http://vagrantup.com) docs. - -See the `Vagrantfile` for the specific details of what gets provisioned. - -### Building Halide and running the tests - -The virtual machine has these directories live-shared with the host: - -- `/Halide` - The root of your Halide source tree -- `/vagrant` - The vagrant work directory. I.e. effectively a hard link to - `/Halide/test/opengl/vagrant` - -Because these are live shared, you can edit Halide source files on your host -machine but build them on the virtual machine. - -The script `build_tests.sh`, run on the virtual machine, is just a quick -shorthand to minimize the amount of typing, letting you build and run everything -at once from the host via - -``` -$ vagrant ssh [u14|u16] -c "sh /vagrant/build_tests.sh" -``` - -But of course for more focused development & debugging you might want to do -things one step at a time: - -``` -$ vagrant ssh [u14|u16] -[...Ubuntu motd...] -vagrant@vagrant:~$ -``` - -These are the steps taken by `build_tests.sh`: - -#### 1. Create an out-of-tree build directory - -``` -vagrant@vagrant:~$ mkdir ~/halide_build -vagrant@vagrant:~$ cd ~/halide_build -vagrant@vagrant:~/halide_build$ ln -s /Halide/Makefile . -``` - -It's important to build out-of-tree, because `/Halide` tree is live shared and -we don't want the virtual machine's object files to clobber the host object -files! - -#### 2. Build Halide - -Nothing special here, just build normally, e.g.: - -``` -vagrant@vagrant:~/halide_build$ make -j 3 -``` - -The machine is provisioned with environment variables `LLVM_CONFIG` globally set -appropriately. - -#### 3. Build & run the OpenGL tests - -Again nothing special here, just build the opengl tests normally, e.g.: - -``` -vagrant@vagrant:~/halide_build$ make -k test_opengl -``` - -Or of course you can build and run just one test, e.g.: - -``` -vagrant@vagrant:~/halide_build$ make opengl_float_texture -``` - -The machine is provisioned with environment variables `HL_TARGET` and -`HL_JIT_TARGET` set to `host-opengl`. You can of course override in your shell, -e.g. if you want to use `host-opengl-debug`. - -The machine is provisioned with `lldb` installed in case you need to do some -debugging. Aside from that it's bare-bones; if you need anything else for your -debugging or development you will need to `apt-get install` it. diff --git a/test/opengl/vagrant/Vagrantfile b/test/opengl/vagrant/Vagrantfile deleted file mode 100644 index 5d7fef1a0afe..000000000000 --- a/test/opengl/vagrant/Vagrantfile +++ /dev/null @@ -1,118 +0,0 @@ -# -*- mode: ruby -*- -# vi: set ft=ruby : - -# All Vagrant configuration is done below. The "2" in Vagrant.configure -# configures the configuration version (we support older styles for -# backwards compatibility). Please don't change it unless you know what -# you're doing. -Vagrant.configure("2") do |config| - # The most common configuration options are documented and commented below. - # For a complete reference, please see the online documentation at - # https://docs.vagrantup.com. - - # Every Vagrant development environment requires a box. You can search for - # boxes at https://atlas.hashicorp.com/search. - config.vm.define "u14", autostart: false do |u14| - u14.vm.box = "bento/ubuntu-14.04" - u14.vm.provision "shell", inline: <<-SHELL - # Create and start headless X service using upstart - cp /vagrant/provision/etc/init/xdummy.conf /etc/init/ - service xdummy start - SHELL - end - config.vm.define "u16", primary: true do |u16| - u16.vm.box = "bento/ubuntu-16.04" - u16.vm.provision "shell", inline: <<-SHELL - # Create and start headless X service using systemd - cp /vagrant/provision/etc/systemd/system/xdummy.service /etc/systemd/system/ - systemctl start xdummy - SHELL - end - - config.vm.boot_timeout = 600 - - - # Disable automatic box update checking. If you disable this, then - # boxes will only be checked for updates when the user runs - # `vagrant box outdated`. This is not recommended. - # config.vm.box_check_update = false - - # Create a forwarded port mapping which allows access to a specific port - # within the machine from a port on the host machine. In the example below, - # accessing "localhost:8080" will access port 80 on the guest machine. - # config.vm.network "forwarded_port", guest: 80, host: 8080 - - # Create a private network, which allows host-only access to the machine - # using a specific IP. - # config.vm.network "private_network", ip: "192.168.33.10" - - # Create a public network, which generally matched to bridged network. - # Bridged networks make the machine appear as another physical device on - # your network. - # config.vm.network "public_network" - - # Share an additional folder to the guest VM. The first argument is - # the path on the host to the actual folder. The second argument is - # the path on the guest to mount the folder. And the optional third - # argument is a set of non-required options. - # config.vm.synced_folder "../data", "/vagrant_data" - config.vm.synced_folder "../../..", "/Halide" - - # Provider-specific configuration so you can fine-tune various - # backing providers for Vagrant. These expose provider-specific options. - # Example for VirtualBox: - # - config.vm.provider "virtualbox" do |vb| - # Display the VirtualBox GUI when booting the machine - vb.gui = false - - # Customize the amount of memory on the VM: - vb.memory = "2048" - end - # - # View the documentation for the provider you are using for more - # information on available options. - - # Define a Vagrant Push strategy for pushing to Atlas. Other push strategies - # such as FTP and Heroku are also available. See the documentation at - # https://docs.vagrantup.com/v2/push/atlas.html for more information. - # config.push.define "atlas" do |push| - # push.app = "YOUR_ATLAS_USERNAME/YOUR_APPLICATION_NAME" - # end - - # Enable provisioning with a shell script. Additional provisioners such as - # Puppet, Chef, Ansible, Salt, and Docker are also available. Please see the - # documentation for more information about their specific syntax and use. - config.vm.provision "shell", inline: <<-SHELL - - # Global environment variables, both for system purposes (PATH, LC_ALL) - # and for convenience of building and running Halide opengl tests - # (DISPLAY, LLVM_CONFIG, CLANG, HL_JIT_TARGET) - cp /vagrant/provision/etc/environment /etc/environment - - apt-get update - - # Install resources for headless X service (final provisioning of the service is machine-specific) - apt-get install -y xserver-xorg-video-dummy - cp /vagrant/provision/usr/share/X11/xorg.conf.d/xdummy.conf /usr/share/X11/xorg.conf.d/xdummy.conf - - # Install llvm-3.8 as llvm - apt-get install -y llvm-3.8 llvm-3.8-dev clang-3.8 lldb-3.8 - for ll in /usr/bin/*-3.8 ; do ln -s -f $ll `echo $ll | sed -e s/-3.8//` ; done - - # Build OpenGL (mesa) using software driver (gallium / llvmpipe). Can't - # use the prebuilt mesa packages because they expect video hardware drivers. - apt-get install -y build-essential scons python-mako flex bison zlib1g-dev libudev-dev pkg-config libx11-dev libxext-dev libxdamage-dev x11proto-gl-dev libx11-xcb-dev - cd /usr/local/src - test -f mesa-12.0.2.tar.xz || wget -q https://mesa.freedesktop.org/archive/12.0.2/mesa-12.0.2.tar.xz - test -d mesa-12.0.2 || tar xkf mesa-12.0.2.tar.xz - cd mesa-12.0.2 - scons build=release texture_float=yes libgl-xlib - ln -s -f `pwd`/include/GL* /usr/local/include/ - cp `pwd`/build/linux-x86_64/gallium/targets/libgl-xlib/libGL.* /usr/local/lib - ldconfig - - # Machine-specific provisioning will happpen next - SHELL - -end diff --git a/test/opengl/vagrant/build_tests.sh b/test/opengl/vagrant/build_tests.sh deleted file mode 100755 index 54dec279c28a..000000000000 --- a/test/opengl/vagrant/build_tests.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/sh -x -mkdir -p ~/halide_build -cd ~/halide_build -ln -s -f /Halide/Makefile . -make -j 3 -make -k test_opengl diff --git a/test/opengl/vagrant/provision/etc/environment b/test/opengl/vagrant/provision/etc/environment deleted file mode 100644 index 2ab25818fbad..000000000000 --- a/test/opengl/vagrant/provision/etc/environment +++ /dev/null @@ -1,7 +0,0 @@ -PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games" -LC_ALL=C -DISPLAY=:0.0 -LLVM_CONFIG=/usr/bin/llvm-config-3.8 -CLANG=/usr/bin/clang-3.8 -HL_TARGET=host-opengl -HL_JIT_TARGET=host-opengl diff --git a/test/opengl/vagrant/provision/etc/init/xdummy.conf b/test/opengl/vagrant/provision/etc/init/xdummy.conf deleted file mode 100644 index da5925809f41..000000000000 --- a/test/opengl/vagrant/provision/etc/init/xdummy.conf +++ /dev/null @@ -1,7 +0,0 @@ -description "Dummy X server providing DISPLAY=:0.0" - -expect fork - -script - /usr/bin/Xorg -noreset +extension GLX +extension RANDR +extension RENDER -logfile /var/log/Xorg.log :0 & -end script diff --git a/test/opengl/vagrant/provision/etc/systemd/system/xdummy.service b/test/opengl/vagrant/provision/etc/systemd/system/xdummy.service deleted file mode 100644 index 8d0ce1a3c4d9..000000000000 --- a/test/opengl/vagrant/provision/etc/systemd/system/xdummy.service +++ /dev/null @@ -1,6 +0,0 @@ -[Unit] -Description=Dummy X server providing DISPLAY=:0.0" - -[Service] -Type=simple -ExecStart=/usr/bin/Xorg -noreset +extension GLX +extension RANDR +extension RENDER -config /dev/null -logfile /var/log/Xorg.log :0 diff --git a/test/opengl/vagrant/provision/usr/share/X11/xorg.conf.d/xdummy.conf b/test/opengl/vagrant/provision/usr/share/X11/xorg.conf.d/xdummy.conf deleted file mode 100644 index d31d944c32f3..000000000000 --- a/test/opengl/vagrant/provision/usr/share/X11/xorg.conf.d/xdummy.conf +++ /dev/null @@ -1,137 +0,0 @@ -# This xorg configuration file is meant to be used by xpra -# to start a dummy X11 server. -# For details, please see: -# https://xpra.org/Xdummy.html - -Section "ServerFlags" - Option "DontVTSwitch" "true" - Option "AllowMouseOpenFail" "true" - Option "PciForceNone" "true" - Option "AutoEnableDevices" "false" - Option "AutoAddDevices" "false" -EndSection - -Section "InputDevice" - Identifier "dummy_mouse" - Option "CorePointer" "true" - Driver "void" -EndSection - -Section "InputDevice" - Identifier "dummy_keyboard" - Option "CoreKeyboard" "true" - Driver "void" -EndSection - -Section "Device" - Identifier "dummy_videocard" - Driver "dummy" - Option "ConstantDPI" "true" - #VideoRam 4096000 - #VideoRam 256000 - VideoRam 192000 -EndSection - -Section "Monitor" - Identifier "dummy_monitor" - HorizSync 5.0 - 1000.0 - VertRefresh 5.0 - 200.0 - #This can be used to get a specific DPI, but only for the default resolution: - #DisplaySize 508 317 - #NOTE: the highest modes will not work without increasing the VideoRam - # for the dummy video card. - Modeline "32768x32768" 15226.50 32768 35800 39488 46208 32768 32771 32781 32953 - Modeline "32768x16384" 7516.25 32768 35544 39192 45616 16384 16387 16397 16478 - Modeline "16384x8192" 2101.93 16384 16416 24400 24432 8192 8390 8403 8602 - Modeline "8192x4096" 424.46 8192 8224 9832 9864 4096 4195 4202 4301 - Modeline "5496x1200" 199.13 5496 5528 6280 6312 1200 1228 1233 1261 - Modeline "5280x1080" 169.96 5280 5312 5952 5984 1080 1105 1110 1135 - Modeline "5280x1200" 191.40 5280 5312 6032 6064 1200 1228 1233 1261 - Modeline "5120x3200" 199.75 5120 5152 5904 5936 3200 3277 3283 3361 - Modeline "4800x1200" 64.42 4800 4832 5072 5104 1200 1229 1231 1261 - Modeline "3840x2880" 133.43 3840 3872 4376 4408 2880 2950 2955 3025 - Modeline "3840x2560" 116.93 3840 3872 4312 4344 2560 2622 2627 2689 - Modeline "3840x2048" 91.45 3840 3872 4216 4248 2048 2097 2101 2151 - Modeline "3840x1080" 100.38 3840 3848 4216 4592 1080 1081 1084 1093 - Modeline "3600x1200" 106.06 3600 3632 3984 4368 1200 1201 1204 1214 - Modeline "3288x1080" 39.76 3288 3320 3464 3496 1080 1106 1108 1135 - Modeline "2048x2048" 49.47 2048 2080 2264 2296 2048 2097 2101 2151 - Modeline "2048x1536" 80.06 2048 2104 2312 2576 1536 1537 1540 1554 - Modeline "2560x1600" 47.12 2560 2592 2768 2800 1600 1639 1642 1681 - Modeline "2560x1440" 42.12 2560 2592 2752 2784 1440 1475 1478 1513 - Modeline "1920x1440" 69.47 1920 1960 2152 2384 1440 1441 1444 1457 - Modeline "1920x1200" 26.28 1920 1952 2048 2080 1200 1229 1231 1261 - Modeline "1920x1080" 23.53 1920 1952 2040 2072 1080 1106 1108 1135 - Modeline "1680x1050" 20.08 1680 1712 1784 1816 1050 1075 1077 1103 - Modeline "1600x1200" 22.04 1600 1632 1712 1744 1200 1229 1231 1261 - Modeline "1600x900" 33.92 1600 1632 1760 1792 900 921 924 946 - Modeline "1440x900" 30.66 1440 1472 1584 1616 900 921 924 946 - ModeLine "1366x768" 72.00 1366 1414 1446 1494 768 771 777 803 - Modeline "1280x1024" 31.50 1280 1312 1424 1456 1024 1048 1052 1076 - Modeline "1280x800" 24.15 1280 1312 1400 1432 800 819 822 841 - Modeline "1280x768" 23.11 1280 1312 1392 1424 768 786 789 807 - Modeline "1360x768" 24.49 1360 1392 1480 1512 768 786 789 807 - Modeline "1024x768" 18.71 1024 1056 1120 1152 768 786 789 807 - Modeline "768x1024" 19.50 768 800 872 904 1024 1048 1052 1076 - - - #common resolutions for android devices (both orientations): - Modeline "800x1280" 25.89 800 832 928 960 1280 1310 1315 1345 - Modeline "1280x800" 24.15 1280 1312 1400 1432 800 819 822 841 - Modeline "720x1280" 30.22 720 752 864 896 1280 1309 1315 1345 - Modeline "1280x720" 27.41 1280 1312 1416 1448 720 737 740 757 - Modeline "768x1024" 24.93 768 800 888 920 1024 1047 1052 1076 - Modeline "1024x768" 23.77 1024 1056 1144 1176 768 785 789 807 - Modeline "600x1024" 19.90 600 632 704 736 1024 1047 1052 1076 - Modeline "1024x600" 18.26 1024 1056 1120 1152 600 614 617 631 - Modeline "536x960" 16.74 536 568 624 656 960 982 986 1009 - Modeline "960x536" 15.23 960 992 1048 1080 536 548 551 563 - Modeline "600x800" 15.17 600 632 688 720 800 818 822 841 - Modeline "800x600" 14.50 800 832 880 912 600 614 617 631 - Modeline "480x854" 13.34 480 512 560 592 854 873 877 897 - Modeline "848x480" 12.09 848 880 920 952 480 491 493 505 - Modeline "480x800" 12.43 480 512 552 584 800 818 822 841 - Modeline "800x480" 11.46 800 832 872 904 480 491 493 505 - #resolutions for android devices (both orientations) - #minus the status bar - #38px status bar (and width rounded up) - Modeline "800x1242" 25.03 800 832 920 952 1242 1271 1275 1305 - Modeline "1280x762" 22.93 1280 1312 1392 1424 762 780 783 801 - Modeline "720x1242" 29.20 720 752 856 888 1242 1271 1276 1305 - Modeline "1280x682" 25.85 1280 1312 1408 1440 682 698 701 717 - Modeline "768x986" 23.90 768 800 888 920 986 1009 1013 1036 - Modeline "1024x730" 22.50 1024 1056 1136 1168 730 747 750 767 - Modeline "600x986" 19.07 600 632 704 736 986 1009 1013 1036 - Modeline "1024x562" 17.03 1024 1056 1120 1152 562 575 578 591 - Modeline "536x922" 16.01 536 568 624 656 922 943 947 969 - Modeline "960x498" 14.09 960 992 1040 1072 498 509 511 523 - Modeline "600x762" 14.39 600 632 680 712 762 779 783 801 - Modeline "800x562" 13.52 800 832 880 912 562 575 578 591 - Modeline "480x810" 12.59 480 512 552 584 810 828 832 851 - Modeline "848x442" 11.09 848 880 920 952 442 452 454 465 - Modeline "480x762" 11.79 480 512 552 584 762 779 783 801 -EndSection - -Section "Screen" - Identifier "dummy_screen" - Device "dummy_videocard" - Monitor "dummy_monitor" - DefaultDepth 24 - SubSection "Display" - Viewport 0 0 - Depth 24 - #Modes "32768x32768" "32768x16384" "16384x8192" "8192x4096" "5120x3200" "3840x2880" "3840x2560" "3840x2048" "2048x2048" "2560x1600" "1920x1440" "1920x1200" "1920x1080" "1600x1200" "1680x1050" "1600x900" "1400x1050" "1440x900" "1280x1024" "1366x768" "1280x800" "1024x768" "1024x600" "800x600" "320x200" - Modes "5120x3200" "3840x2880" "3840x2560" "3840x2048" "2048x2048" "2560x1600" "1920x1440" "1920x1200" "1920x1080" "1600x1200" "1680x1050" "1600x900" "1400x1050" "1440x900" "1280x1024" "1366x768" "1280x800" "1024x768" "1024x600" "800x600" "320x200" - #Virtual 32000 32000 - #Virtual 16384 8192 - Virtual 8192 4096 - #Virtual 5120 3200 - EndSubSection -EndSection - -Section "ServerLayout" - Identifier "dummy_layout" - Screen "dummy_screen" - InputDevice "dummy_mouse" - InputDevice "dummy_keyboard" -EndSection diff --git a/test/opengl/varying.cpp b/test/opengl/varying.cpp deleted file mode 100644 index 314136215c94..000000000000 --- a/test/opengl/varying.cpp +++ /dev/null @@ -1,218 +0,0 @@ -#include "Halide.h" -#include - -#include "testing.h" - -using namespace Halide; -using namespace Halide::Internal; - -// This test exercises several use cases for the GLSL varying attributes -// feature. This feature detects expressions that are linear in terms of the -// loop variables of a .glsl(..) scheduled Func and uses graphics pipeline -// interpolation to evaluate the expressions instead of evaluating them per -// fragment in the Halide generated fragment shader. Common examples are texture -// coordinates interpolated across a Func domain or texture coordinates -// transformed by a matrix and interpolated across the domain. Both cases arise -// when GLSL shaders are ported to Halide. - -// This is a mutator that injects code that counts the number of variables -// tagged .varying -#ifdef _MSC_VER -#define DLLEXPORT __declspec(dllexport) -#else -#define DLLEXPORT -#endif - -// This global variable is used to count the number of unique varying attribute -// variables that appear in the lowered Halide IR. -std::set varyings; - -// This function is a HalideExtern used to add variables to the set. The tests -// below check the total number of unique variables found--not the specific -// names of the variables which are arbitrary. -extern "C" DLLEXPORT const Variable *record_varying(const Variable *op) { - if (varyings.find(op->name) == varyings.end()) { - fprintf(stderr, "Found varying attribute: %s\n", op->name.c_str()); - varyings.insert(op->name); - } - return op; -} -HalideExtern_1(const Variable *, record_varying, const Variable *); - -// This visitor inserts the above function in the IR tree. -class CountVarying : public IRMutator { - using IRMutator::visit; - - Expr visit(const Variable *op) override { - Expr expr = IRMutator::visit(op); - if (ends_with(op->name, ".varying")) { - expr = record_varying(op); - } - return expr; - } -}; - -bool perform_test(const char *label, const Target target, Func f, int expected_nvarying, float tol, std::function expected_val) { - fprintf(stderr, "%s\n", label); - - Buffer out(8, 8, 3); - - varyings.clear(); - f.add_custom_lowering_pass(new CountVarying); - f.realize(out, target); - - // Check for the correct number of varying attributes - if ((int)varyings.size() != expected_nvarying) { - fprintf(stderr, - "%s: Error: wrong number of varying attributes: %d should be %d\n", - label, (int)varyings.size(), expected_nvarying); - return false; - } - - // Check for correct result values - out.copy_to_host(); - - if (!Testing::check_result(out, tol, expected_val)) { - return false; - } - - fprintf(stderr, "%s Passed!\n", label); - return true; -} - -// This is a simple test case where there are two expressions that are not -// linearly varying in terms of a loop variable and one expression that is. -bool test0(const Target target, Var &x, Var &y, Var &c) { - float p_value = 8.0f; - Param p("p"); - p.set(p_value); - - Func f0("f0"); - f0(x, y, c) = mux(c, {4.0f, // Constant term - p * 10.0f, // Linear expression not in terms of a loop parameter - cast(x) * 100.0f}); // Linear expression in terms of x - - f0.bound(c, 0, 3); - f0.glsl(x, y, c); - return perform_test("Test0", target, f0, 2, 0.0f, [&](int x, int y, int c) { - switch (c) { - case 0: return 4.0f; - case 1: return p_value * 10.0f; - default: return static_cast(x) * 100.0f; - } }); -} - -struct CoordXform { - const float th = 3.141592f / 8.0f; - const float s_th = sinf(th); - const float c_th = cosf(th); - const float m[6] = { - c_th, -s_th, 0.0f, - s_th, c_th, 0.0f}; - Param m0, m1, m2, m3, m4, m5; - CoordXform() - : m0("m0"), m1("m1"), m2("m2"), m3("m3"), m4("m4"), m5("m5") { - m0.set(m[0]); - m1.set(m[1]); - m2.set(m[2]); - m3.set(m[3]); - m4.set(m[4]); - m5.set(m[5]); - } -}; - -// This is a more complicated test case where several expressions are linear -// in all of the loop variables. This is the coordinate transformation case -bool test1(const Target target, Var &x, Var &y, Var &c) { - struct CoordXform m; - Func f1("f1"); - f1(x, y, c) = mux(c, {m.m0 * x + m.m1 * y + m.m2, - m.m3 * x + m.m4 * y + m.m5, - 1.0f}); - - f1.bound(c, 0, 3); - f1.glsl(x, y, c); - - return perform_test("Test1", target, f1, 4, 0.000001f, [&](int x, int y, int c) { - switch (c) { - case 0: return m.m[0] * x + m.m[1] * y + m.m[2]; - case 1: return m.m[3] * x + m.m[4] * y + m.m[5]; - default: return 1.0f; - } }); -} - -// The feature is supposed to find linearly varying sub-expressions as well -// so for example, if the above expressions are wrapped in a non-linear -// function like sqrt, they should still be extracted. -bool test2(const Target target, Var &x, Var &y, Var &c) { - struct CoordXform m; - Func f2("f2"); - f2(x, y, c) = mux(c, {sqrt(m.m0 * x + m.m1 * y + m.m2), - sqrt(m.m3 * x + m.m4 * y + m.m5), - 1.0f}); - f2.bound(c, 0, 3); - f2.glsl(x, y, c); - - return perform_test("Test2", target, f2, 4, 0.000001f, [&](int x, int y, int c) { - switch (c) { - case 0: return sqrtf(m.m[0] * x + m.m[1] * y + m.m[2]); - case 1: return sqrtf(m.m[3] * x + m.m[4] * y + m.m[5]); - default: return 1.0f; - } }); -} - -// This case tests a large expression linearly varying in terms of a loop -// variable -bool test3(const Target target, Var &x, Var &y, Var &c) { - float p_value = 8.0f; - Param p("p"); - p.set(p_value); - Expr foo = p; - for (int i = 0; i < 10; i++) { - foo = foo + foo + foo; - } - foo = x + foo; - - float foo_value = p_value; - for (int i = 0; i < 10; i++) { - foo_value = foo_value + foo_value + foo_value; - } - - Func f3("f3"); - f3(x, y, c) = mux(c, {foo, 1.0f, 2.0f}); - - f3.bound(c, 0, 3); - f3.glsl(x, y, c); - - return perform_test("Test3", target, f3, 2, 0.000001f, [&](int x, int y, int c) { - switch (c) { - case 0: return (float)x + foo_value; - case 1: return 1.0f; - default: return 2.0f; - } }); -} - -int main() { - // This test must be run with an OpenGL target. - const Target target = get_jit_target_from_environment().with_feature(Target::OpenGL); - - Var x("x"); - Var y("y"); - Var c("c"); - - bool pass = true; - pass &= test0(target, x, y, c); - pass &= test1(target, x, y, c); - pass &= test2(target, x, y, c); - pass &= test3(target, x, y, c); - if (!pass) { - return 1; - } - - // The test will return early on error. - fprintf(stderr, "Success!\n"); - - // This test may abort with the message "Failed to free device buffer" due - // to https://github.com/halide/Halide/issues/559 - return 0; -} From 05e427d75d5f440799d28be5b6eb9f8bcc6129fa Mon Sep 17 00:00:00 2001 From: Steven Johnson Date: Fri, 8 Jan 2021 17:25:13 -0800 Subject: [PATCH 02/12] WIP --- src/Target.cpp | 3 --- src/runtime/CMakeLists.txt | 1 - src/runtime/HalideRuntime.h | 1 - test/CMakeLists.txt | 5 ----- test/correctness/device_buffer_copy.cpp | 3 +-- 5 files changed, 1 insertion(+), 12 deletions(-) diff --git a/src/Target.cpp b/src/Target.cpp index 3b2648dff035..c9ea844729c2 100644 --- a/src/Target.cpp +++ b/src/Target.cpp @@ -773,14 +773,12 @@ bool Target::supports_type(const Type &t) const { if (t.bits() == 64) { if (t.is_float()) { return !has_feature(Metal) && - !has_feature(OpenGL) && !has_feature(OpenGLCompute) && !has_feature(D3D12Compute) && (!has_feature(Target::OpenCL) || has_feature(Target::CLDoubles)); } else { return (!has_feature(Metal) && !has_feature(OpenGLCompute) && - !has_feature(OpenGL) && !has_feature(D3D12Compute)); } } @@ -951,7 +949,6 @@ bool Target::get_runtime_compatible_target(const Target &other, Target &result) Metal, NoNEON, OpenCL, - OpenGL, OpenGLCompute, // These features are actually intersection-y, but because targets only record the _highest_, diff --git a/src/runtime/CMakeLists.txt b/src/runtime/CMakeLists.txt index 0fc83ed53f3c..25214f37e68f 100644 --- a/src/runtime/CMakeLists.txt +++ b/src/runtime/CMakeLists.txt @@ -47,7 +47,6 @@ set(RUNTIME_CPP msan msan_stubs opencl - opengl opengl_egl_context opengl_glx_context openglcompute diff --git a/src/runtime/HalideRuntime.h b/src/runtime/HalideRuntime.h index e4b7dc24fd54..3a88b91a0611 100644 --- a/src/runtime/HalideRuntime.h +++ b/src/runtime/HalideRuntime.h @@ -1283,7 +1283,6 @@ typedef enum halide_target_feature_t { halide_target_feature_cl_doubles, ///< Enable double support on OpenCL targets halide_target_feature_cl_atomic64, ///< Enable 64-bit atomics operations on OpenCL targets - halide_target_feature_opengl, ///< Enable the OpenGL runtime. NOTE: this feature is deprecated and will be removed in Halide 12. halide_target_feature_openglcompute, ///< Enable OpenGL Compute runtime. halide_target_feature_user_context, ///< Generated code takes a user_context pointer as first argument diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index dee66bc4dd21..c5147af5adc7 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -34,11 +34,6 @@ if (WITH_TEST_PERFORMANCE) add_subdirectory(performance) endif () -option(WITH_TEST_OPENGL "Build OpenGL tests" OFF) -if (WITH_TEST_OPENGL) - add_subdirectory(opengl) -endif () - option(WITH_TEST_GENERATOR "Build generator tests" ON) if (WITH_TEST_GENERATOR) add_subdirectory(generator) diff --git a/test/correctness/device_buffer_copy.cpp b/test/correctness/device_buffer_copy.cpp index cff35c8a11f1..c83a079eb151 100644 --- a/test/correctness/device_buffer_copy.cpp +++ b/test/correctness/device_buffer_copy.cpp @@ -214,8 +214,7 @@ int main(int argc, char **argv) { // Test copying between different device APIs. Probably will not // run on test infrastructure as we do not configure more than one // GPU API at a time. For now, special case CUDA and OpenCL as these are - // the most likely to be supported together. (OpenGL would be a candidate - // but buffer_copy support needs to be added.) + // the most likely to be supported together. if (target.has_feature(Target::CUDA) && target.has_feature(Target::OpenCL)) { printf("Test cross device copy device to device.\n"); { From f0c0bb2820acac4bd64d06861641517ba231fb6a Mon Sep 17 00:00:00 2001 From: Steven Johnson Date: Fri, 8 Jan 2021 17:44:10 -0800 Subject: [PATCH 03/12] Update runtime_api.cpp --- src/runtime/runtime_api.cpp | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/src/runtime/runtime_api.cpp b/src/runtime/runtime_api.cpp index 6351b68a2572..7b38a15caefe 100644 --- a/src/runtime/runtime_api.cpp +++ b/src/runtime/runtime_api.cpp @@ -147,16 +147,8 @@ extern "C" __attribute__((used)) void *halide_runtime_api_functions[] = { (void *)&halide_opencl_set_device_type, (void *)&halide_opencl_set_platform_name, (void *)&halide_opencl_wrap_cl_mem, - // (void *)&halide_opengl_context_lost, - // (void *)&halide_opengl_create_context, - // (void *)&halide_opengl_detach_texture, - // (void *)&halide_opengl_device_interface, - // (void *)&halide_opengl_get_proc_address, - // (void *)&halide_opengl_get_texture, - // (void *)&halide_opengl_initialize_kernels, - // (void *)&halide_opengl_run, - // (void *)&halide_opengl_wrap_render_target, - // (void *)&halide_opengl_wrap_texture, + (void *)&halide_opengl_create_context, + (void *)&halide_opengl_get_proc_address, (void *)&halide_openglcompute_device_interface, (void *)&halide_openglcompute_initialize_kernels, (void *)&halide_openglcompute_run, From c49b1695540a0364a3a04d3a8562deea9f54e979 Mon Sep 17 00:00:00 2001 From: Steven Johnson Date: Mon, 11 Jan 2021 10:49:51 -0800 Subject: [PATCH 04/12] Update target.cpp --- test/correctness/target.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/correctness/target.cpp b/test/correctness/target.cpp index d7d50bffed1c..7c575c5233ee 100644 --- a/test/correctness/target.cpp +++ b/test/correctness/target.cpp @@ -55,7 +55,7 @@ int main(int argc, char **argv) { Target::CUDA, Target::OpenCL, Target::OpenGLCompute, Target::Debug}); ts = t1.to_string(); - if (ts != "arm-32-android-avx-avx2-cuda-debug-jit-opencl-opengl-openglcompute-sse41") { + if (ts != "arm-32-android-avx-avx2-cuda-debug-jit-opencl-openglcompute-sse41") { printf("to_string failure: %s\n", ts.c_str()); return -1; } From 0914d57245bad6bda7cf74429c08e3a2a649904f Mon Sep 17 00:00:00 2001 From: Steven Johnson Date: Mon, 11 Jan 2021 11:18:26 -0800 Subject: [PATCH 05/12] Update Generator.cpp --- src/Generator.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Generator.cpp b/src/Generator.cpp index e732fca13889..e973885ee53d 100644 --- a/src/Generator.cpp +++ b/src/Generator.cpp @@ -757,7 +757,7 @@ std::string halide_type_to_c_type(const Type &t) { int generate_filter_main_inner(int argc, char **argv, std::ostream &cerr) { const char kUsage[] = - "gengen \n" + "gengen\n" " [-g GENERATOR_NAME] [-f FUNCTION_NAME] [-o OUTPUT_DIR] [-r RUNTIME_NAME] [-d 1|0]\n" " [-e EMIT_OPTIONS] [-n FILE_BASE_NAME] [-p PLUGIN_NAME] [-s AUTOSCHEDULER_NAME]\n" " target=target-string[,target-string...] [generator_arg=value [...]]\n" From 8f629eba8f9f622f167933f380b341f3b10e60f6 Mon Sep 17 00:00:00 2001 From: Steven Johnson Date: Mon, 11 Jan 2021 12:44:30 -0800 Subject: [PATCH 06/12] Update Generator.cpp --- src/Generator.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Generator.cpp b/src/Generator.cpp index e973885ee53d..e732fca13889 100644 --- a/src/Generator.cpp +++ b/src/Generator.cpp @@ -757,7 +757,7 @@ std::string halide_type_to_c_type(const Type &t) { int generate_filter_main_inner(int argc, char **argv, std::ostream &cerr) { const char kUsage[] = - "gengen\n" + "gengen \n" " [-g GENERATOR_NAME] [-f FUNCTION_NAME] [-o OUTPUT_DIR] [-r RUNTIME_NAME] [-d 1|0]\n" " [-e EMIT_OPTIONS] [-n FILE_BASE_NAME] [-p PLUGIN_NAME] [-s AUTOSCHEDULER_NAME]\n" " target=target-string[,target-string...] [generator_arg=value [...]]\n" From a80a371c3b5a9ad3923a9106e8810e026b5f91e3 Mon Sep 17 00:00:00 2001 From: Steven Johnson Date: Mon, 11 Jan 2021 12:55:53 -0800 Subject: [PATCH 07/12] Update Generator.cpp --- src/Generator.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Generator.cpp b/src/Generator.cpp index e732fca13889..e973885ee53d 100644 --- a/src/Generator.cpp +++ b/src/Generator.cpp @@ -757,7 +757,7 @@ std::string halide_type_to_c_type(const Type &t) { int generate_filter_main_inner(int argc, char **argv, std::ostream &cerr) { const char kUsage[] = - "gengen \n" + "gengen\n" " [-g GENERATOR_NAME] [-f FUNCTION_NAME] [-o OUTPUT_DIR] [-r RUNTIME_NAME] [-d 1|0]\n" " [-e EMIT_OPTIONS] [-n FILE_BASE_NAME] [-p PLUGIN_NAME] [-s AUTOSCHEDULER_NAME]\n" " target=target-string[,target-string...] [generator_arg=value [...]]\n" From 593accb1159bf9254b967208b15e099905af3ac4 Mon Sep 17 00:00:00 2001 From: Steven Johnson Date: Mon, 11 Jan 2021 13:24:29 -0800 Subject: [PATCH 08/12] Update LLVM_Runtime_Linker.cpp --- src/LLVM_Runtime_Linker.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/LLVM_Runtime_Linker.cpp b/src/LLVM_Runtime_Linker.cpp index 33b38bdbefe2..78f40fae23b4 100644 --- a/src/LLVM_Runtime_Linker.cpp +++ b/src/LLVM_Runtime_Linker.cpp @@ -103,7 +103,6 @@ DECLARE_CPP_INITMOD(module_jit_ref_count) DECLARE_CPP_INITMOD(msan) DECLARE_CPP_INITMOD(msan_stubs) DECLARE_CPP_INITMOD(opencl) -DECLARE_CPP_INITMOD(opengl) DECLARE_CPP_INITMOD(openglcompute) DECLARE_CPP_INITMOD(opengl_egl_context) DECLARE_CPP_INITMOD(opengl_glx_context) From 55eb19f2aa944f4d16b4a88bdb3ba34c05f46e62 Mon Sep 17 00:00:00 2001 From: Steven Johnson Date: Mon, 11 Jan 2021 16:39:38 -0800 Subject: [PATCH 09/12] Remove glsl-specific intrinsics. --- src/CodeGen_OpenGL_Dev.cpp | 131 ------------------------------------- src/CodeGen_OpenGL_Dev.h | 1 - src/Deinterleave.cpp | 8 --- src/DeviceArgument.cpp | 10 +-- src/IR.h | 3 - 5 files changed, 3 insertions(+), 150 deletions(-) diff --git a/src/CodeGen_OpenGL_Dev.cpp b/src/CodeGen_OpenGL_Dev.cpp index 496f58d902d0..2081fd37b75c 100644 --- a/src/CodeGen_OpenGL_Dev.cpp +++ b/src/CodeGen_OpenGL_Dev.cpp @@ -758,127 +758,6 @@ void CodeGen_GLSL::visit(const Evaluate *op) { print_expr(op->value); } -void CodeGen_GLSL::visit(const Call *op) { - ostringstream rhs; - if (op->is_intrinsic(Call::glsl_texture_load)) { - // This intrinsic takes five arguments - // glsl_texture_load(, , , , ) - internal_assert(op->args.size() == 5); - - // The argument to the call is either a StringImm or a broadcasted - // StringImm if this is part of a vectorized expression - internal_assert(op->args[0].as() || - (op->args[0].as() && op->args[0].as()->value.as())); - - const StringImm *string_imm = op->args[0].as(); - if (!string_imm) { - string_imm = op->args[0].as()->value.as(); - } - - // Determine the halide buffer associated with this load - string buffername = string_imm->value; - - internal_assert((op->type.code() == Type::UInt || op->type.code() == Type::Float) && - (op->type.lanes() >= 1 && op->type.lanes() <= 4)); - - if (op->type.is_uint()) { - rhs << print_type(op->type) << "(floor("; - } - - if (op->type.is_vector()) { - // The channel argument must be a ramp or a broadcast of a constant. - Expr c = op->args[4]; - internal_assert(is_const(c)); - - const Ramp *rc = c.as(); - const Broadcast *bx = op->args[2].as(); - const Broadcast *by = op->args[3].as(); - if (rc && is_const_zero(rc->base) && is_const_one(rc->stride) && bx && by) { - // If the x and y coordinates are broadcasts, and the c - // coordinate is a dense ramp, we can do a single - // texture2D call. - rhs << "texture2D(" << print_name(buffername) << ", vec2(" - << print_expr(bx->value) << ", " - << print_expr(by->value) << "))"; - - // texture2D always returns a vec4. Swizzle out the lanes we want. - switch (op->type.lanes()) { - case 1: - rhs << ".r"; - break; - case 2: - rhs << ".rg"; - break; - case 3: - rhs << ".rgb"; - break; - default: - break; - } - } else { - // Otherwise do one load per lane and make a vector - vector xs = print_lanes(op->args[2]); - vector ys = print_lanes(op->args[3]); - vector cs = print_lanes(op->args[4]); - string name = print_name(buffername); - - string x = print_expr(op->args[2]), y = print_expr(op->args[3]); - rhs << print_type(op->type) << "("; - for (int i = 0; i < op->type.lanes(); i++) { - if (i > 0) { - rhs << ", "; - } - rhs << "texture2D(" << name << ", vec2(" - << xs[i] << ", " << ys[i] << "))[" << cs[i] << "]"; - } - rhs << ")"; - } - } else if (const int64_t *ic = as_const_int(op->args[4])) { - internal_assert(*ic >= 0 && *ic < 4); - rhs << "texture2D(" << print_name(buffername) << ", vec2(" - << print_expr(op->args[2]) << ", " - << print_expr(op->args[3]) << "))." - << get_lane_suffix(*ic); - } else { - rhs << "texture2D(" << print_name(buffername) << ", vec2(" - << print_expr(op->args[2]) << ", " - << print_expr(op->args[3]) << "))[" - << print_expr(op->args[4]) << "]"; - } - - if (op->type.is_uint()) { - rhs << " * " << print_expr(cast(op->type.max())) << " + 0.5))"; - } - - } else if (op->is_intrinsic(Call::glsl_texture_store)) { - internal_assert(op->args.size() == 6); - string sval = print_expr(op->args[5]); - string suffix = get_vector_suffix(op->args[4]); - stream << get_indent() << "gl_FragColor" << suffix - << " = " << sval; - if (op->args[5].type().is_uint()) { - stream << " / " << print_expr(cast(op->args[5].type().max())); - } - stream << ";\n"; - // glsl_texture_store is called only for its side effect; there is - // no return value. - id = ""; - return; - } else if (op->is_intrinsic(Call::glsl_varying)) { - // Varying attributes should be substituted out by this point in - // codegen. - debug(2) << "Found skipped varying attribute: " << op->args[0] << "\n"; - - // Output the tagged expression. - print_expr(op->args[1]); - return; - } else { - CodeGen_GLSLBase::visit(op); - return; - } - print_assignment(op->type, rhs.str()); -} - namespace { class AllAccessConstant : public IRVisitor { using IRVisitor::visit; @@ -1255,16 +1134,6 @@ void CodeGen_GLSL::test() { Broadcast::make(2.f, 4)), "vec4 $ = vec4(2.0, 1.0, 2.0, 2.0);\n"); - // Test codegen for texture loads - Expr load4 = Call::make(Float(32, 4), Call::glsl_texture_load, - {string("buf"), - 0, - Broadcast::make(0, 4), - Broadcast::make(0, 4), - Ramp::make(0, 1, 4)}, - Call::Intrinsic); - check(load4, "vec4 $ = texture2D($buf, vec2(int(0), int(0)));\n"); - check(log(1.0f), "float $ = log(1.0);\n"); check(exp(1.0f), "float $ = exp(1.0);\n"); diff --git a/src/CodeGen_OpenGL_Dev.h b/src/CodeGen_OpenGL_Dev.h index 03cf43e1a1c8..b180b5e0ef12 100644 --- a/src/CodeGen_OpenGL_Dev.h +++ b/src/CodeGen_OpenGL_Dev.h @@ -134,7 +134,6 @@ class CodeGen_GLSL : public CodeGen_GLSLBase { void visit(const Allocate *) override; void visit(const Free *) override; - void visit(const Call *) override; void visit(const AssertStmt *) override; void visit(const Ramp *op) override; void visit(const Broadcast *) override; diff --git a/src/Deinterleave.cpp b/src/Deinterleave.cpp index f5bd78b41d97..9ca31a012ee2 100644 --- a/src/Deinterleave.cpp +++ b/src/Deinterleave.cpp @@ -320,14 +320,6 @@ class Deinterleaver : public IRGraphMutator { // Don't mutate scalars if (op->type.is_scalar()) { return op; - } else if (op->is_intrinsic(Call::glsl_texture_load)) { - // glsl_texture_load returns a result. Deinterleave by - // wrapping the call in a shuffle_vector - std::vector indices; - for (int i = 0; i < new_lanes; i++) { - indices.push_back(i * lane_stride + starting_lane); - } - return Shuffle::make({op}, indices); } else { // Vector calls are always parallel across the lanes, so we diff --git a/src/DeviceArgument.cpp b/src/DeviceArgument.cpp index 5746958235b4..77f81c5001ff 100644 --- a/src/DeviceArgument.cpp +++ b/src/DeviceArgument.cpp @@ -40,9 +40,7 @@ std::vector HostClosure::arguments() { } void HostClosure::visit(const Call *op) { - if (op->is_intrinsic(Call::glsl_texture_load) || - op->is_intrinsic(Call::image_load) || - op->is_intrinsic(Call::glsl_texture_store) || + if (op->is_intrinsic(Call::image_load) || op->is_intrinsic(Call::image_store)) { // The argument to the call is either a StringImm or a broadcasted @@ -64,12 +62,10 @@ void HostClosure::visit(const Call *op) { MemoryType::GPUTexture : MemoryType::Auto; - if (op->is_intrinsic(Call::glsl_texture_load) || - op->is_intrinsic(Call::image_load)) { + if (op->is_intrinsic(Call::image_load)) { ref.read = true; ref.dimensions = (op->args.size() - 2) / 2; - } else if (op->is_intrinsic(Call::glsl_texture_store) || - op->is_intrinsic(Call::image_store)) { + } else if (op->is_intrinsic(Call::image_store)) { ref.write = true; ref.dimensions = op->args.size() - 3; } diff --git a/src/IR.h b/src/IR.h index ce45483882e6..da57626e7c42 100644 --- a/src/IR.h +++ b/src/IR.h @@ -509,9 +509,6 @@ struct Call : public ExprNode { div_round_to_zero, dynamic_shuffle, extract_mask_element, - glsl_texture_load, - glsl_texture_store, - glsl_varying, gpu_thread_barrier, hvx_gather, hvx_scatter, From 709416e2000c5fcbad3495c793d4227280a7413e Mon Sep 17 00:00:00 2001 From: Steven Johnson Date: Mon, 11 Jan 2021 16:42:40 -0800 Subject: [PATCH 10/12] Update IR.cpp --- src/IR.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/IR.cpp b/src/IR.cpp index edc293b6ce71..96b56ed01fed 100644 --- a/src/IR.cpp +++ b/src/IR.cpp @@ -597,9 +597,6 @@ const char *const intrinsic_op_names[] = { "div_round_to_zero", "dynamic_shuffle", "extract_mask_element", - "glsl_texture_load", - "glsl_texture_store", - "glsl_varying", "gpu_thread_barrier", "hvx_gather", "hvx_scatter", From dcde5de473dfaa75f7c8d22c758c129f4dcb481a Mon Sep 17 00:00:00 2001 From: Steven Johnson Date: Mon, 11 Jan 2021 16:52:13 -0800 Subject: [PATCH 11/12] Remove shader-specific code in FlattenDimensions --- src/StorageFlattening.cpp | 30 +++--------------------------- 1 file changed, 3 insertions(+), 27 deletions(-) diff --git a/src/StorageFlattening.cpp b/src/StorageFlattening.cpp index cefd83542911..2ed7fa278939 100644 --- a/src/StorageFlattening.cpp +++ b/src/StorageFlattening.cpp @@ -39,8 +39,7 @@ class FlattenDimensions : public IRMutator { set outputs; set textures; const Target ⌖ - Scope<> realizations, shader_scope_realizations; - bool in_shader = false; + Scope<> realizations; bool in_gpu = false; Expr make_shape_var(string name, const string &field, size_t dim, @@ -110,10 +109,6 @@ class FlattenDimensions : public IRMutator { Stmt visit(const Realize *op) override { realizations.push(op->name); - if (in_shader) { - shader_scope_realizations.push(op->name); - } - if (op->memory_type == MemoryType::GPUTexture) { textures.insert(op->name); debug(2) << "found texture " << op->name << "\n"; @@ -131,10 +126,6 @@ class FlattenDimensions : public IRMutator { realizations.pop(op->name); - if (in_shader) { - shader_scope_realizations.pop(op->name); - } - // The allocation extents of the function taken into account of // the align_storage directives. It is only used to determine the // host allocation size and the strides in halide_buffer_t objects (which @@ -247,19 +238,7 @@ class FlattenDimensions : public IRMutator { } Expr value = mutate(op->values[0]); - if (in_shader && !shader_scope_realizations.contains(op->name)) { - user_assert(op->args.size() == 3) - << "Image stores require three coordinates.\n"; - Expr buffer_var = - Variable::make(type_of(), op->name + ".buffer", output_buf); - vector args = { - op->name, buffer_var, - op->args[0], op->args[1], op->args[2], - value}; - Expr store = Call::make(value.type(), Call::image_store, - args, Call::Intrinsic); - return Evaluate::make(store); - } else if (in_gpu && textures.count(op->name)) { + if (in_gpu && textures.count(op->name)) { Expr buffer_var = Variable::make(type_of(), op->name + ".buffer", output_buf); vector args(2); @@ -296,7 +275,7 @@ class FlattenDimensions : public IRMutator { internal_assert(op->value_index == 0); - if ((in_shader && !shader_scope_realizations.contains(op->name)) || (in_gpu && textures.count(op->name))) { + if (in_gpu && textures.count(op->name)) { ReductionDomain rdom; Expr buffer_var = Variable::make(type_of(), op->name + ".buffer", @@ -396,15 +375,12 @@ class FlattenDimensions : public IRMutator { } Stmt visit(const For *op) override { - bool old_in_shader = in_shader; bool old_in_gpu = in_gpu; if (op->for_type == ForType::GPUBlock || op->for_type == ForType::GPUThread) { in_gpu = true; } - // TODO: in_shader = true is no longer possible, clean up code accordingly Stmt stmt = IRMutator::visit(op); - in_shader = old_in_shader; in_gpu = old_in_gpu; return stmt; } From b4346841d1bc97d6f10a0db5208951bb83d357d2 Mon Sep 17 00:00:00 2001 From: Steven Johnson Date: Tue, 12 Jan 2021 09:55:04 -0800 Subject: [PATCH 12/12] Update TODOs --- README.md | 2 +- README_cmake.md | 2 +- cmake/HalideGeneratorHelpers.cmake | 2 +- dependencies/CMakeLists.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 370da822df33..b80441df5535 100644 --- a/README.md +++ b/README.md @@ -336,7 +336,7 @@ an older XCode which does not default to libc++. # Halide OpenGL/GLSL backend -TODO: update this for OpenGLCompute, which is staying +TODO(https://github.com/halide/Halide/issues/5633): update this for OpenGLCompute, which is staying # Halide for Hexagon HVX diff --git a/README_cmake.md b/README_cmake.md index d7842b49f977..421d93007bee 100644 --- a/README_cmake.md +++ b/README_cmake.md @@ -464,7 +464,7 @@ If the CMake version is lower than 3.18, the deprecated [`FindCUDA`][findcuda] module will be used instead. It reads the variable `CUDA_TOOLKIT_ROOT_DIR` instead of `CUDAToolkit_ROOT` above. -TODO: update this section for OpenGLCompute, which needs some (but maybe not all) of this. +TODO(https://github.com/halide/Halide/issues/5633): update this section for OpenGLCompute, which needs some (but maybe not all) of this. When targeting OpenGL, the [`FindOpenGL`][findopengl] and [`FindX11`][findx11] modules will be used to link AOT generated binaries. These modules can be diff --git a/cmake/HalideGeneratorHelpers.cmake b/cmake/HalideGeneratorHelpers.cmake index 29b069af0d94..d48e02778970 100644 --- a/cmake/HalideGeneratorHelpers.cmake +++ b/cmake/HalideGeneratorHelpers.cmake @@ -342,7 +342,7 @@ function(_Halide_add_targets_to_runtime TARGET) endfunction() function(_Halide_target_link_gpu_libs TARGET VISIBILITY) - # TODO: verify that this is correct & necessary for OpenGLCompute + # TODO(https://github.com/halide/Halide/issues/5633): verify that this is correct & necessary for OpenGLCompute if ("${ARGN}" MATCHES "openglcompute") if ("${ARGN}" MATCHES "egl") find_package(OpenGL REQUIRED COMPONENTS OpenGL EGL) diff --git a/dependencies/CMakeLists.txt b/dependencies/CMakeLists.txt index 98803af0bdbc..002afd0bcd7d 100644 --- a/dependencies/CMakeLists.txt +++ b/dependencies/CMakeLists.txt @@ -6,7 +6,7 @@ set(THREADS_PREFER_PTHREAD_FLAG YES) find_package(Threads REQUIRED) set_target_properties(Threads::Threads PROPERTIES IMPORTED_GLOBAL TRUE) -# TODO: verify this is still correct / necessary for OpenGLCompute +# TODO(https://github.com/halide/Halide/issues/5633): verify this is still correct / necessary for OpenGLCompute find_package(OpenGL) if (TARGET OpenGL::GL) set_target_properties(OpenGL::GL PROPERTIES IMPORTED_GLOBAL TRUE)