mirror of
https://github.com/TomHarte/CLK.git
synced 2025-02-23 03:29:04 +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:
parent
27ca782cac
commit
230b9fc9e6
@ -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">
|
||||
|
@ -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];
|
||||
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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_;
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user