tinydisplay: Add Cocoa-based backend

Adds support for tinydisplay rendering on macOS

Fixes #1285
This commit is contained in:
rdb 2023-03-21 23:19:12 +01:00
parent 545ede9d94
commit a5fe1fa4bd
9 changed files with 598 additions and 1 deletions

View File

@ -5034,6 +5034,8 @@ if not PkgSkip("TINYDISPLAY"):
OPTS=['DIR:panda/src/tinydisplay', 'BUILDING:TINYDISPLAY', 'X11'] OPTS=['DIR:panda/src/tinydisplay', 'BUILDING:TINYDISPLAY', 'X11']
if not PkgSkip("X11"): if not PkgSkip("X11"):
OPTS += ['X11'] OPTS += ['X11']
if not PkgSkip("COCOA"):
OPTS += ['COCOA']
TargetAdd('p3tinydisplay_composite1.obj', opts=OPTS, input='p3tinydisplay_composite1.cxx') TargetAdd('p3tinydisplay_composite1.obj', opts=OPTS, input='p3tinydisplay_composite1.cxx')
TargetAdd('p3tinydisplay_composite2.obj', opts=OPTS, input='p3tinydisplay_composite2.cxx') TargetAdd('p3tinydisplay_composite2.obj', opts=OPTS, input='p3tinydisplay_composite2.cxx')
TargetAdd('p3tinydisplay_ztriangle_1.obj', opts=OPTS, input='ztriangle_1.cxx') TargetAdd('p3tinydisplay_ztriangle_1.obj', opts=OPTS, input='ztriangle_1.cxx')
@ -5044,7 +5046,13 @@ if not PkgSkip("TINYDISPLAY"):
if GetTarget() == 'windows': if GetTarget() == 'windows':
TargetAdd('libp3tinydisplay.dll', input='libp3windisplay.dll') TargetAdd('libp3tinydisplay.dll', input='libp3windisplay.dll')
TargetAdd('libp3tinydisplay.dll', opts=['WINIMM', 'WINGDI', 'WINKERNEL', 'WINOLDNAMES', 'WINUSER', 'WINMM']) TargetAdd('libp3tinydisplay.dll', opts=['WINIMM', 'WINGDI', 'WINKERNEL', 'WINOLDNAMES', 'WINUSER', 'WINMM'])
elif GetTarget() != 'darwin' and not PkgSkip("X11"): elif GetTarget() == 'darwin':
if not PkgSkip("COCOA"):
TargetAdd('libp3tinydisplay_tinyCocoaGraphicsWindow.obj', opts=OPTS, input='tinyCocoaGraphicsWindow.mm')
TargetAdd('libp3tinydisplay.dll', input='libp3tinydisplay_tinyCocoaGraphicsWindow.obj')
TargetAdd('libp3tinydisplay.dll', input='p3cocoadisplay_composite1.obj')
TargetAdd('libp3tinydisplay.dll', opts=['COCOA', 'CARBON', 'QUARTZ'])
elif not PkgSkip("X11"):
TargetAdd('libp3tinydisplay.dll', input='p3x11display_composite1.obj') TargetAdd('libp3tinydisplay.dll', input='p3x11display_composite1.obj')
TargetAdd('libp3tinydisplay.dll', opts=['X11']) TargetAdd('libp3tinydisplay.dll', opts=['X11'])
TargetAdd('libp3tinydisplay.dll', input='p3tinydisplay_composite1.obj') TargetAdd('libp3tinydisplay.dll', input='p3tinydisplay_composite1.obj')

View File

@ -12,6 +12,8 @@
*/ */
#include "config_tinydisplay.h" #include "config_tinydisplay.h"
#include "tinyCocoaGraphicsPipe.h"
#include "tinyCocoaGraphicsWindow.h"
#include "tinyXGraphicsPipe.h" #include "tinyXGraphicsPipe.h"
#include "tinyXGraphicsWindow.h" #include "tinyXGraphicsWindow.h"
#include "tinyWinGraphicsPipe.h" #include "tinyWinGraphicsPipe.h"
@ -79,6 +81,14 @@ init_libtinydisplay() {
GraphicsPipeSelection *selection = GraphicsPipeSelection::get_global_ptr(); GraphicsPipeSelection *selection = GraphicsPipeSelection::get_global_ptr();
#ifdef HAVE_COCOA
TinyCocoaGraphicsPipe::init_type();
TinyCocoaGraphicsWindow::init_type();
selection->add_pipe_type(TinyCocoaGraphicsPipe::get_class_type(),
TinyCocoaGraphicsPipe::pipe_constructor);
ps->set_system_tag("TinyPanda", "native_window_system", "Cocoa");
#endif
#ifdef HAVE_X11 #ifdef HAVE_X11
TinyXGraphicsPipe::init_type(); TinyXGraphicsPipe::init_type();
TinyXGraphicsWindow::init_type(); TinyXGraphicsWindow::init_type();
@ -116,6 +126,10 @@ init_libtinydisplay() {
int int
get_pipe_type_p3tinydisplay() { get_pipe_type_p3tinydisplay() {
#ifdef HAVE_COCOA
return TinyCocoaGraphicsPipe::get_class_type().get_index();
#endif
#ifdef _WIN32 #ifdef _WIN32
return TinyWinGraphicsPipe::get_class_type().get_index(); return TinyWinGraphicsPipe::get_class_type().get_index();
#endif #endif

View File

@ -1,3 +1,4 @@
#include "tinyCocoaGraphicsPipe.cxx"
#include "tinyGraphicsStateGuardian.cxx" #include "tinyGraphicsStateGuardian.cxx"
#include "tinyOffscreenGraphicsPipe.cxx" #include "tinyOffscreenGraphicsPipe.cxx"
#include "tinySDLGraphicsPipe.cxx" #include "tinySDLGraphicsPipe.cxx"

View File

@ -0,0 +1,12 @@
/**
* PANDA 3D SOFTWARE
* Copyright (c) Carnegie Mellon University. All rights reserved.
*
* All use of this software is subject to the terms of the revised BSD
* license. You should have received a copy of this license along
* with this source code in a file named "LICENSE."
*
* @file tinyCocoaGraphicsPipe.I
* @author rdb
* @date 2023-03-21
*/

View File

@ -0,0 +1,114 @@
/**
* PANDA 3D SOFTWARE
* Copyright (c) Carnegie Mellon University. All rights reserved.
*
* All use of this software is subject to the terms of the revised BSD
* license. You should have received a copy of this license along
* with this source code in a file named "LICENSE."
*
* @file tinyCocoaGraphicsPipe.cxx
* @author rdb
* @date 2023-03-21
*/
#include "pandabase.h"
#ifdef HAVE_COCOA
#include "tinyCocoaGraphicsPipe.h"
#include "tinyCocoaGraphicsWindow.h"
#include "tinyGraphicsStateGuardian.h"
#include "tinyGraphicsBuffer.h"
#include "config_tinydisplay.h"
#include "frameBufferProperties.h"
TypeHandle TinyCocoaGraphicsPipe::_type_handle;
/**
* Takes a CoreGraphics display ID, which defaults to the main display.
*/
TinyCocoaGraphicsPipe::
TinyCocoaGraphicsPipe(CGDirectDisplayID display) : CocoaGraphicsPipe(display) {
}
/**
*
*/
TinyCocoaGraphicsPipe::
~TinyCocoaGraphicsPipe() {
}
/**
* Returns the name of the rendering interface associated with this
* GraphicsPipe. This is used to present to the user to allow him/her to
* choose between several possible GraphicsPipes available on a particular
* platform, so the name should be meaningful and unique for a given platform.
*/
std::string TinyCocoaGraphicsPipe::
get_interface_name() const {
return "TinyPanda";
}
/**
* This function is passed to the GraphicsPipeSelection object to allow the
* user to make a default TinyCocoaGraphicsPipe.
*/
PT(GraphicsPipe) TinyCocoaGraphicsPipe::
pipe_constructor() {
return new TinyCocoaGraphicsPipe;
}
/**
* Creates a new window on the pipe, if possible.
*/
PT(GraphicsOutput) TinyCocoaGraphicsPipe::
make_output(const std::string &name,
const FrameBufferProperties &fb_prop,
const WindowProperties &win_prop,
int flags,
GraphicsEngine *engine,
GraphicsStateGuardian *gsg,
GraphicsOutput *host,
int retry,
bool &precertify) {
TinyGraphicsStateGuardian *tinygsg = 0;
if (gsg != 0) {
DCAST_INTO_R(tinygsg, gsg, nullptr);
}
// First thing to try: a TinyCocoaGraphicsWindow
// We check _is_valid only in this case. The pipe will be invalid if it
// can't contact the X server, but that shouldn't prevent the creation of an
// offscreen buffer.
if (retry == 0 && _is_valid) {
if ((flags & BF_require_parasite) != 0 ||
(flags & BF_refuse_window) != 0 ||
(flags & BF_resizeable) != 0 ||
(flags & BF_size_track_host) != 0 ||
(flags & BF_rtt_cumulative) != 0 ||
(flags & BF_can_bind_color) != 0 ||
(flags & BF_can_bind_every) != 0) {
return nullptr;
}
return new TinyCocoaGraphicsWindow(engine, this, name, fb_prop, win_prop,
flags, gsg, host);
}
// Second thing to try: a TinyGraphicsBuffer
// No need to check _is_valid here. We can create an offscreen buffer even
// if the pipe is not technically valid.
if (retry == 1) {
if ((flags & BF_require_parasite) != 0 ||
(flags & BF_require_window) != 0) {
return nullptr;
}
return new TinyGraphicsBuffer(engine, this, name, fb_prop, win_prop, flags, gsg, host);
}
// Nothing else left to try.
return nullptr;
}
#endif // HAVE_COCOA

View File

@ -0,0 +1,70 @@
/**
* PANDA 3D SOFTWARE
* Copyright (c) Carnegie Mellon University. All rights reserved.
*
* All use of this software is subject to the terms of the revised BSD
* license. You should have received a copy of this license along
* with this source code in a file named "LICENSE."
*
* @file tinyCocoaGraphicsPipe.h
* @author rdb
* @date 2023-03-21
*/
#ifndef TINYCOCOAGRAPHICSPIPE_H
#define TINYCOCOAGRAPHICSPIPE_H
#include "pandabase.h"
#ifdef HAVE_COCOA
#include "cocoaGraphicsWindow.h"
#include "cocoaGraphicsPipe.h"
#include "tinyGraphicsStateGuardian.h"
/**
* This graphics pipe represents the interface for creating TinyPanda graphics
* windows on a Cocoa-based (macOS) client.
*/
class EXPCL_TINYDISPLAY TinyCocoaGraphicsPipe : public CocoaGraphicsPipe {
public:
TinyCocoaGraphicsPipe(CGDirectDisplayID display = CGMainDisplayID());
virtual ~TinyCocoaGraphicsPipe();
virtual std::string get_interface_name() const;
static PT(GraphicsPipe) pipe_constructor();
protected:
virtual PT(GraphicsOutput) make_output(const std::string &name,
const FrameBufferProperties &fb_prop,
const WindowProperties &win_prop,
int flags,
GraphicsEngine *engine,
GraphicsStateGuardian *gsg,
GraphicsOutput *host,
int retry,
bool &precertify);
public:
static TypeHandle get_class_type() {
return _type_handle;
}
static void init_type() {
CocoaGraphicsPipe::init_type();
register_type(_type_handle, "TinyCocoaGraphicsPipe",
CocoaGraphicsPipe::get_class_type());
}
virtual TypeHandle get_type() const {
return get_class_type();
}
virtual TypeHandle force_init_type() {init_type(); return get_class_type();}
private:
static TypeHandle _type_handle;
};
#include "tinyCocoaGraphicsPipe.I"
#endif // HAVE_COCOA
#endif

View File

@ -0,0 +1,12 @@
/**
* PANDA 3D SOFTWARE
* Copyright (c) Carnegie Mellon University. All rights reserved.
*
* All use of this software is subject to the terms of the revised BSD
* license. You should have received a copy of this license along
* with this source code in a file named "LICENSE."
*
* @file tinyCocoaGraphicsWindow.I
* @author rdb
* @date 2023-03-21
*/

View File

@ -0,0 +1,88 @@
/**
* PANDA 3D SOFTWARE
* Copyright (c) Carnegie Mellon University. All rights reserved.
*
* All use of this software is subject to the terms of the revised BSD
* license. You should have received a copy of this license along
* with this source code in a file named "LICENSE."
*
* @file tinyCocoaGraphicsWindow.h
* @author rdb
* @date 2023-03-21
*/
#ifndef TINYCOCOAGRAPHICSWINDOW_H
#define TINYCOCOAGRAPHICSWINDOW_H
#include "pandabase.h"
#ifdef HAVE_COCOA
#include "tinyCocoaGraphicsPipe.h"
#include "cocoaGraphicsWindow.h"
#include "small_vector.h"
/**
* Opens a window on macOS to display the TinyPanda software rendering.
*/
class EXPCL_TINYDISPLAY TinyCocoaGraphicsWindow : public CocoaGraphicsWindow {
public:
TinyCocoaGraphicsWindow(GraphicsEngine *engine, GraphicsPipe *pipe,
const std::string &name,
const FrameBufferProperties &fb_prop,
const WindowProperties &win_prop,
int flags,
GraphicsStateGuardian *gsg,
GraphicsOutput *host);
virtual ~TinyCocoaGraphicsWindow();
virtual bool begin_frame(FrameMode mode, Thread *current_thread);
virtual void end_frame(FrameMode mode, Thread *current_thread);
virtual void end_flip();
virtual bool supports_pixel_zoom() const;
virtual void process_events();
protected:
virtual void close_window();
virtual bool open_window();
virtual void pixel_factor_changed();
private:
void create_swap_chain();
void do_present();
private:
CGColorSpaceRef _color_space = nil;
struct SwapBuffer {
ZBuffer *_frame_buffer = nullptr;
CGDataProviderRef _data_provider = nil;
};
small_vector<SwapBuffer, 2> _swap_chain;
int _swap_index = 0;
public:
static TypeHandle get_class_type() {
return _type_handle;
}
static void init_type() {
CocoaGraphicsWindow::init_type();
register_type(_type_handle, "TinyCocoaGraphicsWindow",
CocoaGraphicsWindow::get_class_type());
}
virtual TypeHandle get_type() const {
return get_class_type();
}
virtual TypeHandle force_init_type() {init_type(); return get_class_type();}
private:
static TypeHandle _type_handle;
};
#include "tinyCocoaGraphicsWindow.I"
#endif // HAVE_COCOA
#endif

View File

@ -0,0 +1,278 @@
/**
* PANDA 3D SOFTWARE
* Copyright (c) Carnegie Mellon University. All rights reserved.
*
* All use of this software is subject to the terms of the revised BSD
* license. You should have received a copy of this license along
* with this source code in a file named "LICENSE."
*
* @file tinyCocoaGraphicsWindow.mm
* @author rdb
* @date 2023-03-21
*/
#include "pandabase.h"
#ifdef HAVE_COCOA
#include "tinyCocoaGraphicsWindow.h"
#include "tinyGraphicsStateGuardian.h"
#include "tinyCocoaGraphicsPipe.h"
#include "config_tinydisplay.h"
#import <QuartzCore/CALayer.h>
TypeHandle TinyCocoaGraphicsWindow::_type_handle;
/**
*
*/
TinyCocoaGraphicsWindow::
TinyCocoaGraphicsWindow(GraphicsEngine *engine, GraphicsPipe *pipe,
const std::string &name,
const FrameBufferProperties &fb_prop,
const WindowProperties &win_prop,
int flags,
GraphicsStateGuardian *gsg,
GraphicsOutput *host) :
CocoaGraphicsWindow(engine, pipe, name, fb_prop, win_prop, flags, gsg, host),
_color_space(CGColorSpaceCreateDeviceRGB())
{
update_pixel_factor();
}
/**
*
*/
TinyCocoaGraphicsWindow::
~TinyCocoaGraphicsWindow() {
CGColorSpaceRelease(_color_space);
}
/**
* This function will be called within the draw thread before beginning
* rendering for a given frame. It should do whatever setup is required, and
* return true if the frame should be rendered, or false if it should be
* skipped.
*/
bool TinyCocoaGraphicsWindow::
begin_frame(FrameMode mode, Thread *current_thread) {
begin_frame_spam(mode);
if (_gsg == nullptr) {
return false;
}
TinyGraphicsStateGuardian *tinygsg;
DCAST_INTO_R(tinygsg, _gsg, false);
tinygsg->_current_frame_buffer = _swap_chain[_swap_index]._frame_buffer;
tinygsg->reset_if_new();
if (mode == FM_render) {
// begin_render_texture();
clear_cube_map_selection();
}
_gsg->set_current_properties(&get_fb_properties());
return _gsg->begin_frame(current_thread);
}
/**
* This function will be called within the draw thread after rendering is
* completed for a given frame. It should do whatever finalization is
* required.
*/
void TinyCocoaGraphicsWindow::
end_frame(FrameMode mode, Thread *current_thread) {
end_frame_spam(mode);
nassertv(_gsg != nullptr);
if (mode == FM_render) {
copy_to_textures();
}
_gsg->end_frame(current_thread);
if (mode == FM_render) {
if (_swap_chain.size() == 1) {
do_present();
}
trigger_flip();
clear_cube_map_selection();
}
}
/**
* This function will be called within the draw thread after begin_flip() has
* been called on all windows, to finish the exchange of the front and back
* buffers.
*
* This should cause the window to wait for the flip, if necessary.
*/
void TinyCocoaGraphicsWindow::
end_flip() {
if (_flip_ready) {
do_present();
_swap_index = (_swap_index + 1) % _swap_chain.size();
}
GraphicsWindow::end_flip();
}
/**
* Returns true if a call to set_pixel_zoom() will be respected, false if it
* will be ignored. If this returns false, then get_pixel_factor() will
* always return 1.0, regardless of what value you specify for
* set_pixel_zoom().
*
* This may return false if the underlying renderer doesn't support pixel
* zooming, or if you have called this on a DisplayRegion that doesn't have
* both set_clear_color() and set_clear_depth() enabled.
*/
bool TinyCocoaGraphicsWindow::
supports_pixel_zoom() const {
return true;
}
/**
* Do whatever processing is necessary to ensure that the window responds to
* user events. Also, honor any requests recently made via
* request_properties()
*
* This function is called only within the window thread.
*/
void TinyCocoaGraphicsWindow::
process_events() {
CocoaGraphicsWindow::process_events();
if (!_swap_chain.empty()) {
int xsize = (get_fb_x_size() + 3) & ~3;
int ysize = get_fb_y_size();
ZBuffer *frame_buffer = _swap_chain[0]._frame_buffer;
if (xsize != frame_buffer->xsize ||
ysize != frame_buffer->ysize) {
create_swap_chain();
}
}
}
/**
* Closes the window right now. Called from the window thread.
*/
void TinyCocoaGraphicsWindow::
close_window() {
if (_gsg != nullptr) {
TinyGraphicsStateGuardian *tinygsg;
DCAST_INTO_V(tinygsg, _gsg);
tinygsg->_current_frame_buffer = nullptr;
_gsg.clear();
}
for (SwapBuffer &swap_buffer : _swap_chain) {
CFRelease(swap_buffer._data_provider);
ZB_close(swap_buffer._frame_buffer);
}
CocoaGraphicsWindow::close_window();
}
/**
* Opens the window right now. Called from the window thread. Returns true
* if the window is successfully opened, or false if there was a problem.
*/
bool TinyCocoaGraphicsWindow::
open_window() {
TinyCocoaGraphicsPipe *tinycocoa_pipe;
DCAST_INTO_R(tinycocoa_pipe, _pipe, false);
// GSG CreationInitialization
TinyGraphicsStateGuardian *tinygsg;
if (_gsg == nullptr) {
// There is no old gsg. Create a new one.
tinygsg = new TinyGraphicsStateGuardian(_engine, _pipe, nullptr);
_gsg = tinygsg;
} else {
DCAST_INTO_R(tinygsg, _gsg, false);
}
if (!CocoaGraphicsWindow::open_window()) {
return false;
}
// Make sure we have a CALayer for drawing into.
_view.wantsLayer = YES;
_view.layerContentsRedrawPolicy = NSViewLayerContentsRedrawNever;
create_swap_chain();
if (_swap_chain.empty()) {
tinydisplay_cat.error()
<< "Could not create frame buffer.\n";
return false;
}
tinygsg->_current_frame_buffer = _swap_chain[_swap_index]._frame_buffer;
tinygsg->reset_if_new();
if (!tinygsg->is_valid()) {
close_window();
return false;
}
return true;
}
/**
* Called internally when the pixel factor changes.
*/
void TinyCocoaGraphicsWindow::
pixel_factor_changed() {
CocoaGraphicsWindow::pixel_factor_changed();
create_swap_chain();
}
/**
* Creates a suitable frame buffer for the current window size.
*/
void TinyCocoaGraphicsWindow::
create_swap_chain() {
[_view layer].contents = nil;
_swap_index = 0;
for (SwapBuffer &swap_buffer : _swap_chain) {
CFRelease(swap_buffer._data_provider);
ZB_close(swap_buffer._frame_buffer);
}
LVecBase2i size = get_fb_size();
int num_buffers = get_fb_properties().get_back_buffers() + 1;
_swap_chain.resize(num_buffers);
for (SwapBuffer &swap_buffer : _swap_chain) {
ZBuffer *frame_buffer = ZB_open(size[0], size[1], ZB_MODE_RGBA, 0, 0, 0, 0);
swap_buffer._frame_buffer = frame_buffer;
swap_buffer._data_provider =
CGDataProviderCreateWithData(nullptr, frame_buffer->pbuf,
frame_buffer->linesize * frame_buffer->ysize,
nullptr);
}
}
/**
* Assigns the current framebuffer contents to the layer.
*/
void TinyCocoaGraphicsWindow::
do_present() {
LVecBase2i size = get_fb_size();
SwapBuffer &swap_buffer = _swap_chain[_swap_index];
CGImageRef image =
CGImageCreate(size[0], size[1], 8, 32, swap_buffer._frame_buffer->linesize,
_color_space, kCGBitmapByteOrder32Host | kCGImageAlphaNoneSkipFirst,
swap_buffer._data_provider, nullptr, false, kCGRenderingIntentDefault);
[_view layer].contents = (id)image;
CFRelease(image);
}
#endif // HAVE_COCOA