|
@@ -34,98 +34,283 @@ package com.jme3.opencl;
|
|
import java.nio.ByteBuffer;
|
|
import java.nio.ByteBuffer;
|
|
|
|
|
|
/**
|
|
/**
|
|
- *
|
|
|
|
|
|
+ * Wrapper for an OpenCL buffer object.
|
|
|
|
+ * A buffer object stores a one-dimensional collection of elements. Elements of a buffer object can
|
|
|
|
+ * be a scalar data type (such as an int, float), vector data type, or a user-defined structure.
|
|
|
|
+ * <br>
|
|
|
|
+ * Buffers are created by the {@link Context}.
|
|
|
|
+ * <br>
|
|
|
|
+ * All access methods (read/write/copy/map) are available in both sychronized/blocking versions
|
|
|
|
+ * and in async/non-blocking versions. The later ones always return an {@link Event} object
|
|
|
|
+ * and have the prefix -Async in their name.
|
|
|
|
+ *
|
|
|
|
+ * @see Context#createBuffer(long, com.jme3.opencl.MemoryAccess)
|
|
* @author Sebastian Weiss
|
|
* @author Sebastian Weiss
|
|
*/
|
|
*/
|
|
public abstract class Buffer implements OpenCLObject {
|
|
public abstract class Buffer implements OpenCLObject {
|
|
|
|
|
|
- public abstract int getSize();
|
|
|
|
|
|
+ /**
|
|
|
|
+ * @return the size of the buffer in bytes.
|
|
|
|
+ * @see Context#createBuffer(long)
|
|
|
|
+ */
|
|
|
|
+ public abstract long getSize();
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
+ * @return the memory access flags set on creation.
|
|
|
|
+ * @see Context#createBuffer(long, com.jme3.opencl.MemoryAccess)
|
|
|
|
+ */
|
|
public abstract MemoryAccess getMemoryAccessFlags();
|
|
public abstract MemoryAccess getMemoryAccessFlags();
|
|
|
|
|
|
- public abstract void read(CommandQueue queue, ByteBuffer dest, int size, int offset);
|
|
|
|
|
|
+ /**
|
|
|
|
+ * Performs a blocking read of the buffer.
|
|
|
|
+ * The target buffer must have at least {@code size} bytes remaining.
|
|
|
|
+ * This method may set the limit to the last byte read.
|
|
|
|
+ * @param queue the command queue
|
|
|
|
+ * @param dest the target buffer
|
|
|
|
+ * @param size the size in bytes being read
|
|
|
|
+ * @param offset the offset in bytes in the buffer to read from
|
|
|
|
+ */
|
|
|
|
+ public abstract void read(CommandQueue queue, ByteBuffer dest, long size, long offset);
|
|
|
|
|
|
- public void read(CommandQueue queue, ByteBuffer dest, int size) {
|
|
|
|
|
|
+ /**
|
|
|
|
+ * Alternative version of {@link #read(com.jme3.opencl.CommandQueue, java.nio.ByteBuffer, long, long) },
|
|
|
|
+ * sets {@code offset} to zero.
|
|
|
|
+ */
|
|
|
|
+ public void read(CommandQueue queue, ByteBuffer dest, long size) {
|
|
read(queue, dest, size, 0);
|
|
read(queue, dest, size, 0);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
+ * Alternative version of {@link #read(com.jme3.opencl.CommandQueue, java.nio.ByteBuffer, long) },
|
|
|
|
+ * sets {@code size} to {@link #getSize() }.
|
|
|
|
+ */
|
|
public void read(CommandQueue queue, ByteBuffer dest) {
|
|
public void read(CommandQueue queue, ByteBuffer dest) {
|
|
read(queue, dest, getSize());
|
|
read(queue, dest, getSize());
|
|
}
|
|
}
|
|
|
|
|
|
- public abstract Event readAsync(CommandQueue queue, ByteBuffer dest, int size, int offset);
|
|
|
|
|
|
+ /**
|
|
|
|
+ * Performs an async/non-blocking read of the buffer.
|
|
|
|
+ * The target buffer must have at least {@code size} bytes remaining.
|
|
|
|
+ * This method may set the limit to the last byte read.
|
|
|
|
+ * @param queue the command queue
|
|
|
|
+ * @param dest the target buffer
|
|
|
|
+ * @param size the size in bytes being read
|
|
|
|
+ * @param offset the offset in bytes in the buffer to read from
|
|
|
|
+ * @return the event indicating when the memory has been fully read into the provided buffer
|
|
|
|
+ */
|
|
|
|
+ public abstract Event readAsync(CommandQueue queue, ByteBuffer dest, long size, long offset);
|
|
|
|
|
|
- public Event readAsync(CommandQueue queue, ByteBuffer dest, int size) {
|
|
|
|
|
|
+ /**
|
|
|
|
+ * Alternative version of {@link #readAsync(com.jme3.opencl.CommandQueue, java.nio.ByteBuffer, long, long) },
|
|
|
|
+ * sets {@code offset} to zero.
|
|
|
|
+ */
|
|
|
|
+ public Event readAsync(CommandQueue queue, ByteBuffer dest, long size) {
|
|
return readAsync(queue, dest, size, 0);
|
|
return readAsync(queue, dest, size, 0);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
+ * Alternative version of {@link #readAsync(com.jme3.opencl.CommandQueue, java.nio.ByteBuffer, long) },
|
|
|
|
+ * sets {@code size} to {@link #getSize() }
|
|
|
|
+ */
|
|
public Event readAsync(CommandQueue queue, ByteBuffer dest) {
|
|
public Event readAsync(CommandQueue queue, ByteBuffer dest) {
|
|
return readAsync(queue, dest, getSize());
|
|
return readAsync(queue, dest, getSize());
|
|
}
|
|
}
|
|
|
|
|
|
- public abstract void write(CommandQueue queue, ByteBuffer src, int size, int offset);
|
|
|
|
|
|
+ /**
|
|
|
|
+ * Performs a blocking write to the buffer.
|
|
|
|
+ * The target buffer must have at least {@code size} bytes remaining.
|
|
|
|
+ * This method may set the limit to the last byte that will be written.
|
|
|
|
+ * @param queue the command queue
|
|
|
|
+ * @param src the source buffer, its data is written to this buffer
|
|
|
|
+ * @param size the size in bytes to write
|
|
|
|
+ * @param offset the offset into the target buffer
|
|
|
|
+ */
|
|
|
|
+ public abstract void write(CommandQueue queue, ByteBuffer src, long size, long offset);
|
|
|
|
|
|
- public void write(CommandQueue queue, ByteBuffer src, int size) {
|
|
|
|
|
|
+ /**
|
|
|
|
+ * Alternative version of {@link #write(com.jme3.opencl.CommandQueue, java.nio.ByteBuffer, long, long) },
|
|
|
|
+ * sets {@code offset} to zero.
|
|
|
|
+ */
|
|
|
|
+ public void write(CommandQueue queue, ByteBuffer src, long size) {
|
|
write(queue, src, size, 0);
|
|
write(queue, src, size, 0);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
+ * Alternative version of {@link #write(com.jme3.opencl.CommandQueue, java.nio.ByteBuffer, long) },
|
|
|
|
+ * sets {@code size} to {@link #getSize() }.
|
|
|
|
+ */
|
|
public void write(CommandQueue queue, ByteBuffer src) {
|
|
public void write(CommandQueue queue, ByteBuffer src) {
|
|
write(queue, src, getSize());
|
|
write(queue, src, getSize());
|
|
}
|
|
}
|
|
|
|
|
|
- public abstract Event writeAsync(CommandQueue queue, ByteBuffer src, int size, int offset);
|
|
|
|
|
|
+ /**
|
|
|
|
+ * Performs an async/non-blocking write to the buffer.
|
|
|
|
+ * The target buffer must have at least {@code size} bytes remaining.
|
|
|
|
+ * This method may set the limit to the last byte that will be written.
|
|
|
|
+ * @param queue the command queue
|
|
|
|
+ * @param src the source buffer, its data is written to this buffer
|
|
|
|
+ * @param size the size in bytes to write
|
|
|
|
+ * @param offset the offset into the target buffer
|
|
|
|
+ * @return the event object indicating when the write operation is completed
|
|
|
|
+ */
|
|
|
|
+ public abstract Event writeAsync(CommandQueue queue, ByteBuffer src, long size, long offset);
|
|
|
|
|
|
- public Event writeAsync(CommandQueue queue, ByteBuffer src, int size) {
|
|
|
|
|
|
+ /**
|
|
|
|
+ * Alternative version of {@link #writeAsync(com.jme3.opencl.CommandQueue, java.nio.ByteBuffer, long, long) },
|
|
|
|
+ * sets {@code offset} to zero.
|
|
|
|
+ */
|
|
|
|
+ public Event writeAsync(CommandQueue queue, ByteBuffer src, long size) {
|
|
return writeAsync(queue, src, size, 0);
|
|
return writeAsync(queue, src, size, 0);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
+ * Alternative version of {@link #writeAsync(com.jme3.opencl.CommandQueue, java.nio.ByteBuffer, long) },
|
|
|
|
+ * sets {@code size} to {@link #getSize() }.
|
|
|
|
+ */
|
|
public Event writeAsync(CommandQueue queue, ByteBuffer src) {
|
|
public Event writeAsync(CommandQueue queue, ByteBuffer src) {
|
|
return writeAsync(queue, src, getSize());
|
|
return writeAsync(queue, src, getSize());
|
|
}
|
|
}
|
|
|
|
|
|
- public abstract void copyTo(CommandQueue queue, Buffer dest, int size, int srcOffset, int destOffset);
|
|
|
|
|
|
+ /**
|
|
|
|
+ * Performs a blocking copy operation from this buffer to the specified buffer.
|
|
|
|
+ * @param queue the command queue
|
|
|
|
+ * @param dest the target buffer
|
|
|
|
+ * @param size the size in bytes to copy
|
|
|
|
+ * @param srcOffset offset in bytes into this buffer
|
|
|
|
+ * @param destOffset offset in bytes into the target buffer
|
|
|
|
+ */
|
|
|
|
+ public abstract void copyTo(CommandQueue queue, Buffer dest, long size, long srcOffset, long destOffset);
|
|
|
|
|
|
- public void copyTo(CommandQueue queue, Buffer dest, int size) {
|
|
|
|
|
|
+ /**
|
|
|
|
+ * Alternative version of {@link #copyTo(com.jme3.opencl.CommandQueue, com.jme3.opencl.Buffer, long, long, long) },
|
|
|
|
+ * sets {@code srcOffset} and {@code destOffset} to zero.
|
|
|
|
+ */
|
|
|
|
+ public void copyTo(CommandQueue queue, Buffer dest, long size) {
|
|
copyTo(queue, dest, size, 0, 0);
|
|
copyTo(queue, dest, size, 0, 0);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
+ * Alternative version of {@link #copyTo(com.jme3.opencl.CommandQueue, com.jme3.opencl.Buffer, long) },
|
|
|
|
+ * sets {@code size} to {@code this.getSize()}.
|
|
|
|
+ */
|
|
public void copyTo(CommandQueue queue, Buffer dest) {
|
|
public void copyTo(CommandQueue queue, Buffer dest) {
|
|
copyTo(queue, dest, getSize());
|
|
copyTo(queue, dest, getSize());
|
|
}
|
|
}
|
|
|
|
|
|
- public abstract Event copyToAsync(CommandQueue queue, Buffer dest, int size, int srcOffset, int destOffset);
|
|
|
|
|
|
+ /**
|
|
|
|
+ * Performs an async/non-blocking copy operation from this buffer to the specified buffer.
|
|
|
|
+ * @param queue the command queue
|
|
|
|
+ * @param dest the target buffer
|
|
|
|
+ * @param size the size in bytes to copy
|
|
|
|
+ * @param srcOffset offset in bytes into this buffer
|
|
|
|
+ * @param destOffset offset in bytes into the target buffer
|
|
|
|
+ * @return the event object indicating when the copy operation is finished
|
|
|
|
+ */
|
|
|
|
+ public abstract Event copyToAsync(CommandQueue queue, Buffer dest, long size, long srcOffset, long destOffset);
|
|
|
|
|
|
- public Event copyToAsync(CommandQueue queue, Buffer dest, int size) {
|
|
|
|
|
|
+ /**
|
|
|
|
+ * Alternative version of {@link #copyToAsync(com.jme3.opencl.CommandQueue, com.jme3.opencl.Buffer, long, long, long) },
|
|
|
|
+ * sets {@code srcOffset} and {@code destOffset} to zero.
|
|
|
|
+ */
|
|
|
|
+ public Event copyToAsync(CommandQueue queue, Buffer dest, long size) {
|
|
return copyToAsync(queue, dest, size, 0, 0);
|
|
return copyToAsync(queue, dest, size, 0, 0);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
+ * Alternative version of {@link #copyToAsync(com.jme3.opencl.CommandQueue, com.jme3.opencl.Buffer, long) },
|
|
|
|
+ * sets {@code size} to {@code this.getSize()}.
|
|
|
|
+ */
|
|
public Event copyToAsync(CommandQueue queue, Buffer dest) {
|
|
public Event copyToAsync(CommandQueue queue, Buffer dest) {
|
|
return copyToAsync(queue, dest, getSize());
|
|
return copyToAsync(queue, dest, getSize());
|
|
}
|
|
}
|
|
|
|
|
|
- public abstract ByteBuffer map(CommandQueue queue, int size, int offset, MappingAccess access);
|
|
|
|
|
|
+ /**
|
|
|
|
+ * Maps this buffer directly into host memory. This might be the fastest method
|
|
|
|
+ * to access the contents of the buffer since the OpenCL implementation directly
|
|
|
|
+ * provides the memory.<br>
|
|
|
|
+ * <b>Important:</b> The mapped memory MUST be released by calling
|
|
|
|
+ * {@link #unmap(com.jme3.opencl.CommandQueue, java.nio.ByteBuffer) }.
|
|
|
|
+ * @param queue the command queue
|
|
|
|
+ * @param size the size in bytes to map
|
|
|
|
+ * @param offset the offset into this buffer
|
|
|
|
+ * @param access specifies the possible access to the memory: READ_ONLY, WRITE_ONLY, READ_WRITE
|
|
|
|
+ * @return the byte buffer directly reflecting the buffer contents
|
|
|
|
+ */
|
|
|
|
+ public abstract ByteBuffer map(CommandQueue queue, long size, long offset, MappingAccess access);
|
|
|
|
|
|
- public ByteBuffer map(CommandQueue queue, int size, MappingAccess access) {
|
|
|
|
|
|
+ /**
|
|
|
|
+ * Alternative version of {@link #map(com.jme3.opencl.CommandQueue, long, long, com.jme3.opencl.MappingAccess) },
|
|
|
|
+ * sets {@code offset} to zero.
|
|
|
|
+ * <b>Important:</b> The mapped memory MUST be released by calling
|
|
|
|
+ * {@link #unmap(com.jme3.opencl.CommandQueue, java.nio.ByteBuffer) }.
|
|
|
|
+ */
|
|
|
|
+ public ByteBuffer map(CommandQueue queue, long size, MappingAccess access) {
|
|
return map(queue, size, 0, access);
|
|
return map(queue, size, 0, access);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
+ * Alternative version of {@link #map(com.jme3.opencl.CommandQueue, long, com.jme3.opencl.MappingAccess) },
|
|
|
|
+ * sets {@code size} to {@link #getSize() }.
|
|
|
|
+ * <b>Important:</b> The mapped memory MUST be released by calling
|
|
|
|
+ * {@link #unmap(com.jme3.opencl.CommandQueue, java.nio.ByteBuffer) }.
|
|
|
|
+ */
|
|
public ByteBuffer map(CommandQueue queue, MappingAccess access) {
|
|
public ByteBuffer map(CommandQueue queue, MappingAccess access) {
|
|
return map(queue, getSize(), access);
|
|
return map(queue, getSize(), access);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
+ * Unmaps a previously mapped memory.
|
|
|
|
+ * This releases the native resources and for WRITE_ONLY or READ_WRITE access,
|
|
|
|
+ * the memory content is sent back to the GPU.
|
|
|
|
+ * @param queue the command queue
|
|
|
|
+ * @param ptr the buffer that was previously mapped
|
|
|
|
+ */
|
|
public abstract void unmap(CommandQueue queue, ByteBuffer ptr);
|
|
public abstract void unmap(CommandQueue queue, ByteBuffer ptr);
|
|
|
|
|
|
- public abstract AsyncMapping mapAsync(CommandQueue queue, int size, int offset, MappingAccess access);
|
|
|
|
- public AsyncMapping mapAsync(CommandQueue queue, int size, MappingAccess access) {
|
|
|
|
|
|
+ /**
|
|
|
|
+ * Maps this buffer asynchronously into host memory. This might be the fastest method
|
|
|
|
+ * to access the contents of the buffer since the OpenCL implementation directly
|
|
|
|
+ * provides the memory.<br>
|
|
|
|
+ * <b>Important:</b> The mapped memory MUST be released by calling
|
|
|
|
+ * {@link #unmap(com.jme3.opencl.CommandQueue, java.nio.ByteBuffer) }.
|
|
|
|
+ * @param queue the command queue
|
|
|
|
+ * @param size the size in bytes to map
|
|
|
|
+ * @param offset the offset into this buffer
|
|
|
|
+ * @param access specifies the possible access to the memory: READ_ONLY, WRITE_ONLY, READ_WRITE
|
|
|
|
+ * @return the byte buffer directly reflecting the buffer contents
|
|
|
|
+ * and the event indicating when the buffer contents are available
|
|
|
|
+ */
|
|
|
|
+ public abstract AsyncMapping mapAsync(CommandQueue queue, long size, long offset, MappingAccess access);
|
|
|
|
+ /**
|
|
|
|
+ * Alternative version of {@link #mapAsync(com.jme3.opencl.CommandQueue, long, long, com.jme3.opencl.MappingAccess) },
|
|
|
|
+ * sets {@code offset} to zero.
|
|
|
|
+ * <b>Important:</b> The mapped memory MUST be released by calling
|
|
|
|
+ * {@link #unmap(com.jme3.opencl.CommandQueue, java.nio.ByteBuffer) }.
|
|
|
|
+ */
|
|
|
|
+ public AsyncMapping mapAsync(CommandQueue queue, long size, MappingAccess access) {
|
|
return mapAsync(queue, size, 0, access);
|
|
return mapAsync(queue, size, 0, access);
|
|
}
|
|
}
|
|
|
|
+ /**
|
|
|
|
+ * Alternative version of {@link #mapAsync(com.jme3.opencl.CommandQueue, long, com.jme3.opencl.MappingAccess) },
|
|
|
|
+ * sets {@code size} to {@link #getSize() }.
|
|
|
|
+ * <b>Important:</b> The mapped memory MUST be released by calling
|
|
|
|
+ * {@link #unmap(com.jme3.opencl.CommandQueue, java.nio.ByteBuffer) }.
|
|
|
|
+ */
|
|
public AsyncMapping mapAsync(CommandQueue queue, MappingAccess access) {
|
|
public AsyncMapping mapAsync(CommandQueue queue, MappingAccess access) {
|
|
return mapAsync(queue, getSize(), 0, access);
|
|
return mapAsync(queue, getSize(), 0, access);
|
|
}
|
|
}
|
|
|
|
|
|
- public abstract Event fillAsync(CommandQueue queue, ByteBuffer pattern, int size, int offset);
|
|
|
|
-
|
|
|
|
- //TODO: copy to image
|
|
|
|
|
|
+ /**
|
|
|
|
+ * Enqueues a fill operation. This method can be used to initialize or clear
|
|
|
|
+ * a buffer with a certain value.
|
|
|
|
+ * @param queue the command queue
|
|
|
|
+ * @param pattern the buffer containing the filling pattern.
|
|
|
|
+ * The remaining bytes specify the pattern length
|
|
|
|
+ * @param size the size in bytes to fill, must be a multiple of the pattern length
|
|
|
|
+ * @param offset the offset in bytes into the buffer, must be a multiple of the pattern length
|
|
|
|
+ * @return an event indicating when this operation is finished
|
|
|
|
+ */
|
|
|
|
+ public abstract Event fillAsync(CommandQueue queue, ByteBuffer pattern, long size, long offset);
|
|
|
|
|
|
/**
|
|
/**
|
|
* Result of an async mapping operation, contains the event and the target byte buffer.
|
|
* Result of an async mapping operation, contains the event and the target byte buffer.
|
|
@@ -144,18 +329,58 @@ public abstract class Buffer implements OpenCLObject {
|
|
this.buffer = buffer;
|
|
this.buffer = buffer;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
+ * @return the event object indicating when the data in the mapped buffer
|
|
|
|
+ * is available
|
|
|
|
+ */
|
|
public Event getEvent() {
|
|
public Event getEvent() {
|
|
return event;
|
|
return event;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
+ * @return the mapped buffer, only valid when the event object signals completion
|
|
|
|
+ */
|
|
public ByteBuffer getBuffer() {
|
|
public ByteBuffer getBuffer() {
|
|
return buffer;
|
|
return buffer;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
+ * Copies this buffer to the specified image.
|
|
|
|
+ * Note that no format conversion is done.
|
|
|
|
+ * <br>
|
|
|
|
+ * For detailed description of the origin and region paramenter, see the
|
|
|
|
+ * documentation of the {@link Image} class.
|
|
|
|
+ *
|
|
|
|
+ * @param queue the command queue
|
|
|
|
+ * @param dest the target image
|
|
|
|
+ * @param srcOffset the offset in bytes into this buffer
|
|
|
|
+ * @param destOrigin the origin of the copied area
|
|
|
|
+ * @param destRegion the size of the copied area
|
|
|
|
+ * @return the event object
|
|
|
|
+ */
|
|
public abstract Event copyToImageAsync(CommandQueue queue, Image dest, long srcOffset, long[] destOrigin, long[] destRegion);
|
|
public abstract Event copyToImageAsync(CommandQueue queue, Image dest, long srcOffset, long[] destOrigin, long[] destRegion);
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
+ * Aquires this buffer object for using. Only call this method if this buffer
|
|
|
|
+ * represents a shared object from OpenGL, created with e.g.
|
|
|
|
+ * {@link Context#bindVertexBuffer(com.jme3.scene.VertexBuffer, com.jme3.opencl.MemoryAccess) }.
|
|
|
|
+ * This method must be called before the buffer is used. After the work is
|
|
|
|
+ * done, the buffer must be released by calling
|
|
|
|
+ * {@link #releaseBufferForSharingAsync(com.jme3.opencl.CommandQueue) }
|
|
|
|
+ * so that OpenGL can use the VertexBuffer again.
|
|
|
|
+ * @param queue the command queue
|
|
|
|
+ * @return the event object
|
|
|
|
+ */
|
|
public abstract Event acquireBufferForSharingAsync(CommandQueue queue);
|
|
public abstract Event acquireBufferForSharingAsync(CommandQueue queue);
|
|
|
|
+ /**
|
|
|
|
+ * Releases a shared buffer object.
|
|
|
|
+ * Call this method after the buffer object was acquired by
|
|
|
|
+ * {@link #acquireBufferForSharingAsync(com.jme3.opencl.CommandQueue) }
|
|
|
|
+ * to hand the control back to OpenGL.
|
|
|
|
+ * @param queue the command queue
|
|
|
|
+ * @return the event object
|
|
|
|
+ */
|
|
public abstract Event releaseBufferForSharingAsync(CommandQueue queue);
|
|
public abstract Event releaseBufferForSharingAsync(CommandQueue queue);
|
|
//TODO: add variants of the above two methods that don't create the event object, but release the event immediately
|
|
//TODO: add variants of the above two methods that don't create the event object, but release the event immediately
|
|
}
|
|
}
|