9.3. バッファーの読み込み・書き込み・コピー

ホストプログラムではメモリオブジェクトを継承したバッファオブジェクトの生成を行なうclCreateBufferを前の項目で解説しました。

バッファオブジェクトはカーネルに処理をさせたいデータを渡したり、カーネルから処理済みのデータを受け取ることを可能とします。

つまり以下の2点のような処理が必要となります。

OpenCLではclEnqueueReadBuffer/clEnqueueWriteBufferを使いホストメモリ-バッファオブジェクト間のデータ読み込み、書き込みを行います。

9.3.1. clEnqueueReadBufferとclEnqueueWriteBuffer

まずバッファオブジェクトからの読み込みから見てみましょう。

注記

詳しくは「表:clEnqueueReadBufferとclEnqueueWriteBuffer」(表B.31「表:clEnqueueReadBufferとclEnqueueWriteBuffer」)を参照ください。

int org.jocl.CL.clEnqueueReadBuffer(
    cl_command_queue command_queue,  //(1)
    cl_mem buffer, //(2)
    boolean blocking_read, //(3)
    long offset, //(4)
    long cb, //(5)
    Pointer ptr, //(6)
    int num_events_in_wait_list, //(7)
    cl_event[] event_wait_list, //(8)
    cl_event event) //(9)

(1)

コマンドキュー

(2)

読みとるバッファオブジェクト

(3)

読みとりのブロックフラグ

(4)

オフセット

(5)

サイズ

(6)

読み取ったデータをコピーする先のホストポインタ

(7)

待機イベントリストにあるイベント数

(8)

待機イベントリスト

(9)

イベントオブジェクト

このうち❹ はオフセットが必要な場合、❼、❽、❾はコマンドの処理順序を整理させる場合でなければNULLか0を設定します。

❷は、clCreateBufferで生成したcl_memオブジェクトを指定して、読み取った結果をコピーするホストメモリの領域を指定するポインタ(cl_memオブジェクトで割当てたデータ型と同型を使う)を指定します。

実装例

この項目で説明するclEnqueueWriteBufferとclEnqueueReadBufferの実装例では引数のエントリは、ptrを除けば同じとします。

clEnqueueWriteBuffer(
    queue,
    data_mem,
    CL_TRUE,
    0,
    Sizeof.cl_int*SIZE,
    Pointer.to(data), //(1)
    0,
    null,
    null);

(1)

dataは書き込むデータのホストメモリ領域を指すポインタです。

clEnqueueReadBuffer(
    queue,
    data_mem,
    CL_TRUE,
    0,
    Sizeof.cl_int*SIZE,
    Pointer.to(output), //(1)
    0,
    null,
    null);

(1)

outputは読み込んだデータを保存するメモリ領域を指すポインタです。

それでは空のバッファに対して書き込みをしてそれを読み込むサンプルソースコードを見てみましょう。

以下の例では、配列要素の全てが7に設定した領域のデータをclEnqueueWriteBufferでバッファに書き込みます。その上で、clEnqueueReadBufferを使い書き込んだデータを読み取ります。読み取ったデータの全要素が7と判定します。

package com.book.jocl.buffer;

import static org.jocl.CL.*;

import org.jocl.CL;
import org.jocl.Pointer;
import org.jocl.Sizeof;
import org.jocl.cl_command_queue;
import org.jocl.cl_context;
import org.jocl.cl_context_properties;
import org.jocl.cl_device_id;
import org.jocl.cl_mem;
import org.jocl.cl_platform_id;
import org.jocl.cl_program;

public class ReadWriteBufferTest {

        private static final int SIZE = 4;

    private static cl_context context;
    private static cl_command_queue queue;
    private static cl_program program;

        public static void main(String[] args) throws Exception {

                CL.setExceptionsEnabled(true);

                cl_platform_id[] platform = new cl_platform_id[1];
                cl_device_id[] device = new cl_device_id[1];
                int[] num_devices = new int[1];

                clGetPlatformIDs(1, platform, null);
                clGetDeviceIDs(platform[0], CL_DEVICE_TYPE_GPU, 1, device, num_devices);

                cl_context_properties props = new cl_context_properties();
                props.addProperty(CL_CONTEXT_PLATFORM, platform[0]);
                context = clCreateContext(props, 1, device, null, null, null);

                queue = clCreateCommandQueue(context, device[0], 0, null);

                cl_mem data_mem = clCreateBuffer(context, CL_MEM_READ_ONLY | CL_MEM_ALLOC_HOST_PTR,
                                Sizeof.cl_int * SIZE, null, null);

                int[] data = new int[]{1,2,3,4};

                clEnqueueWriteBuffer(queue, data_mem, CL_TRUE,
                                0, Sizeof.cl_int*SIZE,
                Pointer.to(data), 0, null, null);

                int[] output = new int[4];

                clEnqueueReadBuffer(queue, data_mem, CL_TRUE, 0, Sizeof.cl_int*SIZE, Pointer.to(output),
                                0, null, null);

                clFinish(queue);

                for(int i = 0; i < SIZE; i++) {
                        System.out.println(output[i]);
                }


                clReleaseMemObject(data_mem);
                clReleaseDevice(device[0]);
                clReleaseContext(context);
                clReleaseCommandQueue(queue);
                clReleaseProgram(program);
        }

}

9.3.2. 矩形領域

物理モデルやイメージ処理をしている方は、特定のデータ範囲を切り取ることは良くあることかと思います。

例えばゲームであれば、プレーヤーのフィールド座標によって描画される背景が動いたり。イメージデータとしてメモリには存在する背景の座標情報から、表示させたい部分を切り取りして処理する方式です。

図9.1 図:矩形領域の用例:グレー部分が処理範囲で、それ以外の範囲は不要で切り捨てる

images/WriteRect2D.png

範囲外にはみ出した部分の処理は手動だと相当面倒になります。そのため処理したい矩形領域だけをデバイスがアクセスするメモリー、バッファに複製したり書き込んだりします。OpenCLでは矩形領域の読み込み・書き込みのために以下の2つの関数を用意しています。

  • clEnqueueReadBufferRect
  • clEnqueueWriteBufferRect

注記

この関数は下記の項目で紹介させて頂きます。

矩形処理関数の応用例は以下のような分野が考えられます。

  • 画面フレームのサイズによって一部の矩形領域へアクセス
  • 2次元ないし3次元データのバッチ処理
  • (ワークグループ・ワークアイテムとマッピングした)グリッドを用いたConvolutionアルゴリズム・フィルター
  • 視点によって異なる透視図を描画処理

他方、矩形領域の処理はエンジニアの専門分野によって全く使わないこともあるので、そうした処理を学ぶ必要性が無い分野の方もいるでしょう。次の項目で紹介する矩形領域処理関数に目を通すかは読者の興味や専門に合わせて判断ください。

9.3.3. clEnqueueReadBufferRectとclEnqueueWriteBufferRect

clEnqueueReadBufferRect関数はバッファオブジェクトからホストメモリへの、2Dまたは3Dの矩形領域の読み込みを行うコマンドを挿入します。

clEnqueueWriteBufferRect関数はホストメモリからバッファオブジェクトへの、2Dまたは3Dの矩形領域の書き込みを行うコマンドを挿入します。

注記

詳しくは「表:clEnqueueReadBufferRectとclEnqueueWriteBufferRect」(表B.32「表:clEnqueueReadBufferRectとclEnqueueWriteBufferRect」)を参照ください。

int org.jocl.CL.clEnqueueReadBufferRect(
    cl_command_queue command_queue, //(1)
    cl_mem buffer, //(2)
    boolean blocking_read, //(3)
    long[] buffer_offset, //(4)
    long[] host_offset, //(5)
    long[] region, //(6)
    long buffer_row_pitch, //(7)
    long buffer_slice_pitch, //(8)
    long host_row_pitch, //(9)
    long host_slice_pitch, //(10)
    Pointer ptr, //(11)
    int num_events_in_wait_list, //(12)
    cl_event[] event_wait_list, //(13)
    cl_event event) //(14)

(1)

読み込みコマンドを挿入するコマンドキューを指定。command_queueとbufferは同じOpenCLコンテキスト上で生成されたものとなる必要がある。

(2)

有効なバッファオブジェクトを指定。

(3)

読み込み操作を、ブロッキングで行うのかノンブロッキングで行うのかを指定。blocking_readにCL_TRUEを指定する場合、読み込みコマンドはブロッキングとなる。バッファデータを読み込んでptrが指すメモリ領域に複製し終わるまで、clEnqueueReadBufferRectは戻らない。

(4)

bufferと関連付けられたメモリ領域の(x, y, z)オフセットを指定。 2D矩形領域では、buffer_origin[2]で指定するz値は0となる必要がある。 オフセットのバイト数は、buffer_origin[2]*buffer_slice_pitch + buffer_origin[1]*buffer_row_pitch + buffer_origin[0]で求められる。

(5)

ptrが指すメモリ領域の(x, y, z)オフセットを指定。 2D矩形領域では、host_origin[2]で指定するz値は0となる必要がある。 オフセットのバイト数は、host_origin[2]*host_slice_pitch + host_origin[1]*host_row_pitch + host_origin[0]で求められる。

(6)

読み込みを行う2Dもしくは3D矩形領域の (width, height, depth) をバイトで指定。2D矩形領域では、region[2]で指定するdepth値は1となる必要がある。regionの値は0にはできない。

(7)

bufferと関連付けられたメモリ領域で各行が使用する長さをバイトで指定。buffer_row_pitchに0を指定すると、region[0]を設定したものとして扱われる。

(8)

bufferと関連付けられたメモリ領域で各2Dスライスが使用する長さをバイトで指定。buffer_slice_pitchに0を指定すると、region[1]*buffer_row_pitchを設定したものとして扱われる。

(9)

ptrが指すメモリ領域で各行が使用する長さをバイトで指定。 host_row_pitchに0を指定すると、region[0]を設定したものとして扱われる。

(10)

ptrが指すメモリ領域で各2Dスライスが使用する長さをバイトで指定します。host_slice_pitchに0を指定すると、region[1]*host_row_pitchを設定したものとして扱われる。

(11)

読み込むデータを保存しているホストメモリバッファへのポインタを指定。

(12)

event_wait_list で指定したイベントオブジェクトの数を指定します。

(13)

このコマンドが実行される前に完了していなければならないイベントを指定。event_wait_listがNULLの場合、このコマンドはどのイベントの完了待機もしない。

(14)

このコマンドを識別するイベントオブジェクトが戻され、コマンド完了の確認やコマンド完了待機をする。eventがNULLの場合、アプリケーションはコマンドの状況確認やコマンド完了待ちを行えない。代わりに、clEnqueueBarrierWithWaitListを使うことができる。

int org.jocl.CL.clEnqueueWriteBufferRect(
    cl_command_queue command_queue, //(1)
    cl_mem buffer, //(2)
    boolean blocking_write, //(3)
    long[] buffer_offset, //(4)
    long[] host_offset, //(5)
    long[] region, //(6)
    long buffer_row_pitch, //(7)
    long buffer_slice_pitch, //(8)
    long host_row_pitch, //(9)
    long host_slice_pitch, //(10)
    Pointer ptr, //(11)
    int num_events_in_wait_list, //(12)
    cl_event[] event_wait_list, //(13)
    cl_event event) //(14)

(1)

書き込みコマンドを挿入するコマンドキューを指定。command_queueとbufferは同じOpenCLコンテキスト上で生成されたものとなる必要がある。

(2)

有効なバッファオブジェクトを指定。

(3)

書き込み操作を、ブロッキングで行うのかノンブロッキングで行うのかを指定。

(4)

bufferと関連付けられたメモリ領域の(x, y, z)オフセットを指定。 2D矩形領域では、buffer_origin[2]で指定するz値は0となる必要がある。 オフセットのバイト数は、buffer_origin[2]*buffer_slice_pitch + buffer_origin[1]*buffer_row_pitch + buffer_origin[0]で求められる。

(5)

ptrが指すメモリ領域の(x, y, z)オフセットを指定。 2D矩形領域では、host_origin[2]で指定するz値は0となる必要がある。 オフセットのバイト数は、host_origin[2]*host_slice_pitch + host_origin[1]*host_row_pitch + host_origin[0]で求められる。

(6)

書き込みを行う2Dもしくは3D矩形領域の (widthバイト, height行, depthスライス) を指定。2D矩形領域では、region[2]で指定するdepth値は1となる必要がある。regionの値は0にはできない。

(7)

bufferと関連付けられたメモリ領域で各行が使用する長さをバイトで指定。buffer_row_pitchに0を指定すると、region[0]を設定したものとして扱われる。

(8)

bufferと関連付けられたメモリ領域で各2Dスライスが使用する長さをバイトで指定。buffer_slice_pitchに0を指定すると、region[1]*buffer_row_pitchを設定したものとして扱われる。

(9)

ptrが指すメモリ領域で各行が使用する長さをバイトで指定。 host_row_pitchに0を指定すると、region[0]を設定したものとして扱われる。

(10)

ptrが指すメモリ領域で各2Dスライスが使用する長さをバイトで指定します。host_slice_pitchに0を指定すると、region[1]*host_row_pitchを設定したものとして扱われる。

(11)

書き込むデータの保存先となるホストメモリバッファへのポインタを指定。

(12)

event_wait_list で指定したイベントオブジェクトの数を指定します。

(13)

このコマンドが実行される前に完了していなければならないイベントを指定。event_wait_listがNULLの場合、このコマンドはどのイベントの完了待機もしない。

(14)

このコマンドを識別するイベントオブジェクトが戻され、コマンド完了の確認やコマンド完了待機をする。eventがNULLの場合、アプリケーションはコマンドの状況確認やコマンド完了待ちを行えない。代わりに、clEnqueueBarrierWithWaitListを使うことができる。

9.3.4. clEnqueueCopyBuffer

clEnqueueCopyBuffer関数はバッファオブジェクトで識別されたsrc_bufferからdst_bufferへの複製を行うコマンドを挿入します。

注記

詳しくは「表:clEnqueueCopyBuffer」(表B.33「表:clEnqueueCopyBuffer」)を参照ください。

int org.jocl.CL.clEnqueueCopyBuffer(
    cl_command_queue command_queue, //(1)
    cl_mem src_buffer, //(2)
    cl_mem dst_buffer, //(3)
    long src_offset, //(4)
    long dst_offset, //(5)
    long cb, //(6)
    int num_events_in_wait_list, //(7)
    cl_event[] event_wait_list, //(8)
    cl_event event) //(9)

(1)

複製コマンドを挿入するコマンドキューを指定。command_queue、src_buffer、dst_buffer と関連付けられたOpenCLコンテキストは同じとなる必要がある。

(2)

複製元のバッファオブジェクト。

(3)

複製先のバッファオブジェクト。

(4)

src_bufferから複製を行う際に、読み込み開始位置をどれだけずらすかを指定。

(5)

dst_bufferへ複製を行う際に、書き込み開始位置をどれだけずらすかを指定。

(6)

複製するデータのサイズをバイトで指定。

(7)

event_wait_list で指定したイベントオブジェクトの数を指定します。

(8)

このコマンドが実行される前に完了していなければならないイベントを指定。 event_wait_listがNULLの場合、このコマンドはどのイベントの完了待機もしない。event_wait_listがNULLの場合、num_events_in_wait_listは0となる必要がある。

(9)

このコマンドを識別するイベントオブジェクトが戻され、コマンド完了の確認やコマンド完了待機をする。eventがNULLの場合、アプリケーションはコマンドの状況確認やコマンド完了待ちを行えない。代わりに、clEnqueueBarrierWithWaitListを使うことができる。 event_wait_listとevent引数がNULLでない場合、event引数はevent_wait_list配列の要素を参照すべきでない。

9.3.5. clEnqueueCopyBufferRect

clEnqueueCopyBufferRect関数はsrc_bufferで識別されるバッファオブジェクト内の2Dまたは3D矩形領域から、dst_bufferで識別されるバッファオブジェクト内で2Dまたは3D領域への複製を行うコマンドを挿入します。

注記

詳しくは「表:clEnqueueCopyBufferRect」(表B.34「表:clEnqueueCopyBufferRect」)を参照ください。

int org.jocl.CL.clEnqueueCopyBufferRect(
    cl_command_queue command_queue, //(1)
    cl_mem src_buffer, //(2)
    cl_mem dst_buffer, //(3)
    long[] src_origin, //(4)
    long[] dst_origin, //(5)
    long[] region, //(6)
    long src_row_pitch, //(7)
    long src_slice_pitch, //(8)
    long dst_row_pitch, //(9)
    long dst_slice_pitch, //(10)
    int num_events_in_wait_list, //(11)
    cl_event[] event_wait_list, //(12)
    cl_event event) //(13)

(1)

複製コマンドを挿入するコマンドキューを指定。command_queue、src_buffer、dst_buffer と関連付けられたOpenCLコンテキストは同じとなる必要があります。

(2)

複製元バッファオブジェクト

(3)

複製先バッファオブジェクト

(4)

src_bufferと関連付けられたメモリ領域の(x, y, z)オフセットを指定。2D矩形領域では、src_origin[2]で指定するz値は0となる必要がある。 オフセットのバイト数は、src_origin[2]*src_slice_pitch + src_origin[1]*src_row_pitch + src_origin[0]で求められる。

(5)

dst_bufferと関連付けられたメモリ領域の(x, y, z)オフセットを指定します。2D矩形領域では、dst_origin[2]で指定するz値は0となる必要がある。 オフセットのバイト数は、dst_origin[2]*dst_slice_pitch + dst_origin[1]*dst_row_pitch + dst_origin[0]で求められる。

(6)

複製を行う2Dもしくは3D矩形領域の (widthバイト, height行, depthスライス) を指定。2D矩形領域では、region[2]で指定するdepth値は1となる必要がある。regionの値は0にはできない。

(7)

src_bufferと関連付けられたメモリ領域で各行が使用する長さをバイトで指定。src_row_pitchに0を指定すると、region[0]を設定したものとして扱われる。

(8)

src_bufferと関連付けられたメモリ領域で各2Dスライスが使用する長さをバイトで指定。src_slice_pitchに0を指定すると、region[1]*src_row_pitchを設定したものとして扱われる。

(9)

dst_bufferと関連付けられたメモリ領域で各行が使用する長さをバイトで指定。dst_row_pitch に0を指定すると、region[0]を設定したものとして扱われる。

(10)

dst_bufferと関連付けられたメモリ領域で各2Dスライスが使用する長さをバイトで指定。dst_slice_pitchに0を指定すると、region[1]*dst_row_pitchを設定したものとして扱われる。

(11)

event_wait_list で指定したイベントオブジェクトの数を指定します。

(12)

このコマンドが実行される前に完了していなければならないイベントを指定。event_wait_listがNULLの場合、このコマンドはどのイベントの完了待機もしない。event_wait_listがNULLの場合、num_events_in_wait_listは0となる必要がある。

(13)

このコマンドを識別するイベントオブジェクトが戻され、コマンド完了の確認やコマンド完了待機をする。eventがNULLの場合、アプリケーションはコマンドの状況確認やコマンド完了待ちを行えない。代わりに、clEnqueueBarrierWithWaitListを使うことができる。

clEnqueueCopyBufferRectについては、clEnqueueWriteBufferRectの引数を継承します。src_bufferとdst_bufferがある分と、バッファ間のアラインメントをとる点だけが異なります。

既に矩形領域には相当な解説をしておりますので、この関数についての実装例は割愛させて頂きます。

9.3.6. clEnqueueFillBuffer

clEnqueueFillBuffer関数は指定したパターンサイズのパターンで、バッファオブジェクトをフィル(埋める)するコマンドを挿入します。

注記

詳しくは「表:clEnqueueFillBuffer」(表B.35「表:clEnqueueFillBuffer」)を参照ください。

int org.jocl.CL.clEnqueueFillBuffer(
    cl_command_queue command_queue, //(1)
    cl_mem buffer, //(2)
    Pointer pattern, //(3)
    long pattern_size, //(4)
    long offset, //(5)
    long size, //(6)
    int num_events_in_wait_list, //(7)
    cl_event[] event_wait_list, //(8)
    cl_event event) //(9)

(1)

フィルコマンドが挿入されるコマンドキューを指定。コマンドキューとバッファに関連付けられたOpenCLコンテキストは同じとなる必要がある。

(2)

有効なバッファオブジェクトを指定。

(3)

データパターンを指すポインタ。データパターンのサイズはpattern_sizeで指定(バイトで表す)。パターンはoffsetで開始するバッファ内の領域(sizeバイト)をフィルするのに使われます。データパターンはスカラ型か整数ベクトル型、またはOpenCLがサポートする浮動小数点型となります。

(4)

データパターンのサイズはpattern_sizeで指定(バイトで表す)。

(5)

バッファ内でフィルされる領域の位置をバイトで表した値を指定。pattern_sizeの倍数となる必要がある。

(6)

バッファ内でフィルされる領域のバイトサイズを指定。pattern_sizeの倍数となる必要がある。

(7)

event_wait_listで指定したイベントオブジェクトの数を指定。

(8)

このコマンドが実行される前に完了していなければならないイベントを指定。 event_wait_listがNULLの場合、このコマンドはどのイベントの完了待機もしない。

(9)

このコマンドを識別するイベントオブジェクトが戻され、コマンド完了の確認やコマンド完了待機をする。eventがNULLの場合、アプリケーションはコマンドの状況確認やコマンド完了待ちを行えない。代わりに、clEnqueueBarrierWithWaitListを使うことができる。

Copyright 2018-2019, by Masaki Komatsu