Skip to content

Commit b371263

Browse files
authored
Merge pull request #115 from robbert-vdh/feature/merge-raw-gl-context
Merge raw-gl-context into baseview to allow OpenGL contexts to be created during window creation
2 parents a801001 + 85b6437 commit b371263

File tree

19 files changed

+1307
-229
lines changed

19 files changed

+1307
-229
lines changed

.github/workflows/rust.yml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@ jobs:
2121
with:
2222
toolchain: stable
2323
override: true
24-
- name: Build
24+
- name: Build with default features
2525
run: cargo build --examples --workspace --verbose
26+
- name: Build again with all features
27+
run: cargo build --examples --workspace --all-features --verbose
2628
- name: Run tests
27-
run: cargo test --examples --workspace --verbose
29+
run: cargo test --examples --workspace --all-features --verbose

Cargo.toml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,18 @@ authors = [
99
"Billy Messenger <[email protected]>",
1010
"Anton Lazarev <https://antonok.com>",
1111
"Joakim Frostegård <[email protected]>",
12+
"Robbert van der Helm <[email protected]>",
1213
]
1314
edition = "2018"
1415
license = "MIT OR Apache-2.0"
1516

17+
[features]
18+
default = []
19+
opengl = ["uuid", "x11/glx"]
20+
1621
[dependencies]
1722
keyboard-types = { version = "0.6.1", default-features = false }
18-
raw-window-handle = "0.3.3"
23+
raw-window-handle = "0.4.2"
1924

2025
[target.'cfg(target_os="linux")'.dependencies]
2126
xcb = { version = "0.9", features = ["thread", "xlib_xcb", "dri2"] }
@@ -25,6 +30,7 @@ nix = "0.22.0"
2530

2631
[target.'cfg(target_os="windows")'.dependencies]
2732
winapi = { version = "0.3.8", features = ["libloaderapi", "winuser", "windef", "minwindef", "guiddef", "combaseapi", "wingdi", "errhandlingapi"] }
33+
uuid = { version = "0.8", features = ["v4"], optional = true }
2834

2935
[target.'cfg(target_os="macos")'.dependencies]
3036
cocoa = "0.24.0"

README.md

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,14 @@ Interested in learning more about the project? Join us on [discord](https://disc
1010

1111
Below is a proposed list of milestones (roughly in-order) and their status. Subject to change at any time.
1212

13-
| Feature | Windows | Mac OS | Linux |
14-
| ----------------------------------------------- | ------------------ | ------------------ | ------------------ |
15-
| Spawns a window, no parent | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
16-
| Cross-platform API for window spawning | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
17-
| Can find DPI scale factor | | :heavy_check_mark: | :heavy_check_mark: |
18-
| Basic event handling (mouse, keyboard) | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
19-
| Parent window support | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
13+
| Feature | Windows | Mac OS | Linux |
14+
| ----------------------------------------------------- | ------------------ | ------------------ | ------------------ |
15+
| Spawns a window, no parent | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
16+
| Cross-platform API for window spawning | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
17+
| Can find DPI scale factor | | :heavy_check_mark: | :heavy_check_mark: |
18+
| Basic event handling (mouse, keyboard) | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
19+
| Parent window support | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
20+
| OpenGL context creation (behind the `opengl` feature) | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
2021

2122
## Prerequisites
2223

examples/open_window.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ fn main() {
3636
title: "baseview".into(),
3737
size: baseview::Size::new(512.0, 512.0),
3838
scale: WindowScalePolicy::SystemScaleFactor,
39+
40+
// TODO: Add an example that uses the OpenGL context
41+
#[cfg(feature = "opengl")]
42+
gl_config: None,
3943
};
4044

4145
let (mut tx, rx) = RingBuffer::new(128);

src/gl/macos.rs

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
use std::ffi::c_void;
2+
use std::str::FromStr;
3+
4+
use raw_window_handle::{HasRawWindowHandle, RawWindowHandle};
5+
6+
use cocoa::appkit::{
7+
NSOpenGLContext, NSOpenGLContextParameter, NSOpenGLPFAAccelerated, NSOpenGLPFAAlphaSize,
8+
NSOpenGLPFAColorSize, NSOpenGLPFADepthSize, NSOpenGLPFADoubleBuffer, NSOpenGLPFAMultisample,
9+
NSOpenGLPFAOpenGLProfile, NSOpenGLPFASampleBuffers, NSOpenGLPFASamples, NSOpenGLPFAStencilSize,
10+
NSOpenGLPixelFormat, NSOpenGLProfileVersion3_2Core, NSOpenGLProfileVersion4_1Core,
11+
NSOpenGLProfileVersionLegacy, NSOpenGLView, NSView,
12+
};
13+
use cocoa::base::{id, nil, YES};
14+
15+
use core_foundation::base::TCFType;
16+
use core_foundation::bundle::{CFBundleGetBundleWithIdentifier, CFBundleGetFunctionPointerForName};
17+
use core_foundation::string::CFString;
18+
19+
use objc::{msg_send, sel, sel_impl};
20+
21+
use super::{GlConfig, GlError, Profile};
22+
23+
pub type CreationFailedError = ();
24+
pub struct GlContext {
25+
view: id,
26+
context: id,
27+
}
28+
29+
impl GlContext {
30+
pub unsafe fn create(
31+
parent: &impl HasRawWindowHandle, config: GlConfig,
32+
) -> Result<GlContext, GlError> {
33+
let handle = if let RawWindowHandle::AppKit(handle) = parent.raw_window_handle() {
34+
handle
35+
} else {
36+
return Err(GlError::InvalidWindowHandle);
37+
};
38+
39+
if handle.ns_view.is_null() {
40+
return Err(GlError::InvalidWindowHandle);
41+
}
42+
43+
let parent_view = handle.ns_view as id;
44+
45+
let version = if config.version < (3, 2) && config.profile == Profile::Compatibility {
46+
NSOpenGLProfileVersionLegacy
47+
} else if config.version == (3, 2) && config.profile == Profile::Core {
48+
NSOpenGLProfileVersion3_2Core
49+
} else if config.version > (3, 2) && config.profile == Profile::Core {
50+
NSOpenGLProfileVersion4_1Core
51+
} else {
52+
return Err(GlError::VersionNotSupported);
53+
};
54+
55+
#[rustfmt::skip]
56+
let mut attrs = vec![
57+
NSOpenGLPFAOpenGLProfile as u32, version as u32,
58+
NSOpenGLPFAColorSize as u32, (config.red_bits + config.blue_bits + config.green_bits) as u32,
59+
NSOpenGLPFAAlphaSize as u32, config.alpha_bits as u32,
60+
NSOpenGLPFADepthSize as u32, config.depth_bits as u32,
61+
NSOpenGLPFAStencilSize as u32, config.stencil_bits as u32,
62+
NSOpenGLPFAAccelerated as u32,
63+
];
64+
65+
if config.samples.is_some() {
66+
#[rustfmt::skip]
67+
attrs.extend_from_slice(&[
68+
NSOpenGLPFAMultisample as u32,
69+
NSOpenGLPFASampleBuffers as u32, 1,
70+
NSOpenGLPFASamples as u32, config.samples.unwrap() as u32,
71+
]);
72+
}
73+
74+
if config.double_buffer {
75+
attrs.push(NSOpenGLPFADoubleBuffer as u32);
76+
}
77+
78+
attrs.push(0);
79+
80+
let pixel_format = NSOpenGLPixelFormat::alloc(nil).initWithAttributes_(&attrs);
81+
82+
if pixel_format == nil {
83+
return Err(GlError::CreationFailed(()));
84+
}
85+
86+
let view =
87+
NSOpenGLView::alloc(nil).initWithFrame_pixelFormat_(parent_view.frame(), pixel_format);
88+
89+
if view == nil {
90+
return Err(GlError::CreationFailed(()));
91+
}
92+
93+
view.setWantsBestResolutionOpenGLSurface_(YES);
94+
95+
let () = msg_send![view, retain];
96+
NSOpenGLView::display_(view);
97+
parent_view.addSubview_(view);
98+
99+
let context: id = msg_send![view, openGLContext];
100+
let () = msg_send![context, retain];
101+
102+
context.setValues_forParameter_(
103+
&(config.vsync as i32),
104+
NSOpenGLContextParameter::NSOpenGLCPSwapInterval,
105+
);
106+
107+
let () = msg_send![pixel_format, release];
108+
109+
Ok(GlContext { view, context })
110+
}
111+
112+
pub unsafe fn make_current(&self) {
113+
self.context.makeCurrentContext();
114+
}
115+
116+
pub unsafe fn make_not_current(&self) {
117+
NSOpenGLContext::clearCurrentContext(self.context);
118+
}
119+
120+
pub fn get_proc_address(&self, symbol: &str) -> *const c_void {
121+
let symbol_name = CFString::from_str(symbol).unwrap();
122+
let framework_name = CFString::from_str("com.apple.opengl").unwrap();
123+
let framework =
124+
unsafe { CFBundleGetBundleWithIdentifier(framework_name.as_concrete_TypeRef()) };
125+
let addr = unsafe {
126+
CFBundleGetFunctionPointerForName(framework, symbol_name.as_concrete_TypeRef())
127+
};
128+
addr as *const c_void
129+
}
130+
131+
pub fn swap_buffers(&self) {
132+
unsafe {
133+
self.context.flushBuffer();
134+
let () = msg_send![self.view, setNeedsDisplay: YES];
135+
}
136+
}
137+
}
138+
139+
impl Drop for GlContext {
140+
fn drop(&mut self) {
141+
unsafe {
142+
let () = msg_send![self.context, release];
143+
let () = msg_send![self.view, release];
144+
}
145+
}
146+
}

src/gl/mod.rs

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
use std::ffi::c_void;
2+
use std::marker::PhantomData;
3+
4+
// On X11 creating the context is a two step process
5+
#[cfg(not(target_os = "linux"))]
6+
use raw_window_handle::HasRawWindowHandle;
7+
8+
#[cfg(target_os = "windows")]
9+
mod win;
10+
#[cfg(target_os = "windows")]
11+
use win as platform;
12+
13+
// We need to use this directly within the X11 window creation to negotiate the correct visual
14+
#[cfg(target_os = "linux")]
15+
pub(crate) mod x11;
16+
#[cfg(target_os = "linux")]
17+
pub(crate) use self::x11 as platform;
18+
19+
#[cfg(target_os = "macos")]
20+
mod macos;
21+
#[cfg(target_os = "macos")]
22+
use macos as platform;
23+
24+
#[derive(Clone, Debug)]
25+
pub struct GlConfig {
26+
pub version: (u8, u8),
27+
pub profile: Profile,
28+
pub red_bits: u8,
29+
pub blue_bits: u8,
30+
pub green_bits: u8,
31+
pub alpha_bits: u8,
32+
pub depth_bits: u8,
33+
pub stencil_bits: u8,
34+
pub samples: Option<u8>,
35+
pub srgb: bool,
36+
pub double_buffer: bool,
37+
pub vsync: bool,
38+
}
39+
40+
impl Default for GlConfig {
41+
fn default() -> Self {
42+
GlConfig {
43+
version: (3, 2),
44+
profile: Profile::Core,
45+
red_bits: 8,
46+
blue_bits: 8,
47+
green_bits: 8,
48+
alpha_bits: 8,
49+
depth_bits: 24,
50+
stencil_bits: 8,
51+
samples: None,
52+
srgb: true,
53+
double_buffer: true,
54+
vsync: false,
55+
}
56+
}
57+
}
58+
59+
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
60+
pub enum Profile {
61+
Compatibility,
62+
Core,
63+
}
64+
65+
#[derive(Debug)]
66+
pub enum GlError {
67+
InvalidWindowHandle,
68+
VersionNotSupported,
69+
CreationFailed(platform::CreationFailedError),
70+
}
71+
72+
pub struct GlContext {
73+
context: platform::GlContext,
74+
phantom: PhantomData<*mut ()>,
75+
}
76+
77+
impl GlContext {
78+
#[cfg(not(target_os = "linux"))]
79+
pub(crate) unsafe fn create(
80+
parent: &impl HasRawWindowHandle, config: GlConfig,
81+
) -> Result<GlContext, GlError> {
82+
platform::GlContext::create(parent, config)
83+
.map(|context| GlContext { context, phantom: PhantomData })
84+
}
85+
86+
/// The X11 version needs to be set up in a different way compared to the Windows and macOS
87+
/// versions. So the platform-specific versions should be used to construct the context within
88+
/// baseview, and then this object can be passed to the user.
89+
#[cfg(target_os = "linux")]
90+
pub(crate) fn new(context: platform::GlContext) -> GlContext {
91+
GlContext { context, phantom: PhantomData }
92+
}
93+
94+
pub unsafe fn make_current(&self) {
95+
self.context.make_current();
96+
}
97+
98+
pub unsafe fn make_not_current(&self) {
99+
self.context.make_not_current();
100+
}
101+
102+
pub fn get_proc_address(&self, symbol: &str) -> *const c_void {
103+
self.context.get_proc_address(symbol)
104+
}
105+
106+
pub fn swap_buffers(&self) {
107+
self.context.swap_buffers();
108+
}
109+
}

0 commit comments

Comments
 (0)