Skip to content

Commit

Permalink
Merge pull request #115 from robbert-vdh/feature/merge-raw-gl-context
Browse files Browse the repository at this point in the history
Merge raw-gl-context into baseview to allow OpenGL contexts to be created during window creation
  • Loading branch information
micahrj authored Mar 7, 2022
2 parents a801001 + 85b6437 commit b371263
Show file tree
Hide file tree
Showing 19 changed files with 1,307 additions and 229 deletions.
6 changes: 4 additions & 2 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ jobs:
with:
toolchain: stable
override: true
- name: Build
- name: Build with default features
run: cargo build --examples --workspace --verbose
- name: Build again with all features
run: cargo build --examples --workspace --all-features --verbose
- name: Run tests
run: cargo test --examples --workspace --verbose
run: cargo test --examples --workspace --all-features --verbose
8 changes: 7 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,18 @@ authors = [
"Billy Messenger <[email protected]>",
"Anton Lazarev <https://antonok.com>",
"Joakim Frostegård <[email protected]>",
"Robbert van der Helm <[email protected]>",
]
edition = "2018"
license = "MIT OR Apache-2.0"

[features]
default = []
opengl = ["uuid", "x11/glx"]

[dependencies]
keyboard-types = { version = "0.6.1", default-features = false }
raw-window-handle = "0.3.3"
raw-window-handle = "0.4.2"

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

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

[target.'cfg(target_os="macos")'.dependencies]
cocoa = "0.24.0"
Expand Down
15 changes: 8 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,14 @@ Interested in learning more about the project? Join us on [discord](https://disc

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

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

## Prerequisites

Expand Down
4 changes: 4 additions & 0 deletions examples/open_window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ fn main() {
title: "baseview".into(),
size: baseview::Size::new(512.0, 512.0),
scale: WindowScalePolicy::SystemScaleFactor,

// TODO: Add an example that uses the OpenGL context
#[cfg(feature = "opengl")]
gl_config: None,
};

let (mut tx, rx) = RingBuffer::new(128);
Expand Down
146 changes: 146 additions & 0 deletions src/gl/macos.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
use std::ffi::c_void;
use std::str::FromStr;

use raw_window_handle::{HasRawWindowHandle, RawWindowHandle};

use cocoa::appkit::{
NSOpenGLContext, NSOpenGLContextParameter, NSOpenGLPFAAccelerated, NSOpenGLPFAAlphaSize,
NSOpenGLPFAColorSize, NSOpenGLPFADepthSize, NSOpenGLPFADoubleBuffer, NSOpenGLPFAMultisample,
NSOpenGLPFAOpenGLProfile, NSOpenGLPFASampleBuffers, NSOpenGLPFASamples, NSOpenGLPFAStencilSize,
NSOpenGLPixelFormat, NSOpenGLProfileVersion3_2Core, NSOpenGLProfileVersion4_1Core,
NSOpenGLProfileVersionLegacy, NSOpenGLView, NSView,
};
use cocoa::base::{id, nil, YES};

use core_foundation::base::TCFType;
use core_foundation::bundle::{CFBundleGetBundleWithIdentifier, CFBundleGetFunctionPointerForName};
use core_foundation::string::CFString;

use objc::{msg_send, sel, sel_impl};

use super::{GlConfig, GlError, Profile};

pub type CreationFailedError = ();
pub struct GlContext {
view: id,
context: id,
}

impl GlContext {
pub unsafe fn create(
parent: &impl HasRawWindowHandle, config: GlConfig,
) -> Result<GlContext, GlError> {
let handle = if let RawWindowHandle::AppKit(handle) = parent.raw_window_handle() {
handle
} else {
return Err(GlError::InvalidWindowHandle);
};

if handle.ns_view.is_null() {
return Err(GlError::InvalidWindowHandle);
}

let parent_view = handle.ns_view as id;

let version = if config.version < (3, 2) && config.profile == Profile::Compatibility {
NSOpenGLProfileVersionLegacy
} else if config.version == (3, 2) && config.profile == Profile::Core {
NSOpenGLProfileVersion3_2Core
} else if config.version > (3, 2) && config.profile == Profile::Core {
NSOpenGLProfileVersion4_1Core
} else {
return Err(GlError::VersionNotSupported);
};

#[rustfmt::skip]
let mut attrs = vec![
NSOpenGLPFAOpenGLProfile as u32, version as u32,
NSOpenGLPFAColorSize as u32, (config.red_bits + config.blue_bits + config.green_bits) as u32,
NSOpenGLPFAAlphaSize as u32, config.alpha_bits as u32,
NSOpenGLPFADepthSize as u32, config.depth_bits as u32,
NSOpenGLPFAStencilSize as u32, config.stencil_bits as u32,
NSOpenGLPFAAccelerated as u32,
];

if config.samples.is_some() {
#[rustfmt::skip]
attrs.extend_from_slice(&[
NSOpenGLPFAMultisample as u32,
NSOpenGLPFASampleBuffers as u32, 1,
NSOpenGLPFASamples as u32, config.samples.unwrap() as u32,
]);
}

if config.double_buffer {
attrs.push(NSOpenGLPFADoubleBuffer as u32);
}

attrs.push(0);

let pixel_format = NSOpenGLPixelFormat::alloc(nil).initWithAttributes_(&attrs);

if pixel_format == nil {
return Err(GlError::CreationFailed(()));
}

let view =
NSOpenGLView::alloc(nil).initWithFrame_pixelFormat_(parent_view.frame(), pixel_format);

if view == nil {
return Err(GlError::CreationFailed(()));
}

view.setWantsBestResolutionOpenGLSurface_(YES);

let () = msg_send![view, retain];
NSOpenGLView::display_(view);
parent_view.addSubview_(view);

let context: id = msg_send![view, openGLContext];
let () = msg_send![context, retain];

context.setValues_forParameter_(
&(config.vsync as i32),
NSOpenGLContextParameter::NSOpenGLCPSwapInterval,
);

let () = msg_send![pixel_format, release];

Ok(GlContext { view, context })
}

pub unsafe fn make_current(&self) {
self.context.makeCurrentContext();
}

pub unsafe fn make_not_current(&self) {
NSOpenGLContext::clearCurrentContext(self.context);
}

pub fn get_proc_address(&self, symbol: &str) -> *const c_void {
let symbol_name = CFString::from_str(symbol).unwrap();
let framework_name = CFString::from_str("com.apple.opengl").unwrap();
let framework =
unsafe { CFBundleGetBundleWithIdentifier(framework_name.as_concrete_TypeRef()) };
let addr = unsafe {
CFBundleGetFunctionPointerForName(framework, symbol_name.as_concrete_TypeRef())
};
addr as *const c_void
}

pub fn swap_buffers(&self) {
unsafe {
self.context.flushBuffer();
let () = msg_send![self.view, setNeedsDisplay: YES];
}
}
}

impl Drop for GlContext {
fn drop(&mut self) {
unsafe {
let () = msg_send![self.context, release];
let () = msg_send![self.view, release];
}
}
}
109 changes: 109 additions & 0 deletions src/gl/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
use std::ffi::c_void;
use std::marker::PhantomData;

// On X11 creating the context is a two step process
#[cfg(not(target_os = "linux"))]
use raw_window_handle::HasRawWindowHandle;

#[cfg(target_os = "windows")]
mod win;
#[cfg(target_os = "windows")]
use win as platform;

// We need to use this directly within the X11 window creation to negotiate the correct visual
#[cfg(target_os = "linux")]
pub(crate) mod x11;
#[cfg(target_os = "linux")]
pub(crate) use self::x11 as platform;

#[cfg(target_os = "macos")]
mod macos;
#[cfg(target_os = "macos")]
use macos as platform;

#[derive(Clone, Debug)]
pub struct GlConfig {
pub version: (u8, u8),
pub profile: Profile,
pub red_bits: u8,
pub blue_bits: u8,
pub green_bits: u8,
pub alpha_bits: u8,
pub depth_bits: u8,
pub stencil_bits: u8,
pub samples: Option<u8>,
pub srgb: bool,
pub double_buffer: bool,
pub vsync: bool,
}

impl Default for GlConfig {
fn default() -> Self {
GlConfig {
version: (3, 2),
profile: Profile::Core,
red_bits: 8,
blue_bits: 8,
green_bits: 8,
alpha_bits: 8,
depth_bits: 24,
stencil_bits: 8,
samples: None,
srgb: true,
double_buffer: true,
vsync: false,
}
}
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Profile {
Compatibility,
Core,
}

#[derive(Debug)]
pub enum GlError {
InvalidWindowHandle,
VersionNotSupported,
CreationFailed(platform::CreationFailedError),
}

pub struct GlContext {
context: platform::GlContext,
phantom: PhantomData<*mut ()>,
}

impl GlContext {
#[cfg(not(target_os = "linux"))]
pub(crate) unsafe fn create(
parent: &impl HasRawWindowHandle, config: GlConfig,
) -> Result<GlContext, GlError> {
platform::GlContext::create(parent, config)
.map(|context| GlContext { context, phantom: PhantomData })
}

/// The X11 version needs to be set up in a different way compared to the Windows and macOS
/// versions. So the platform-specific versions should be used to construct the context within
/// baseview, and then this object can be passed to the user.
#[cfg(target_os = "linux")]
pub(crate) fn new(context: platform::GlContext) -> GlContext {
GlContext { context, phantom: PhantomData }
}

pub unsafe fn make_current(&self) {
self.context.make_current();
}

pub unsafe fn make_not_current(&self) {
self.context.make_not_current();
}

pub fn get_proc_address(&self, symbol: &str) -> *const c_void {
self.context.get_proc_address(symbol)
}

pub fn swap_buffers(&self) {
self.context.swap_buffers();
}
}
Loading

0 comments on commit b371263

Please sign in to comment.