From ca72395e9913cfd9bb587d29906b42c1aa8816d1 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Wed, 17 Feb 2021 16:34:35 -0600 Subject: [PATCH 01/11] Add an examples CI job --- .github/workflows/ci.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e5b38257..12ad255a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -90,6 +90,21 @@ jobs: build_py_project_in_conda_env build_docs + examples: + name: Examples + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: "Main Script" + run: | + CONDA_ENVIRONMENT=.test-conda-env-py3.yml + curl -L -O -k https://gitlab.tiker.net/inducer/ci-support/raw/master/ci-support.sh + . ci-support.sh + EXTRA_INSTALL="pillow cgen" + build_py_project_in_conda_env + (cd examples; rm -f gl_*) + run_examples + wheels: name: Build and upload wheels runs-on: ubuntu-latest -- GitLab From 198ba5c9bc889a12f2d49ba2d08c9b406c67a177 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Wed, 17 Feb 2021 16:44:20 -0600 Subject: [PATCH 02/11] Fix up some examples --- examples/demo_meta_codepy.py | 10 +-- examples/demo_meta_template.py | 12 +-- examples/gl_particle_animation.py | 121 ++++++++++++++++++------------ 3 files changed, 86 insertions(+), 57 deletions(-) diff --git a/examples/demo_meta_codepy.py b/examples/demo_meta_codepy.py index c080109b..2ba293c5 100644 --- a/examples/demo_meta_codepy.py +++ b/examples/demo_meta_codepy.py @@ -19,10 +19,10 @@ a_buf = cl.Buffer(ctx, mf.READ_ONLY | mf.COPY_HOST_PTR, hostbuf=a) b_buf = cl.Buffer(ctx, mf.READ_ONLY | mf.COPY_HOST_PTR, hostbuf=b) c_buf = cl.Buffer(ctx, mf.WRITE_ONLY, b.nbytes) -from codepy.cgen import FunctionBody, \ +from cgen import FunctionBody, \ FunctionDeclaration, Typedef, POD, Value, \ Pointer, Module, Block, Initializer, Assign, Const -from codepy.cgen.opencl import CLKernel, CLGlobal, \ +from cgen.opencl import CLKernel, CLGlobal, \ CLRequiredWorkGroupSize mod = Module([ @@ -33,14 +33,14 @@ mod = Module([ arg_decls=[CLGlobal(Pointer(Const(POD(dtype, name)))) for name in ["tgt", "op1", "op2"]]))), Block([ - Initializer(POD(numpy.int32, "idx"), + Initializer(POD(numpy.int32, "idx"), "get_local_id(0) + %d * get_group_id(0)" % (local_size*thread_strides)) ]+[ Assign( "tgt[idx+%d]" % (o*local_size), "op1[idx+%d] + op2[idx+%d]" % ( - o*local_size, + o*local_size, o*local_size)) for o in range(thread_strides)]))]) @@ -50,7 +50,7 @@ knl(queue, (local_size*macroblock_count,), (local_size,), c_buf, a_buf, b_buf) c = numpy.empty_like(a) -cl.enqueue_read_buffer(queue, c_buf, c).wait() +cl.enqueue_copy(queue, c, c_buf).wait() assert la.norm(c-(a+b)) == 0 diff --git a/examples/demo_meta_template.py b/examples/demo_meta_template.py index fc649343..a39e9542 100644 --- a/examples/demo_meta_template.py +++ b/examples/demo_meta_template.py @@ -23,8 +23,8 @@ from mako.template import Template tpl = Template(""" __kernel void add( - __global ${ type_name } *tgt, - __global const ${ type_name } *op1, + __global ${ type_name } *tgt, + __global const ${ type_name } *op1, __global const ${ type_name } *op2) { int idx = get_local_id(0) @@ -33,13 +33,13 @@ tpl = Template(""" % for i in range(thread_strides): <% offset = i*local_size %> - tgt[idx + ${ offset }] = - op1[idx + ${ offset }] + tgt[idx + ${ offset }] = + op1[idx + ${ offset }] + op2[idx + ${ offset } ]; % endfor }""") -rendered_tpl = tpl.render(type_name="float", +rendered_tpl = tpl.render(type_name="float", local_size=local_size, thread_strides=thread_strides) knl = cl.Program(ctx, str(rendered_tpl)).build().add @@ -48,6 +48,6 @@ knl(queue, (local_size*macroblock_count,), (local_size,), c_buf, a_buf, b_buf) c = numpy.empty_like(a) -cl.enqueue_read_buffer(queue, c_buf, c).wait() +cl.enqueue_copy(queue, c, c_buf).wait() assert la.norm(c-(a+b)) == 0 diff --git a/examples/gl_particle_animation.py b/examples/gl_particle_animation.py index 1d838a2a..c8ac9c20 100644 --- a/examples/gl_particle_animation.py +++ b/examples/gl_particle_animation.py @@ -1,25 +1,27 @@ # Visualization of particles with gravity # Source: http://enja.org/2010/08/27/adventures-in-opencl-part-2-particles-with-opengl/ -import pyopencl as cl # OpenCL - GPU computing interface +import pyopencl as cl # OpenCL - GPU computing interface + mf = cl.mem_flags from pyopencl.tools import get_gl_sharing_context_properties -from OpenGL.GL import * # OpenGL - GPU rendering interface -from OpenGL.GLU import * # OpenGL tools (mipmaps, NURBS, perspective projection, shapes) -from OpenGL.GLUT import * # OpenGL tool to make a visualization window -from OpenGL.arrays import vbo -import numpy # Number tools -import sys # System tools (path, modules, maxint) +from OpenGL.GL import * # OpenGL - GPU rendering interface +from OpenGL.GLU import * # OpenGL tools (mipmaps, NURBS, perspective projection, shapes) +from OpenGL.GLUT import * # OpenGL tool to make a visualization window +from OpenGL.arrays import vbo +import numpy # Number tools +import sys # System tools (path, modules, maxint) width = 800 height = 600 num_particles = 100000 -time_step = .005 +time_step = 0.005 mouse_down = False -mouse_old = {'x': 0., 'y': 0.} -rotate = {'x': 0., 'y': 0., 'z': 0.} -translate = {'x': 0., 'y': 0., 'z': 0.} -initial_translate = {'x': 0., 'y': 0., 'z': -2.5} +mouse_old = {"x": 0.0, "y": 0.0} +rotate = {"x": 0.0, "y": 0.0, "z": 0.0} +translate = {"x": 0.0, "y": 0.0, "z": 0.0} +initial_translate = {"x": 0.0, "y": 0.0, "z": -2.5} + def glut_window(): glutInit(sys.argv) @@ -37,60 +39,79 @@ def glut_window(): glViewport(0, 0, width, height) glMatrixMode(GL_PROJECTION) glLoadIdentity() - gluPerspective(60., width / float(height), .1, 1000.) + gluPerspective(60.0, width / float(height), 0.1, 1000.0) + + return window - return(window) def initial_buffers(num_particles): np_position = numpy.ndarray((num_particles, 4), dtype=numpy.float32) np_color = numpy.ndarray((num_particles, 4), dtype=numpy.float32) np_velocity = numpy.ndarray((num_particles, 4), dtype=numpy.float32) - np_position[:,0] = numpy.sin(numpy.arange(0., num_particles) * 2.001 * numpy.pi / num_particles) - np_position[:,0] *= numpy.random.random_sample((num_particles,)) / 3. + .2 - np_position[:,1] = numpy.cos(numpy.arange(0., num_particles) * 2.001 * numpy.pi / num_particles) - np_position[:,1] *= numpy.random.random_sample((num_particles,)) / 3. + .2 - np_position[:,2] = 0. - np_position[:,3] = 1. - - np_color[:,:] = [1.,1.,1.,1.] # White particles - - np_velocity[:,0] = np_position[:,0] * 2. - np_velocity[:,1] = np_position[:,1] * 2. - np_velocity[:,2] = 3. - np_velocity[:,3] = numpy.random.random_sample((num_particles, )) - - gl_position = vbo.VBO(data=np_position, usage=GL_DYNAMIC_DRAW, target=GL_ARRAY_BUFFER) + np_position[:, 0] = numpy.sin( + numpy.arange(0.0, num_particles) * 2.001 * numpy.pi / num_particles + ) + np_position[:, 0] *= numpy.random.random_sample((num_particles,)) / 3.0 + 0.2 + np_position[:, 1] = numpy.cos( + numpy.arange(0.0, num_particles) * 2.001 * numpy.pi / num_particles + ) + np_position[:, 1] *= numpy.random.random_sample((num_particles,)) / 3.0 + 0.2 + np_position[:, 2] = 0.0 + np_position[:, 3] = 1.0 + + np_color[:, :] = [1.0, 1.0, 1.0, 1.0] # White particles + + np_velocity[:, 0] = np_position[:, 0] * 2.0 + np_velocity[:, 1] = np_position[:, 1] * 2.0 + np_velocity[:, 2] = 3.0 + np_velocity[:, 3] = numpy.random.random_sample((num_particles,)) + + gl_position = vbo.VBO( + data=np_position, usage=GL_DYNAMIC_DRAW, target=GL_ARRAY_BUFFER + ) gl_position.bind() gl_color = vbo.VBO(data=np_color, usage=GL_DYNAMIC_DRAW, target=GL_ARRAY_BUFFER) gl_color.bind() return (np_position, np_velocity, gl_position, gl_color) + def on_timer(t): glutTimerFunc(t, on_timer, t) glutPostRedisplay() + def on_key(*args): - if args[0] == '\033' or args[0] == 'q': + if args[0] == "\033" or args[0] == "q": sys.exit() + def on_click(button, state, x, y): - mouse_old['x'] = x - mouse_old['y'] = y + mouse_old["x"] = x + mouse_old["y"] = y + def on_mouse_move(x, y): - rotate['x'] += (y - mouse_old['y']) * .2 - rotate['y'] += (x - mouse_old['x']) * .2 + rotate["x"] += (y - mouse_old["y"]) * 0.2 + rotate["y"] += (x - mouse_old["x"]) * 0.2 + + mouse_old["x"] = x + mouse_old["y"] = y - mouse_old['x'] = x - mouse_old['y'] = y def on_display(): - """Render the particles""" + """Render the particles""" # Update or particle positions by calling the OpenCL kernel cl.enqueue_acquire_gl_objects(queue, [cl_gl_position, cl_gl_color]) - kernelargs = (cl_gl_position, cl_gl_color, cl_velocity, cl_start_position, cl_start_velocity, numpy.float32(time_step)) + kernelargs = ( + cl_gl_position, + cl_gl_color, + cl_velocity, + cl_start_position, + cl_start_velocity, + numpy.float32(time_step), + ) program.particle_fountain(queue, (num_particles,), None, *(kernelargs)) cl.enqueue_release_gl_objects(queue, [cl_gl_position, cl_gl_color]) queue.finish() @@ -101,11 +122,11 @@ def on_display(): glLoadIdentity() # Handle mouse transformations - glTranslatef(initial_translate['x'], initial_translate['y'], initial_translate['z']) - glRotatef(rotate['x'], 1, 0, 0) - glRotatef(rotate['y'], 0, 1, 0) #we switched around the axis so make this rotate_z - glTranslatef(translate['x'], translate['y'], translate['z']) - + glTranslatef(initial_translate["x"], initial_translate["y"], initial_translate["z"]) + glRotatef(rotate["x"], 1, 0, 0) + glRotatef(rotate["y"], 0, 1, 0) # we switched around the axis so make this rotate_z + glTranslatef(translate["x"], translate["y"], translate["z"]) + # Render the particles glEnable(GL_POINT_SMOOTH) glPointSize(2) @@ -130,17 +151,25 @@ def on_display(): glutSwapBuffers() + window = glut_window() (np_position, np_velocity, gl_position, gl_color) = initial_buffers(num_particles) platform = cl.get_platforms()[0] -context = cl.Context(properties=[(cl.context_properties.PLATFORM, platform)] + get_gl_sharing_context_properties()) +context = cl.Context( + properties=[(cl.context_properties.PLATFORM, platform)] + + get_gl_sharing_context_properties() +) queue = cl.CommandQueue(context) cl_velocity = cl.Buffer(context, mf.COPY_HOST_PTR, hostbuf=np_velocity) -cl_start_position = cl.Buffer(context, mf.READ_ONLY | mf.COPY_HOST_PTR, hostbuf=np_position) -cl_start_velocity = cl.Buffer(context, mf.READ_ONLY | mf.COPY_HOST_PTR, hostbuf=np_velocity) +cl_start_position = cl.Buffer( + context, mf.READ_ONLY | mf.COPY_HOST_PTR, hostbuf=np_position +) +cl_start_velocity = cl.Buffer( + context, mf.READ_ONLY | mf.COPY_HOST_PTR, hostbuf=np_velocity +) cl_gl_position = cl.GLBuffer(context, mf.READ_WRITE, int(gl_position)) cl_gl_color = cl.GLBuffer(context, mf.READ_WRITE, int(gl_color)) -- GitLab From babd45fcb0e8471342212a2c9d7dc23a2fe6bb22 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Wed, 17 Feb 2021 17:03:48 -0600 Subject: [PATCH 03/11] Make mandelbrot demo workable without tkinter --- examples/demo_mandelbrot.py | 137 +++++++++++++++++++++--------------- 1 file changed, 80 insertions(+), 57 deletions(-) diff --git a/examples/demo_mandelbrot.py b/examples/demo_mandelbrot.py index 6ca51347..6debd37d 100644 --- a/examples/demo_mandelbrot.py +++ b/examples/demo_mandelbrot.py @@ -23,6 +23,8 @@ import numpy as np import pyopencl as cl +from PIL import Image + # You can choose a calculation routine below (calc_fractal), uncomment # one of the three lines to test the three variations # Speed notes are listed in the same place @@ -42,7 +44,9 @@ def calc_fractal_opencl(q, maxiter): q_opencl = cl.Buffer(ctx, mf.READ_ONLY | mf.COPY_HOST_PTR, hostbuf=q) output_opencl = cl.Buffer(ctx, mf.WRITE_ONLY, output.nbytes) - prg = cl.Program(ctx, """ + prg = cl.Program( + ctx, + """ #pragma OPENCL EXTENSION cl_khr_byte_addressable_store : enable __kernel void mandelbrot(__global float2 *q, __global ushort *output, ushort const maxiter) @@ -62,10 +66,12 @@ def calc_fractal_opencl(q, maxiter): output[gid] = curiter; } } - """).build() + """, + ).build() - prg.mandelbrot(queue, output.shape, None, q_opencl, - output_opencl, np.uint16(maxiter)) + prg.mandelbrot( + queue, output.shape, None, q_opencl, output_opencl, np.uint16(maxiter) + ) cl.enqueue_copy(queue, output, output_opencl).wait() @@ -77,10 +83,15 @@ def calc_fractal_serial(q, maxiter): # note that, unlike the other two implementations, # the number of iterations per point is NOT constant z = np.zeros(q.shape, complex) - output = np.resize(np.array(0,), q.shape) + output = np.resize( + np.array( + 0, + ), + q.shape, + ) for i in range(len(q)): for iter in range(maxiter): - z[i] = z[i]*z[i] + q[i] + z[i] = z[i] * z[i] + q[i] if abs(z[i]) > 2.0: output[i] = iter break @@ -90,67 +101,79 @@ def calc_fractal_serial(q, maxiter): def calc_fractal_numpy(q, maxiter): # calculate z using numpy, this is the original # routine from vegaseat's URL - output = np.resize(np.array(0,), q.shape) + output = np.resize( + np.array( + 0, + ), + q.shape, + ) z = np.zeros(q.shape, np.complex64) for it in range(maxiter): - z = z*z + q + z = z * z + q done = np.greater(abs(z), 2.0) - q = np.where(done, 0+0j, q) - z = np.where(done, 0+0j, z) + q = np.where(done, 0 + 0j, q) + z = np.where(done, 0 + 0j, z) output = np.where(done, it, output) return output + # choose your calculation routine here by uncommenting one of the options calc_fractal = calc_fractal_opencl # calc_fractal = calc_fractal_serial # calc_fractal = calc_fractal_numpy -if __name__ == '__main__': - import tkinter as tk - from PIL import Image, ImageTk - - class Mandelbrot: - def __init__(self): - # create window - self.root = tk.Tk() - self.root.title("Mandelbrot Set") - self.create_image() - self.create_label() - # start event loop - self.root.mainloop() - - def draw(self, x1, x2, y1, y2, maxiter=30): - # draw the Mandelbrot set, from numpy example - xx = np.arange(x1, x2, (x2-x1)/w) - yy = np.arange(y2, y1, (y1-y2)/h) * 1j - q = np.ravel(xx+yy[:, np.newaxis]).astype(np.complex64) - - start_main = time.time() - output = calc_fractal(q, maxiter) - end_main = time.time() - - secs = end_main - start_main - print("Main took", secs) - - self.mandel = (output.reshape((h, w)) / - float(output.max()) * 255.).astype(np.uint8) - - def create_image(self): - """" - create the image from the draw() string - """ - # you can experiment with these x and y ranges - self.draw(-2.13, 0.77, -1.3, 1.3) - self.im = Image.fromarray(self.mandel) - self.im.putpalette([i for rgb in ((j, 0, 0) for j in range(255)) - for i in rgb]) - - def create_label(self): - # put the image on a label widget - self.image = ImageTk.PhotoImage(self.im) - self.label = tk.Label(self.root, image=self.image) - self.label.pack() - - # test the class + +class Mandelbrot: + def draw(self, x1, x2, y1, y2, maxiter=30): + # draw the Mandelbrot set, from numpy example + xx = np.arange(x1, x2, (x2 - x1) / w) + yy = np.arange(y2, y1, (y1 - y2) / h) * 1j + q = np.ravel(xx + yy[:, np.newaxis]).astype(np.complex64) + + start_main = time.time() + output = calc_fractal(q, maxiter) + end_main = time.time() + + secs = end_main - start_main + print("Main took", secs) + + self.mandel = (output.reshape((h, w)) / float(output.max()) * 255.0).astype( + np.uint8 + ) + + def create_image(self): + """ " + create the image from the draw() string + """ + # you can experiment with these x and y ranges + self.draw(-2.13, 0.77, -1.3, 1.3) + self.im = Image.fromarray(self.mandel) + self.im.putpalette([i for rgb in ((j, 0, 0) for j in range(255)) + for i in rgb]) + + def create_label(self): + # put the image on a label widget + self.image = ImageTk.PhotoImage(self.im) + self.label = tk.Label(self.root, image=self.image) + self.label.pack() + + def run_tk(self): + self.root = tk.Tk() + self.root.title("Mandelbrot Set") + self.create_image() + self.create_label() + # start event loop + self.root.mainloop() + + +if __name__ == "__main__": test = Mandelbrot() + try: + import tkinter as tk + except ModuleNotFoundError: + test.create_image() + else: + from PIL import ImageTk + + test.run_tk() -- GitLab From 27f2e49986433899663f94bc734888f289318df5 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Wed, 17 Feb 2021 17:41:48 -0600 Subject: [PATCH 04/11] Make mandelbrot demo workable without X11 --- examples/demo_mandelbrot.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/examples/demo_mandelbrot.py b/examples/demo_mandelbrot.py index 6debd37d..1c04da61 100644 --- a/examples/demo_mandelbrot.py +++ b/examples/demo_mandelbrot.py @@ -175,5 +175,7 @@ if __name__ == "__main__": test.create_image() else: from PIL import ImageTk - - test.run_tk() + try: + test.run_tk() + except tk.TclError: + test.create_image() -- GitLab From 892bf862a1990e9abbf88d098576a681687aad27 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Wed, 17 Feb 2021 17:53:14 -0600 Subject: [PATCH 05/11] Add examples CI job in Gitlab --- .gitlab-ci.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 43149559..420721ed 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -222,6 +222,20 @@ Pylint: except: - tags +Examples: + script: | + curl -L -O -k https://gitlab.tiker.net/inducer/ci-support/raw/master/ci-support.sh + . ci-support.sh + EXTRA_INSTALL="pillow cgen" + build_py_project_in_venv + (cd examples; rm -f gl_*) + run_examples + except: + - tags + tags: + - python3 + - pocl + Documentation: script: - EXTRA_INSTALL="pybind11 numpy mako" -- GitLab From 9d25c41ce02ac8489d3eb799ef69f07e00392b1e Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Wed, 17 Feb 2021 18:10:56 -0600 Subject: [PATCH 06/11] Add --no-require-main to run_examples --- .github/workflows/ci.yml | 2 +- .gitlab-ci.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 12ad255a..28a3f78d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -103,7 +103,7 @@ jobs: EXTRA_INSTALL="pillow cgen" build_py_project_in_conda_env (cd examples; rm -f gl_*) - run_examples + run_examples --no-require-main wheels: name: Build and upload wheels diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 420721ed..7a8e540b 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -229,7 +229,7 @@ Examples: EXTRA_INSTALL="pillow cgen" build_py_project_in_venv (cd examples; rm -f gl_*) - run_examples + run_examples --no-require-main except: - tags tags: -- GitLab From 5959da17e053d4c964b0133814dbda1adbdc7f9c Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Wed, 17 Feb 2021 18:15:13 -0600 Subject: [PATCH 07/11] Move print-binary to experiments --- {examples => experiments}/print-binary.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename {examples => experiments}/print-binary.py (100%) diff --git a/examples/print-binary.py b/experiments/print-binary.py similarity index 100% rename from examples/print-binary.py rename to experiments/print-binary.py -- GitLab From fc1b5c7120bcd4c2bd75a4c179635220e15c533c Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Wed, 17 Feb 2021 18:19:20 -0600 Subject: [PATCH 08/11] Fix up narray.py example --- examples/narray.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/narray.py b/examples/narray.py index 40ba9450..924c0d69 100644 --- a/examples/narray.py +++ b/examples/narray.py @@ -29,7 +29,7 @@ except: raise prg.demo(queue, (500,), None, demo_buf) -cl.enqueue_read_buffer(queue, demo_buf, demo_r).wait() +cl.enqueue_copy(queue, demo_r, demo_buf).wait() for res in demo_r: print(res) -- GitLab From e8a6d85b4b7191a357a1052136b7767e24d6ac12 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Wed, 17 Feb 2021 18:23:38 -0600 Subject: [PATCH 09/11] Install mako for examples CI --- .github/workflows/ci.yml | 2 +- .gitlab-ci.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 28a3f78d..6ae645a3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -100,7 +100,7 @@ jobs: CONDA_ENVIRONMENT=.test-conda-env-py3.yml curl -L -O -k https://gitlab.tiker.net/inducer/ci-support/raw/master/ci-support.sh . ci-support.sh - EXTRA_INSTALL="pillow cgen" + EXTRA_INSTALL="pillow cgen mako" build_py_project_in_conda_env (cd examples; rm -f gl_*) run_examples --no-require-main diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 7a8e540b..c0442a73 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -226,7 +226,7 @@ Examples: script: | curl -L -O -k https://gitlab.tiker.net/inducer/ci-support/raw/master/ci-support.sh . ci-support.sh - EXTRA_INSTALL="pillow cgen" + EXTRA_INSTALL="pillow cgen mako" build_py_project_in_venv (cd examples; rm -f gl_*) run_examples --no-require-main -- GitLab From b35e94da8435eb8291c0d9b934a141002f9d0ac7 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Wed, 17 Feb 2021 18:35:35 -0600 Subject: [PATCH 10/11] Fix up median-filter example --- .github/workflows/ci.yml | 2 +- .gitlab-ci.yml | 2 +- examples/median-filter.py | 25 +++++++------------------ examples/noisyImage.jpg | Bin 0 -> 66806 bytes 4 files changed, 9 insertions(+), 20 deletions(-) create mode 100644 examples/noisyImage.jpg diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6ae645a3..96fb4a49 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -100,7 +100,7 @@ jobs: CONDA_ENVIRONMENT=.test-conda-env-py3.yml curl -L -O -k https://gitlab.tiker.net/inducer/ci-support/raw/master/ci-support.sh . ci-support.sh - EXTRA_INSTALL="pillow cgen mako" + EXTRA_INSTALL="pillow cgen mako imageio" build_py_project_in_conda_env (cd examples; rm -f gl_*) run_examples --no-require-main diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index c0442a73..fa83e362 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -226,7 +226,7 @@ Examples: script: | curl -L -O -k https://gitlab.tiker.net/inducer/ci-support/raw/master/ci-support.sh . ci-support.sh - EXTRA_INSTALL="pillow cgen mako" + EXTRA_INSTALL="pillow cgen mako imageio" build_py_project_in_venv (cd examples; rm -f gl_*) run_examples --no-require-main diff --git a/examples/median-filter.py b/examples/median-filter.py index 010e2851..7f787500 100644 --- a/examples/median-filter.py +++ b/examples/median-filter.py @@ -1,25 +1,14 @@ import pyopencl as cl import numpy as np -from scipy.misc import imread, imsave +from imageio import imread, imsave #Read in image -img = imread('noisyImage.jpg', flatten=True).astype(np.float32) +img = imread('noisyImage.jpg').astype(np.float32) +print(img.shape) +img = np.mean(img, axis=2) +print(img.shape) -# Get platforms, both CPU and GPU -plat = cl.get_platforms() -CPU = plat[0].get_devices() -try: - GPU = plat[1].get_devices() -except IndexError: - GPU = "none" - -#Create context for GPU/CPU -if GPU!= "none": - ctx = cl.Context(GPU) -else: - ctx = cl.Context(CPU) - -# Create queue for each kernel execution +ctx = cl.create_some_context() queue = cl.CommandQueue(ctx) mf = cl.mem_flags @@ -97,4 +86,4 @@ result = np.empty_like(img) cl.enqueue_copy(queue, result, result_g) # Show the blurred image -imsave('medianFilter-OpenCL.jpg',result) \ No newline at end of file +imsave('medianFilter-OpenCL.jpg', result) diff --git a/examples/noisyImage.jpg b/examples/noisyImage.jpg new file mode 100644 index 0000000000000000000000000000000000000000..64db427319e4f2e4ce20d76f44cec3cca51a9697 GIT binary patch literal 66806 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 HTc7{geJWNl literal 0 HcmV?d00001 -- GitLab From e09283357ef01eaaea77f88f97b4a09268d413b7 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Wed, 17 Feb 2021 18:41:51 -0600 Subject: [PATCH 11/11] Transpose: enable check, allow running without matplotlib --- examples/transpose.py | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/examples/transpose.py b/examples/transpose.py index 9b07e2b0..6b06a988 100644 --- a/examples/transpose.py +++ b/examples/transpose.py @@ -102,7 +102,7 @@ def transpose_using_cl(ctx, queue, cpu_src, cls): w, h = cpu_src.shape result = numpy.empty((h, w), dtype=cpu_src.dtype) - cl.enqueue_read_buffer(queue, a_t_buf, result).wait() + cl.enqueue_copy(queue, result, a_t_buf).wait() a_buf.release() a_t_buf.release() @@ -144,7 +144,7 @@ def benchmark_transpose(): for dev in ctx.devices: assert dev.local_mem_size > 0 - queue = cl.CommandQueue(ctx, + queue = cl.CommandQueue(ctx, properties=cl.command_queue_properties.PROFILING_ENABLE) sizes = [int(((2**i) // 32) * 32) @@ -186,27 +186,27 @@ def benchmark_transpose(): a_buf.release() a_t_buf.release() - from matplotlib.pyplot import clf, plot, title, xlabel, ylabel, \ - savefig, legend, grid - for i in range(len(methods)): - clf() - for j in range(i+1): - method = methods[j] - name = method.__name__.replace("Transpose", "") - plot(sizes, numpy.array(mem_bandwidths[method])/1e9, "o-", label=name) + try: + from matplotlib.pyplot import clf, plot, title, xlabel, ylabel, \ + savefig, legend, grid + except ModuleNotFoundError: + pass + else: + for i in range(len(methods)): + clf() + for j in range(i+1): + method = methods[j] + name = method.__name__.replace("Transpose", "") + plot(sizes, numpy.array(mem_bandwidths[method])/1e9, "o-", label=name) - xlabel("Matrix width/height $N$") - ylabel("Memory Bandwidth [GB/s]") - legend(loc="best") - grid() + xlabel("Matrix width/height $N$") + ylabel("Memory Bandwidth [GB/s]") + legend(loc="best") + grid() - savefig("transpose-benchmark-%d.pdf" % i) + savefig("transpose-benchmark-%d.pdf" % i) - - - - -#check_transpose() +check_transpose() benchmark_transpose() -- GitLab