fyrox_graphics/gl/
read_buffer.rs

1// Copyright (c) 2019-present Dmitry Stepanov and Fyrox Engine contributors.
2//
3// Permission is hereby granted, free of charge, to any person obtaining a copy
4// of this software and associated documentation files (the "Software"), to deal
5// in the Software without restriction, including without limitation the rights
6// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7// copies of the Software, and to permit persons to whom the Software is
8// furnished to do so, subject to the following conditions:
9//
10// The above copyright notice and this permission notice shall be included in all
11// copies or substantial portions of the Software.
12//
13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19// SOFTWARE.
20
21use crate::{
22    buffer::{BufferKind, BufferUsage, GpuBufferTrait},
23    core::{algebra::Vector2, math::Rect},
24    error::FrameworkError,
25    framebuffer::GpuFrameBufferTrait,
26    gl::{buffer::GlBuffer, framebuffer::GlFrameBuffer, server::GlGraphicsServer, ToGlConstant},
27    gpu_texture::{image_2d_size_bytes, GpuTextureKind},
28    read_buffer::GpuAsyncReadBufferTrait,
29};
30use glow::{HasContext, PixelPackData};
31use std::cell::Cell;
32use std::rc::Weak;
33
34#[derive(Copy, Clone)]
35struct ReadRequest {
36    fence: glow::Fence,
37}
38
39pub struct GlAsyncReadBuffer {
40    server: Weak<GlGraphicsServer>,
41    buffer: GlBuffer,
42    request: Cell<Option<ReadRequest>>,
43    pixel_count: usize,
44    pixel_size: usize,
45}
46
47impl GlAsyncReadBuffer {
48    pub fn new(
49        server: &GlGraphicsServer,
50        pixel_size: usize,
51        pixel_count: usize,
52    ) -> Result<Self, FrameworkError> {
53        let size_bytes = pixel_count * pixel_size;
54        let buffer = GlBuffer::new(
55            server,
56            size_bytes,
57            BufferKind::PixelRead,
58            BufferUsage::StreamRead,
59        )?;
60        Ok(Self {
61            server: server.weak(),
62            buffer,
63            request: Default::default(),
64            pixel_count,
65            pixel_size,
66        })
67    }
68}
69
70impl GpuAsyncReadBufferTrait for GlAsyncReadBuffer {
71    fn schedule_pixels_transfer(
72        &self,
73        framebuffer: &dyn GpuFrameBufferTrait,
74        color_buffer_index: u32,
75        rect: Option<Rect<i32>>,
76    ) -> Result<(), FrameworkError> {
77        if self.request.get().is_some() {
78            return Ok(());
79        }
80
81        let server = self.server.upgrade().unwrap();
82
83        let framebuffer = framebuffer
84            .as_any()
85            .downcast_ref::<GlFrameBuffer>()
86            .unwrap();
87
88        let color_attachment = &framebuffer
89            .color_attachments()
90            .get(color_buffer_index as usize)
91            .ok_or_else(|| {
92                FrameworkError::Custom(format!(
93                    "Framebuffer {:?} does not have {} color attachment!",
94                    framebuffer.id(),
95                    color_buffer_index
96                ))
97            })?
98            .texture;
99
100        let attachment_pixel_descriptor = color_attachment.pixel_kind().pixel_descriptor();
101
102        let color_attachment_size =
103            if let GpuTextureKind::Rectangle { width, height } = color_attachment.kind() {
104                Vector2::new(width, height)
105            } else {
106                return Err(FrameworkError::Custom(
107                    "Only rectangular textures can be read from GPU!".to_string(),
108                ));
109            };
110
111        let actual_size = image_2d_size_bytes(
112            color_attachment.pixel_kind(),
113            color_attachment_size.x,
114            color_attachment_size.y,
115        );
116        let self_bytes_count = self.pixel_count * self.pixel_size;
117        if actual_size != self_bytes_count {
118            return Err(FrameworkError::Custom(format!(
119                "Pixel buffer size {} does not match the size {} of the color \
120                attachment {} of the frame buffer {:?}",
121                self_bytes_count,
122                actual_size,
123                color_buffer_index,
124                framebuffer.id()
125            )));
126        }
127
128        let target_rect = match rect {
129            Some(rect) => rect,
130            None => Rect::new(
131                0,
132                0,
133                color_attachment_size.x as i32,
134                color_attachment_size.y as i32,
135            ),
136        };
137
138        unsafe {
139            let buffer_gl_usage = self.buffer.kind.into_gl();
140
141            server.gl.bind_buffer(buffer_gl_usage, Some(self.buffer.id));
142
143            server
144                .gl
145                .bind_framebuffer(glow::READ_FRAMEBUFFER, framebuffer.id());
146
147            server
148                .gl
149                .read_buffer(glow::COLOR_ATTACHMENT0 + color_buffer_index);
150
151            server.gl.read_pixels(
152                target_rect.position.x,
153                target_rect.position.y,
154                target_rect.size.x,
155                target_rect.size.y,
156                attachment_pixel_descriptor.format,
157                attachment_pixel_descriptor.data_type,
158                PixelPackData::BufferOffset(0),
159            );
160
161            server.gl.bind_buffer(buffer_gl_usage, None);
162
163            self.request.set(Some(ReadRequest {
164                fence: server
165                    .gl
166                    .fence_sync(glow::SYNC_GPU_COMMANDS_COMPLETE, 0)
167                    .unwrap(),
168            }));
169
170            Ok(())
171        }
172    }
173
174    fn is_request_running(&self) -> bool {
175        self.request.get().is_some()
176    }
177
178    fn try_read(&self) -> Option<Vec<u8>> {
179        let server = self.server.upgrade()?;
180
181        let request = self.request.get()?;
182
183        let mut buffer = vec![0; self.pixel_count * self.pixel_size];
184
185        unsafe {
186            // For some reason, glGetSynciv still blocks execution and produces GPU stall, ruining
187            // the performance. glClientWaitSync with timeout=0 does not have this issue.
188            let fence_state = server.gl.client_wait_sync(request.fence, 0, 0);
189            if fence_state != glow::TIMEOUT_EXPIRED && fence_state != glow::WAIT_FAILED {
190                self.buffer.read_data(&mut buffer).unwrap();
191
192                server.gl.delete_sync(request.fence);
193
194                self.request.set(None);
195
196                Some(buffer)
197            } else {
198                None
199            }
200        }
201    }
202}