--- OpenCL/OpenCL.pm 2012/04/25 20:29:03 1.58 +++ OpenCL/OpenCL.pm 2012/05/03 23:30:08 1.69 @@ -143,10 +143,10 @@ my $buf = $ctx->buffer_sv (OpenCL::MEM_COPY_HOST_PTR, "helmut"); - $queue->enqueue_read_buffer ($buf, 1, 1, 3, my $data); + $queue->read_buffer ($buf, 1, 1, 3, my $data); print "$data\n"; - my $ev = $queue->enqueue_read_buffer ($buf, 0, 1, 3, my $data); + my $ev = $queue->read_buffer ($buf, 0, 1, 3, my $data); $ev->wait; print "$data\n"; # prints "elm" @@ -176,10 +176,10 @@ $kernel->set_buffer (1, $output); # execute it for all 4 numbers - $queue->enqueue_nd_range_kernel ($kernel, undef, [4], undef); + $queue->nd_range_kernel ($kernel, undef, [4], undef); # enqueue a synchronous read - $queue->enqueue_read_buffer ($output, 1, 0, OpenCL::SIZEOF_FLOAT * 4, my $data); + $queue->read_buffer ($output, 1, 0, OpenCL::SIZEOF_FLOAT * 4, my $data); # print the results: printf "%s\n", join ", ", unpack "f*", $data; @@ -188,13 +188,13 @@ showing off barriers. # execute it for all 4 numbers - $queue->enqueue_nd_range_kernel ($kernel, undef, [4], undef); + $queue->nd_range_kernel ($kernel, undef, [4], undef); # enqueue a barrier to ensure in-order execution - $queue->enqueue_barrier; + $queue->barrier; # enqueue an async read - $queue->enqueue_read_buffer ($output, 0, 0, OpenCL::SIZEOF_FLOAT * 4, my $data); + $queue->read_buffer ($output, 0, 0, OpenCL::SIZEOF_FLOAT * 4, my $data); # wait for all requests to finish $queue->finish; @@ -203,10 +203,10 @@ showing off event objects and wait lists. # execute it for all 4 numbers - my $ev = $queue->enqueue_nd_range_kernel ($kernel, undef, [4], undef); + my $ev = $queue->nd_range_kernel ($kernel, undef, [4], undef); # enqueue an async read - $ev = $queue->enqueue_read_buffer ($output, 0, 0, OpenCL::SIZEOF_FLOAT * 4, my $data, $ev); + $ev = $queue->read_buffer ($output, 0, 0, OpenCL::SIZEOF_FLOAT * 4, my $data, $ev); # wait for the last event to complete $ev->wait; @@ -214,16 +214,19 @@ =head2 Use the OpenGL module to share a texture between OpenCL and OpenGL and draw some julia set tunnel effect. -This is quite a long example to get you going. +This is quite a long example to get you going - you can download it from +L. use OpenGL ":all"; use OpenCL; + my $S = $ARGV[0] || 256; # window/texture size, smaller is faster + # open a window and create a gl texture - OpenGL::glpOpenWindow width => 256, height => 256; + OpenGL::glpOpenWindow width => $S, height => $S; my $texid = glGenTextures_p 1; glBindTexture GL_TEXTURE_2D, $texid; - glTexImage2D_c GL_TEXTURE_2D, 0, GL_RGBA8, 256, 256, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0; + glTexImage2D_c GL_TEXTURE_2D, 0, GL_RGBA8, $S, $S, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0; # find and use the first opencl device that let's us get a shared opengl context my $platform; @@ -252,19 +255,20 @@ kernel void juliatunnel (write_only image2d_t img, float time) { - float2 p = (float2)(get_global_id (0), get_global_id (1)) / 256.f * 2.f - 1.f; + int2 xy = (int2)(get_global_id (0), get_global_id (1)); + float2 p = convert_float2 (xy) / $S.f * 2.f - 1.f; - float2 m = (float2)(1.f, p.y) / fabs (p.x); - m.x = fabs (fmod (m.x + time * 0.05f, 4.f)) - 2.f; + float2 m = (float2)(1.f, p.y) / fabs (p.x); // tunnel + m.x = fabs (fmod (m.x + time * 0.05f, 4.f) - 2.f); float2 z = m; - float2 c = (float2)(sin (time * 0.05005), cos (time * 0.06001)); + float2 c = (float2)(sin (time * 0.01133f), cos (time * 0.02521f)); - for (int i = 0; i < 25 && dot (z, z) < 4.f; ++i) + for (int i = 0; i < 25 && dot (z, z) < 4.f; ++i) // standard julia z = (float2)(z.x * z.x - z.y * z.y, 2.f * z.x * z.y) + c; - float3 colour = (float3)(z.x, z.y, z.x * z.y); - write_imagef (img, (int2)(get_global_id (0), get_global_id (1)), (float4)(colour * p.x * p.x, 1.)); + float3 colour = (float3)(z.x, z.y, atan2 (z.y, z.x)); + write_imagef (img, xy, (float4)(colour * p.x * p.x, 1.)); } EOF @@ -275,15 +279,14 @@ for (my $time; ; ++$time) { # acquire objects from opengl - $queue->enqueue_acquire_gl_objects ([$tex]); + $queue->acquire_gl_objects ([$tex]); # configure and run our kernel - $kernel->set_image2d (0, $tex); - $kernel->set_float (1, $time); - $queue->enqueue_nd_range_kernel ($kernel, undef, [256, 256], undef); + $kernel->setf ("mf", $tex, $time*2); # mf = memory object, float + $queue->nd_range_kernel ($kernel, undef, [$S, $S], undef); # release objects to opengl again - $queue->enqueue_release_gl_objects ([$tex]); + $queue->release_gl_objects ([$tex]); # wait $queue->finish; @@ -304,6 +307,33 @@ select undef, undef, undef, 1/60; } +=head2 How to modify the previous example to not rely on GL sharing. + +For those poor souls with only a sucky CPU OpenCL implementation, you +currently have to read the image into some perl scalar, and then modify a +texture or use glDrawPixels or so). + +First, when you don't need gl sharing, you can create the context much simpler: + + $ctx = $platform->context (undef, [$dev]) + +To use a texture, you would modify the above example by creating an +OpenCL::Image manually instead of deriving it from a texture: + + my $tex = $ctx->image2d (OpenCL::MEM_WRITE_ONLY, OpenCL::RGBA, OpenCL::UNORM_INT8, $S, $S); + +And in the darw loop, intead of acquire_gl_objects/release_gl_objects, you +would read the image2d after the kernel has written it: + + $queue->read_image ($tex, 0, 0, 0, 0, $S, $S, 1, 0, 0, my $data); + +And then you would upload the pixel data to the texture (or use glDrawPixels): + + glTexSubImage2D_s GL_TEXTURE_2D, 0, 0, 0, $S, $S, GL_RGBA, GL_UNSIGNED_BYTE, $data; + +The fully modified example can be found at +L. + =head1 DOCUMENTATION =head2 BASIC CONVENTIONS @@ -422,7 +452,7 @@ # initialise AnyEvent, by creating a watcher, or: AnyEvent::detect; - my $e = $queue->enqueue_marker; + my $e = $queue->marker; $e->cb (sub { warn "opencl is finished\n"; }) @@ -449,7 +479,7 @@ the C loop following the marker call will be interrupted by the callback: - my $e = $queue->enqueue_marker; + my $e = $queue->marker; my $flag; $e->cb (sub { $flag = 1 }); 1 until $flag; @@ -495,12 +525,13 @@ package OpenCL; use common::sense; +use Carp (); use Async::Interrupt (); our $POLL_FUNC; # set by XS BEGIN { - our $VERSION = '0.97'; + our $VERSION = '0.98'; require XSLoader; XSLoader::load (__PACKAGE__, $VERSION); @@ -528,6 +559,9 @@ @OpenCL::Image1DBuffer::ISA = OpenCL::Image::; @OpenCL::UserEvent::ISA = OpenCL::Event::; + + @OpenCL::MappedBuffer::ISA = + @OpenCL::MappedImage::ISA = OpenCL::Mapped::; } =head2 THE OpenCL PACKAGE @@ -539,9 +573,10 @@ The last error returned by a function - it's only valid after an error occured and before calling another OpenCL function. -=item $str = OpenCL::err2str $errval +=item $str = OpenCL::err2str [$errval] -Comverts an error value into a human readable string. +Converts an error value into a human readable string. IF no error value is +given, then the last error will be used (as returned by OpenCL::errno). =item $str = OpenCL::enum2str $enum @@ -1004,16 +1039,19 @@ sub OpenCL::Context::build_program { my ($self, $prog, $options) = @_; - require Carp; - $prog = $self->program_with_source ($prog) unless ref $prog; - # we build separately per device so we instantly know which one failed + eval { $prog->build (undef, $options); 1 } + or errno == BUILD_PROGRAM_FAILURE + or errno == INVALID_BINARY # workaround nvidia bug + or Carp::croak "OpenCL::Context->build_program: " . err2str; + + # we check status for all devices for my $dev ($self->devices) { - eval { $prog->build ([$dev], $options); 1 } - or Carp::croak ("Building OpenCL program for device '" . $dev->name . "' failed:\n" - . $prog->build_log ($dev)); + $prog->build_status ($dev) == BUILD_SUCCESS + or Carp::croak "Building OpenCL program for device '" . $dev->name . "' failed:\n" + . $prog->build_log ($dev); } $prog @@ -1047,7 +1085,7 @@ Creates a new OpenCL::Buffer (actually OpenCL::BufferObj) object and initialise it with the given data values. -=item $img = $ctx->image ($self, $flags, $channel_order, $channel_type, $type, $width, $height, $depth, $array_size = 0, $row_pitch = 0, $slice_pitch = 0, $num_mip_level = 0, $num_samples = 0, $*data = &PL_sv_undef) +=item $img = $ctx->image ($self, $flags, $channel_order, $channel_type, $type, $width, $height, $depth = 0, $array_size = 0, $row_pitch = 0, $slice_pitch = 0, $num_mip_level = 0, $num_samples = 0, $*data = &PL_sv_undef) Creates a new OpenCL::Image object and optionally initialises it with the given data values. @@ -1122,6 +1160,17 @@ L +=item ($program, \@status) = $ctx->program_with_binary (\@devices, \@binaries) + +Creates a new OpenCL::Program object from the given binaries. + +L + +Example: clone an existing program object that contains a successfully +compiled program, no matter how useless this is. + + my $clone = $ctx->program_with_binary ([$prog->devices], [$prog->binaries]); + =item $packed_value = $ctx->info ($name) See C<< $platform->info >> for details. @@ -1153,12 +1202,13 @@ =head2 THE OpenCL::Queue CLASS An OpenCL::Queue represents an execution queue for OpenCL. You execute -requests by calling their respective C method and waitinf for -it to complete in some way. +requests by calling their respective method and waiting for it to complete +in some way. -All the enqueue methods return an event object that can be used to wait -for completion, unless the method is called in void context, in which case -no event object is created. +Most methods that enqueue some request return an event object that can +be used to wait for completion (optionally using a callback), unless +the method is called in void context, in which case no event object is +created. They also allow you to specify any number of other event objects that this request has to wait for before it starts executing, by simply passing the @@ -1167,7 +1217,7 @@ events. This makes it possible to code operations such as this, without having to put a valid event object into C<$event> first: - $event = $queue->enqueue_xxx (..., $event); + $event = $queue->xxx (..., $event); Queues execute in-order by default, without any parallelism, so in most cases (i.e. you use only one queue) it's not necessary to wait for or @@ -1176,64 +1226,69 @@ =over 4 -=item $ev = $queue->enqueue_read_buffer ($buffer, $blocking, $offset, $len, $data, $wait_events...) +=item $ev = $queue->read_buffer ($buffer, $blocking, $offset, $len, $data, $wait_events...) Reads data from buffer into the given string. L -=item $ev = $queue->enqueue_write_buffer ($buffer, $blocking, $offset, $data, $wait_events...) +=item $ev = $queue->write_buffer ($buffer, $blocking, $offset, $data, $wait_events...) Writes data to buffer from the given string. L -=item $ev = $queue->enqueue_copy_buffer ($src, $dst, $src_offset, $dst_offset, $len, $wait_events...) +=item $ev = $queue->copy_buffer ($src, $dst, $src_offset, $dst_offset, $len, $wait_events...) L -=item $ev = $queue->enqueue_read_buffer_rect (OpenCL::Memory buf, cl_bool blocking, $buf_x, $buf_y, $buf_z, $host_x, $host_y, $host_z, $width, $height, $depth, $buf_row_pitch, $buf_slice_pitch, $host_row_pitch, $host_slice_pitch, $data, $wait_events...) +=item $ev = $queue->read_buffer_rect (OpenCL::Memory buf, cl_bool blocking, $buf_x, $buf_y, $buf_z, $host_x, $host_y, $host_z, $width, $height, $depth, $buf_row_pitch, $buf_slice_pitch, $host_row_pitch, $host_slice_pitch, $data, $wait_events...) http://www.khronos.org/registry/cl/sdk/1.1/docs/man/xhtml/clEnqueueReadBufferRect.html -=item $ev = $queue->enqueue_write_buffer_rect (OpenCL::Memory buf, cl_bool blocking, $buf_x, $buf_y, $buf_z, $host_x, $host_y, $host_z, $width, $height, $depth, $buf_row_pitch, $buf_slice_pitch, $host_row_pitch, $host_slice_pitch, $data, $wait_events...) +=item $ev = $queue->write_buffer_rect (OpenCL::Memory buf, cl_bool blocking, $buf_x, $buf_y, $buf_z, $host_x, $host_y, $host_z, $width, $height, $depth, $buf_row_pitch, $buf_slice_pitch, $host_row_pitch, $host_slice_pitch, $data, $wait_events...) http://www.khronos.org/registry/cl/sdk/1.1/docs/man/xhtml/clEnqueueWriteBufferRect.html -=item $ev = $queue->enqueue_read_image ($src, $blocking, $x, $y, $z, $width, $height, $depth, $row_pitch, $slice_pitch, $data, $wait_events...) +=item $ev = $queue->copy_buffer_to_image ($src_buffer, $dst_image, $src_offset, $dst_x, $dst_y, $dst_z, $width, $height, $depth, $wait_events...) + +L -L +=item $ev = $queue->read_image ($src, $blocking, $x, $y, $z, $width, $height, $depth, $row_pitch, $slice_pitch, $data, $wait_events...) -=item $ev = $queue->enqueue_copy_buffer_to_image ($src_buffer, $dst_image, $src_offset, $dst_x, $dst_y, $dst_z, $width, $height, $depth, $wait_events...) +C<$row_pitch> (and C<$slice_pitch>) can be C<0>, in which case the OpenCL +module uses the image width (and height) to supply default values. L -=item $ev = $queue->enqueue_write_image ($src, $blocking, $x, $y, $z, $width, $height, $depth, $row_pitch, $slice_pitch, $data, $wait_events...) +=item $ev = $queue->write_image ($src, $blocking, $x, $y, $z, $width, $height, $depth, $row_pitch, $slice_pitch, $data, $wait_events...) +C<$row_pitch> (and C<$slice_pitch>) can be C<0>, in which case the OpenCL +module uses the image width (and height) to supply default values. L -=item $ev = $queue->enqueue_copy_image ($src_image, $dst_image, $src_x, $src_y, $src_z, $dst_x, $dst_y, $dst_z, $width, $height, $depth, $wait_events...) +=item $ev = $queue->copy_image ($src_image, $dst_image, $src_x, $src_y, $src_z, $dst_x, $dst_y, $dst_z, $width, $height, $depth, $wait_events...) L -=item $ev = $queue->enqueue_copy_image_to_buffer ($src_image, $dst_image, $src_x, $src_y, $src_z, $width, $height, $depth, $dst_offset, $wait_events...) +=item $ev = $queue->copy_image_to_buffer ($src_image, $dst_image, $src_x, $src_y, $src_z, $width, $height, $depth, $dst_offset, $wait_events...) L -=item $ev = $queue->enqueue_copy_buffer_rect ($src, $dst, $src_x, $src_y, $src_z, $dst_x, $dst_y, $dst_z, $width, $height, $depth, $src_row_pitch, $src_slice_pitch, $dst_row_pitch, $dst_slice_pitch, $wait_event...) +=item $ev = $queue->copy_buffer_rect ($src, $dst, $src_x, $src_y, $src_z, $dst_x, $dst_y, $dst_z, $width, $height, $depth, $src_row_pitch, $src_slice_pitch, $dst_row_pitch, $dst_slice_pitch, $wait_event...) Yeah. L. -=item $ev = $queue->enqueue_fill_buffer ($mem, $pattern, $offset, $size, ...) +=item $ev = $queue->fill_buffer ($mem, $pattern, $offset, $size, ...) Fills the given buffer object with repeated applications of C<$pattern>, starting at C<$offset> for C<$size> octets. L -=item $ev = $queue->enqueue_fill_image ($img, $r, $g, $b, $a, $x, $y, $z, $width, $height, $depth, ...) +=item $ev = $queue->fill_image ($img, $r, $g, $b, $a, $x, $y, $z, $width, $height, $depth, ...) Fills the given image area with the given rgba colour components. The components are normally floating point values between C<0> and C<1>, @@ -1242,11 +1297,11 @@ L -=item $ev = $queue->enqueue_task ($kernel, $wait_events...) +=item $ev = $queue->task ($kernel, $wait_events...) L -=item $ev = $queue->enqueue_nd_range_kernel ($kernel, \@global_work_offset, \@global_work_size, \@local_work_size, $wait_events...) +=item $ev = $queue->nd_range_kernel ($kernel, \@global_work_offset, \@global_work_size, \@local_work_size, $wait_events...) Enqueues a kernel execution. @@ -1264,29 +1319,29 @@ L -=item $ev = $queue->enqueue_acquire_gl_objects ([object, ...], $wait_events...) +=item $ev = $queue->acquire_gl_objects ([object, ...], $wait_events...) Enqueues a list (an array-ref of OpenCL::Memory objects) to be acquired for subsequent OpenCL usage. L -=item $ev = $queue->enqueue_release_gl_objects ([object, ...], $wait_events...) +=item $ev = $queue->release_gl_objects ([object, ...], $wait_events...) Enqueues a list (an array-ref of OpenCL::Memory objects) to be released for subsequent OpenGL usage. L -=item $ev = $queue->enqueue_wait_for_events ($wait_events...) +=item $ev = $queue->wait_for_events ($wait_events...) L -=item $ev = $queue->enqueue_marker ($wait_events...) +=item $ev = $queue->marker ($wait_events...) L -=item $ev = $queue->enqueue_barrier ($wait_events...) +=item $ev = $queue->barrier ($wait_events...) L @@ -1326,6 +1381,73 @@ =back +=head3 MEMORY MAPPED BUFFERS + +OpenCL allows you to map buffers and images to host memory (read: perl +scalars). This is done much like reading or copying a buffer, by enqueuing +a map or unmap operation on the command queue. + +The map operations return an C object - see L section for details on what to do with these +objects. + +The object will be unmapped automatically when the mapped object is +destroyed (you can use a barrier to make sure the unmap has finished, +before using the buffer in a kernel), but you can also enqueue an unmap +operation manually. + +=over 4 + +=item $mapped_buffer = $queue->map_buffer ($buf, $blocking=1, $map_flags=OpenCL::MAP_READ|OpenCL::MAP_WRITE, $offset=0, $size=undef, $wait_events...) + +Maps the given buffer into host memory and returns an +C object. If C<$size> is specified as undef, then +the map will extend to the end of the buffer. + +L + +Example: map the buffer $buf fully and replace the first 4 bytes by "abcd", then unmap. + + { + my $mapped = $queue->map_buffer ($buf, 1, OpenCL::MAP_WRITE); + substr $$mapped, 0, 4, "abcd"; + } # asynchronously unmap because $mapped is destroyed + +=item $mapped_image = $queue->map_image ($img, $blocking=1, $map_flags=OpenCL::MAP_READ|OpenCL::MAP_WRITE, $x=0, $y=0, $z=0, $width=undef, $height=undef, $depth=undef, $wait_events...) + +Maps the given image area into host memory and return an +C object. + +If any of C<$width>, C<$height> and/or C<$depth> are C then they +will be replaced by the maximum possible value. + +L + +Example: map an image (with OpenCL::UNSIGNED_INT8 channel type) and set +the first channel of the leftmost column to 5, then explicitly unmap +it. You are not necessarily meant to do it this way, this example just +shows you the accessors to use :) + + my $mapped = $queue->map_image ($image, 1, OpenCL::MAP_WRITE); + + $mapped->set ($_ * $mapped->row_pitch, pack "C", 5) + for 0..$image->height; + + $mapped->unmap;. + $mapped->wait; # only needed for out of order queues normally + +=item $ev = $queue->unmap ($mapped, $wait_events...) + +Unmaps the data from host memory. You must not call any methods that +modify the data, or modify the data scalar directly, after calling this +method. + +The mapped event object will always be passed as part of the +$wait_events. The mapped event object will be replaced by the new event +object that this request creates. + +=back + =head2 THE OpenCL::Memory CLASS This the superclass of all memory objects - OpenCL::Buffer, OpenCL::Image, @@ -1525,11 +1647,14 @@ compiling whether you use a callback or not. See C if you want to make sure the build is done in the background. -Note that some OpenCL implementations atc up badly, and don't call the +Note that some OpenCL implementations act up badly, and don't call the callback in some error cases (but call it in others). This implementation assumes the callback will always be called, and leaks memory if this is not so. So best make sure you don't pass in invalid values. +Some implementations fail with C when the +compilation state is successful but some later stage fails. + L =item $program->build_async (\@devices = undef, $options = "", $cb->($program) = undef) @@ -1687,6 +1812,40 @@ =for gengetinfo end kernel_work_group +=item $kernel->setf ($format, ...) + +Sets the arguments of a kernel. Since OpenCL 1.1 doesn't have a generic +way to set arguments (and with OpenCL 1.2 it might be rather slow), you +need to specify a format argument, much as with C, to tell OpenCL +what type of argument it is. + +The format arguments are single letters: + + c char + C unsigned char + s short + S unsigned short + i int + I unsigned int + l long + L unsigned long + + h half float (0..65535) + f float + d double + + z local (octet size) + + m memory object (buffer or image) + a sampler + e event + +Space characters in the format string are ignored. + +Example: set the arguments for a kernel that expects an int, two floats, a buffer and an image. + + $kernel->setf ("i ff mm", 5, 0.5, 3, $buffer, $image); + =item $kernel->set_TYPE ($index, $value) =item $kernel->set_char ($index, $value) @@ -1839,6 +1998,107 @@ =back +=head2 THE OpenCL::Mapped CLASS + +This class represents objects mapped into host memory. They are +represented by a blessed string scalar. The string data is the mapped +memory area, that is, if you read or write it, then the mapped object is +accessed directly. + +You must only ever use operations that modify the string in-place - for +example, a C that doesn't change the length, or maybe a regex that +doesn't change the length. Any other operation might cause the data to be +copied. + +When the object is destroyed it will enqueue an implicit unmap operation +on the queue that was used to create it. + +Keep in mind that you I to unmap (or destroy) mapped objects before +OpenCL sees the changes, even if some implementations don't need this +sometimes. + +Example, replace the first two floats in the mapped buffer by 1 and 2. + + my $mapped = $queue->map_buffer ($buf, ... + $mapped->event->wait; # make sure it's there + + # now replace first 8 bytes by new data, which is exactly 8 bytes long + # we blindly assume device endianness to equal host endianness + # (and of course, we assume iee 754 single precision floats :) + substr $$mapped, 0, 8, pack "f*", 1, 2; + +=over 4 + +=item $ev = $mapped->unmap ($wait_events...) + +Unmaps the mapped memory object, using the queue originally used to create +it, quite similarly to C<< $queue->unmap ($mapped, ...) >>. + +=item $bool = $mapped->mapped + +Returns whether the object is still mapped - true before an C is +enqueued, false afterwards. + +=item $ev = $mapped->event + +Return the event object associated with the mapped object. Initially, this +will be the event object created when mapping the object, and after an +unmap, this will be the event object that the unmap operation created. + +=item $mapped->wait + +Same as C<< $mapped->event->wait >> - makes sure no operations on this +mapped object are outstanding. + +=item $bytes = $mapped->size + +Returns the size of the mapped area, in bytes. Same as C. + +=item $ptr = $mapped->ptr + +Returns the raw memory address of the mapped area. + +=item $mapped->set ($offset, $data) + +Replaces the data at the given C<$offset> in the memory area by the new +C<$data>. This method is safer than direct manipulation of C<$mapped> +because it does bounds-checking, but also slower. + +=item $data = $mapped->get ($offset, $length) + +Returns (without copying) a scalar representing the data at the given +C<$offset> and C<$length> in the mapped memory area. This is the same as +the following substr, except much slower; + + $data = substr $$mapped, $offset, $length + +=cut + +sub OpenCL::Mapped::get { + substr ${$_[0]}, $_[1], $_[2] +} + +=back + +=head2 THE OpenCL::MappedBuffer CLASS + +This is a subclass of OpenCL::Mapped, representing mapped buffers. + +=head2 THE OpenCL::MappedImage CLASS + +This is a subclass of OpenCL::Mapped, representing mapped images. + +=over 4 + +=item $bytes = $mapped->row_pitch + +=item $bytes = $mapped->slice_pitch + +Return the row or slice pitch of the image that has been mapped. + +=back + + =cut 1;