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::{Buffer, BufferKind, BufferUsage},
23    core::{algebra::Vector2, math::Rect},
24    error::FrameworkError,
25    framebuffer::FrameBuffer,
26    gl::{buffer::GlBuffer, framebuffer::GlFrameBuffer, server::GlGraphicsServer, ToGlConstant},
27    gpu_texture::{image_2d_size_bytes, GpuTextureKind},
28    read_buffer::AsyncReadBuffer,
29};
30use glow::{HasContext, PixelPackData};
31use std::rc::Weak;
32
33struct ReadRequest {
34    fence: glow::Fence,
35}
36
37pub struct GlAsyncReadBuffer {
38    server: Weak<GlGraphicsServer>,
39    buffer: GlBuffer,
40    request: Option<ReadRequest>,
41    pixel_count: usize,
42    pixel_size: usize,
43}
44
45impl GlAsyncReadBuffer {
46    pub fn new(
47        server: &GlGraphicsServer,
48        pixel_size: usize,
49        pixel_count: usize,
50    ) -> Result<Self, FrameworkError> {
51        let size_bytes = pixel_count * pixel_size;
52        let buffer = GlBuffer::new(
53            server,
54            size_bytes,
55            BufferKind::PixelRead,
56            BufferUsage::StreamRead,
57        )?;
58        Ok(Self {
59            server: server.weak(),
60            buffer,
61            request: None,
62            pixel_count,
63            pixel_size,
64        })
65    }
66}
67
68impl AsyncReadBuffer for GlAsyncReadBuffer {
69    fn schedule_pixels_transfer(
70        &mut self,
71        framebuffer: &dyn FrameBuffer,
72        color_buffer_index: u32,
73        rect: Option<Rect<i32>>,
74    ) -> Result<(), FrameworkError> {
75        if self.request.is_some() {
76            return Ok(());
77        }
78
79        let server = self.server.upgrade().unwrap();
80
81        let framebuffer = framebuffer
82            .as_any()
83            .downcast_ref::<GlFrameBuffer>()
84            .unwrap();
85
86        let color_attachment = &framebuffer
87            .color_attachments()
88            .get(color_buffer_index as usize)
89            .ok_or_else(|| {
90                FrameworkError::Custom(format!(
91                    "Framebuffer {:?} does not have {} color attachment!",
92                    framebuffer.id(),
93                    color_buffer_index
94                ))
95            })?
96            .texture;
97
98        let color_attachment = color_attachment.borrow();
99        let attachment_pixel_descriptor = color_attachment.pixel_kind().pixel_descriptor();
100
101        let color_attachment_size =
102            if let GpuTextureKind::Rectangle { width, height } = color_attachment.kind() {
103                Vector2::new(width, height)
104            } else {
105                return Err(FrameworkError::Custom(
106                    "Only rectangular textures can be read from GPU!".to_string(),
107                ));
108            };
109
110        let actual_size = image_2d_size_bytes(
111            color_attachment.pixel_kind(),
112            color_attachment_size.x,
113            color_attachment_size.y,
114        );
115        let self_bytes_count = self.pixel_count * self.pixel_size;
116        if actual_size != self_bytes_count {
117            return Err(FrameworkError::Custom(format!(
118                "Pixel buffer size {} does not match the size {} of the color \
119                attachment {} of the frame buffer {:?}",
120                self_bytes_count,
121                actual_size,
122                color_buffer_index,
123                framebuffer.id()
124            )));
125        }
126
127        let target_rect = match rect {
128            Some(rect) => rect,
129            None => Rect::new(
130                0,
131                0,
132                color_attachment_size.x as i32,
133                color_attachment_size.y as i32,
134            ),
135        };
136
137        unsafe {
138            let buffer_gl_usage = self.buffer.kind.into_gl();
139
140            server.gl.bind_buffer(buffer_gl_usage, Some(self.buffer.id));
141
142            server
143                .gl
144                .bind_framebuffer(glow::READ_FRAMEBUFFER, framebuffer.id());
145
146            server
147                .gl
148                .read_buffer(glow::COLOR_ATTACHMENT0 + color_buffer_index);
149
150            server.gl.read_pixels(
151                target_rect.position.x,
152                target_rect.position.y,
153                target_rect.size.x,
154                target_rect.size.y,
155                attachment_pixel_descriptor.format,
156                attachment_pixel_descriptor.data_type,
157                PixelPackData::BufferOffset(0),
158            );
159
160            server.gl.bind_buffer(buffer_gl_usage, None);
161
162            self.request = Some(ReadRequest {
163                fence: server
164                    .gl
165                    .fence_sync(glow::SYNC_GPU_COMMANDS_COMPLETE, 0)
166                    .unwrap(),
167            });
168
169            Ok(())
170        }
171    }
172
173    fn is_request_running(&self) -> bool {
174        self.request.is_some()
175    }
176
177    fn try_read(&mut self) -> Option<Vec<u8>> {
178        let server = self.server.upgrade()?;
179
180        let request = self.request.as_ref()?;
181
182        let mut buffer = vec![0; self.pixel_count * self.pixel_size];
183
184        unsafe {
185            // For some reason, glGetSynciv still blocks execution and produces GPU stall, ruining
186            // the performance. glClientWaitSync with timeout=0 does not have this issue.
187            let fence_state = server.gl.client_wait_sync(request.fence, 0, 0);
188            if fence_state != glow::TIMEOUT_EXPIRED && fence_state != glow::WAIT_FAILED {
189                self.buffer.read_data(&mut buffer).unwrap();
190
191                server.gl.delete_sync(request.fence);
192                self.request = None;
193
194                Some(buffer)
195            } else {
196                None
197            }
198        }
199    }
200}