Skip to main content

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