1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-06-25 18:30:07 +00:00

Permits multiple simultaneous scan reading ranges.

Also updates the OpenGL scan target as per the latest movements of things.
This commit is contained in:
Thomas Harte 2020-08-12 22:08:41 -04:00
parent 27ca782cac
commit 230b9fc9e6
5 changed files with 82 additions and 48 deletions

View File

@ -27,7 +27,7 @@
<rect key="frame" x="0.0" y="0.0" width="600" height="450"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<openGLView hidden="YES" wantsLayer="YES" stencilSize="8bit" useAuxiliaryDepthBufferStencil="NO" allowOffline="YES" wantsBestResolutionOpenGLSurface="YES" translatesAutoresizingMaskIntoConstraints="NO" id="DEG-fq-cjd" customClass="CSScanTargetView">
<openGLView hidden="YES" wantsLayer="YES" useAuxiliaryDepthBufferStencil="NO" allowOffline="YES" wantsBestResolutionOpenGLSurface="YES" translatesAutoresizingMaskIntoConstraints="NO" id="DEG-fq-cjd" customClass="CSScanTargetView">
<rect key="frame" x="0.0" y="0.0" width="600" height="450"/>
</openGLView>
<box hidden="YES" boxType="custom" cornerRadius="4" title="Box" titlePosition="noTitle" translatesAutoresizingMaskIntoConstraints="NO" id="4ap-Gi-2AO">

View File

@ -243,38 +243,43 @@ using BufferingScanTarget = Outputs::Display::BufferingScanTarget;
[encoder setVertexBuffer:_uniformsBuffer offset:0 atIndex:1];
[encoder setFragmentBuffer:_uniformsBuffer offset:0 atIndex:0];
_scanTarget.perform([=] (const BufferingScanTarget::OutputArea &outputArea) {
// Ensure texture changes are noted.
const auto writeAreaModificationStart = size_t(outputArea.start.write_area_x + outputArea.start.write_area_y * 2048) * _bytesPerInputPixel;
const auto writeAreaModificationEnd = size_t(outputArea.end.write_area_x + outputArea.end.write_area_y * 2048) * _bytesPerInputPixel;
if(writeAreaModificationStart != writeAreaModificationEnd) {
if(writeAreaModificationStart < writeAreaModificationEnd) {
[_writeAreaBuffer didModifyRange:NSMakeRange(writeAreaModificationStart, writeAreaModificationEnd - writeAreaModificationStart)];
} else {
[_writeAreaBuffer didModifyRange:NSMakeRange(writeAreaModificationStart, _totalTextureBytes - writeAreaModificationStart)];
if(writeAreaModificationEnd) {
[_writeAreaBuffer didModifyRange:NSMakeRange(0, writeAreaModificationEnd)];
}
}
const auto outputArea = _scanTarget.get_output_area();
}
// TEMPORARY: just draw the scans.
if(outputArea.start.scan != outputArea.end.scan) {
if(outputArea.start.scan < outputArea.end.scan) {
[encoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4 instanceCount:outputArea.end.scan - outputArea.start.scan baseInstance:outputArea.start.scan];
} else {
[encoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4 instanceCount:NumBufferedScans - outputArea.start.scan baseInstance:outputArea.start.scan];
if(outputArea.end.scan) {
[encoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4 instanceCount:outputArea.end.scan];
}
// Ensure texture changes are noted.
const auto writeAreaModificationStart = size_t(outputArea.start.write_area_x + outputArea.start.write_area_y * 2048) * _bytesPerInputPixel;
const auto writeAreaModificationEnd = size_t(outputArea.end.write_area_x + outputArea.end.write_area_y * 2048) * _bytesPerInputPixel;
if(writeAreaModificationStart != writeAreaModificationEnd) {
if(writeAreaModificationStart < writeAreaModificationEnd) {
[_writeAreaBuffer didModifyRange:NSMakeRange(writeAreaModificationStart, writeAreaModificationEnd - writeAreaModificationStart)];
} else {
[_writeAreaBuffer didModifyRange:NSMakeRange(writeAreaModificationStart, _totalTextureBytes - writeAreaModificationStart)];
if(writeAreaModificationEnd) {
[_writeAreaBuffer didModifyRange:NSMakeRange(0, writeAreaModificationEnd)];
}
}
});
}
// TEMPORARY: just draw the scans.
if(outputArea.start.scan != outputArea.end.scan) {
if(outputArea.start.scan < outputArea.end.scan) {
[encoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4 instanceCount:outputArea.end.scan - outputArea.start.scan baseInstance:outputArea.start.scan];
} else {
[encoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4 instanceCount:NumBufferedScans - outputArea.start.scan baseInstance:outputArea.start.scan];
if(outputArea.end.scan) {
[encoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4 instanceCount:outputArea.end.scan];
}
}
}
// Complete encoding.
[encoder endEncoding];
// Add a callback to update the buffer.
[commandBuffer addCompletedHandler:^(id<MTLCommandBuffer> _Nonnull) {
self->_scanTarget.complete_output_area(outputArea);
}];
// Register the drawable's presentation, finalise and commit.
[commandBuffer presentDrawable:view.currentDrawable];
[commandBuffer commit];

View File

@ -118,8 +118,9 @@ void ScanTarget::setup_pipeline() {
const auto data_type_size = Outputs::Display::size_for_data_type(modals.input_data_type);
// Resize the texture only if required.
if(data_type_size != write_area_data_size()) {
write_area_texture_.resize(WriteAreaWidth*WriteAreaHeight*data_type_size);
const size_t required_size = WriteAreaWidth*WriteAreaHeight*data_type_size;
if(required_size != write_area_data_size()) {
write_area_texture_.resize(required_size);
set_write_area(write_area_texture_.data());
}
@ -186,7 +187,9 @@ void ScanTarget::update(int, int output_height) {
true);
// Grab the new output list.
perform([=] (const OutputArea &area) {
perform([=] {
OutputArea area = get_output_area();
// Establish the pipeline if necessary.
const auto new_modals = BufferingScanTarget::new_modals();
const bool did_setup_pipeline = bool(new_modals);
@ -478,6 +481,7 @@ void ScanTarget::update(int, int output_height) {
// Grab a fence sync object to avoid busy waiting upon the next extry into this
// function, and reset the is_updating_ flag.
fence_ = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
complete_output_area(area);
});
}

View File

@ -284,40 +284,45 @@ void BufferingScanTarget::set_modals(Modals modals) {
// MARK: - Consumer.
void BufferingScanTarget::perform(const std::function<void(const OutputArea &)> &function) {
#ifdef ONE_BIG_LOCK
std::lock_guard lock_guard(producer_mutex_);
#endif
BufferingScanTarget::OutputArea BufferingScanTarget::get_output_area() {
// The area to draw is that between the read pointers, representing wherever reading
// last stopped, and the submit pointers, representing all the new data that has been
// cleared for submission.
const auto submit_pointers = submit_pointers_.load(std::memory_order::memory_order_acquire);
const auto read_pointers = read_pointers_.load(std::memory_order::memory_order_relaxed);
const auto read_ahead_pointers = read_ahead_pointers_.load(std::memory_order::memory_order_relaxed);
OutputArea area;
area.start.line = read_pointers.line;
area.start.line = read_ahead_pointers.line;
area.end.line = submit_pointers.line;
area.start.scan = read_pointers.scan_buffer;
area.start.scan = read_ahead_pointers.scan_buffer;
area.end.scan = submit_pointers.scan_buffer;
area.start.write_area_x = TextureAddressGetX(read_pointers.write_area);
area.start.write_area_y = TextureAddressGetY(read_pointers.write_area);
area.start.write_area_x = TextureAddressGetX(read_ahead_pointers.write_area);
area.start.write_area_y = TextureAddressGetY(read_ahead_pointers.write_area);
area.end.write_area_x = TextureAddressGetX(submit_pointers.write_area);
area.end.write_area_y = TextureAddressGetY(submit_pointers.write_area);
// Perform only while holding the is_updating lock.
while(is_updating_.test_and_set(std::memory_order_acquire));
function(area);
is_updating_.clear(std::memory_order_release);
// Update the read-ahead pointers.
read_ahead_pointers_.store(submit_pointers, std::memory_order::memory_order_relaxed);
// Update the read pointers.
read_pointers_.store(submit_pointers, std::memory_order::memory_order_relaxed);
return area;
}
void BufferingScanTarget::complete_output_area(const OutputArea &area) {
PointerSet new_read_pointers;
new_read_pointers.line = uint16_t(area.end.line);
new_read_pointers.scan_buffer = uint16_t(area.end.scan);
new_read_pointers.write_area = TextureAddress(area.end.write_area_x, area.end.write_area_y);
read_pointers_.store(new_read_pointers, std::memory_order::memory_order_relaxed);
}
void BufferingScanTarget::perform(const std::function<void(void)> &function) {
#ifdef ONE_BIG_LOCK
std::lock_guard lock_guard(producer_mutex_);
#endif
while(is_updating_.test_and_set(std::memory_order_acquire));
function();
is_updating_.clear(std::memory_order_release);

View File

@ -114,6 +114,10 @@ class BufferingScanTarget: public Outputs::Display::ScanTarget {
/// (iii) the number of lines that have been completed.
///
/// New write areas and scans are exposed only upon completion of the corresponding lines.
/// The values indicated by the start point are the first that should be drawn. Those indicated
/// by the end point are one after the final that should be drawn.
///
/// So e.g. start.scan = 23, end.scan = 24 means draw a single scan, index 23.
struct OutputArea {
struct Endpoint {
int write_area_x, write_area_y;
@ -123,13 +127,27 @@ class BufferingScanTarget: public Outputs::Display::ScanTarget {
Endpoint start, end;
};
/// Gets the current range of content that has been posted but not yet returned by
/// a previous call to get_output_area().
///
/// Does not require the caller to be within a @c perform block.
OutputArea get_output_area();
/// Announces that the output area has now completed output, freeing up its memory for
/// further modification.
///
/// It is the caller's responsibility to ensure that the areas passed to complete_output_area
/// are those from get_output_area and are marked as completed in the same order that
/// they were originally provided.
///
/// Does not require the caller to be within a @c perform block.
void complete_output_area(const OutputArea &);
/// Performs @c action ensuring that no other @c perform actions, or any
/// change to modals, occurs simultaneously.
void perform(const std::function<void(void)> &action);
/// Acts as per void(void) @c perform but also dequeues all latest available video output.
void perform(const std::function<void(const OutputArea &)> &);
/// @returns new Modals if any have been set since the last call to get_new_modals().
/// The caller must be within a @c perform block.
const Modals *new_modals();
@ -201,6 +219,8 @@ class BufferingScanTarget: public Outputs::Display::ScanTarget {
/// may run and is therefore used by both producer and consumer.
std::atomic<PointerSet> read_pointers_;
std::atomic<PointerSet> read_ahead_pointers_;
/// This is used as a spinlock to guard `perform` calls.
std::atomic_flag is_updating_;